Advertisement
ejrh

network.py

Apr 26th, 2011
185
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.21 KB | None | 0 0
  1. """
  2. Network.py, an experimental clone of KDE's KNetwalk.
  3. Copyright (C) 2010-11  Edmund Horner
  4.  
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  18.  
  19. See http://www.gnu.org/licenses/gpl-2.0.html for full licence.
  20. """
  21. import sys
  22. import time
  23. import random
  24. import pygame
  25.  
  26.  
  27. class Tile(object):
  28.     def __init__(self, x, y, ports=[False]*4, source=False):
  29.         self.x = x
  30.         self.y = y
  31.         self.ports = ports[:]
  32.         self.source = source
  33.         self.true_ports = ports[:]
  34.         self.locked = False
  35.         self.heat = 0.0
  36.         self.prev_heat = 0.0
  37.         self.possibles = [True]*4
  38.         self.necessaries = [False]*4
  39.         self.dead_end = False
  40.         self.live_end = False
  41.         self.mark = False
  42.  
  43.  
  44.     def rotate(self, steps=1):
  45.         self.ports = self.ports[steps:] + self.ports[:steps]
  46.         self.heat = 0.0
  47.  
  48.  
  49. class Map(object):
  50.    
  51.     DEFAULT_WIDTH=9
  52.     DEFAULT_HEIGHT=9
  53.    
  54.     def __init__(self, width, height, torus=False, num_seeds=1):
  55.         self.width = self.DEFAULT_WIDTH
  56.         self.height = self.DEFAULT_HEIGHT
  57.         self.torus = False
  58.         self.num_seeds = 1
  59.         self.clear(width, height, torus, num_seeds)
  60.  
  61.  
  62.     def clear(self, width=None, height=None, torus=None, num_seeds=None):
  63.         if width is not None:
  64.             self.width = width
  65.         if height is not None:
  66.             self.height = height
  67.         if torus is not None:
  68.             self.torus = torus
  69.         if num_seeds is not None:
  70.             self.num_seeds = num_seeds
  71.         self.tiles = [None]*self.height
  72.         for i in range(self.height):
  73.             self.tiles[i] = [None]*self.width
  74.         self.sinks = 0
  75.         self.lights = 0
  76.         self.locks = 0
  77.  
  78.  
  79.     def generate(self):
  80.         seeds = []
  81.         for k in range(self.num_seeds):
  82.             i = random.randint(0, self.height - 1)
  83.             j = random.randint(0, self.height - 1)
  84.             t = Tile(j, i, source=True)
  85.             self.tiles[i][j] = t
  86.             seeds.append(t)
  87.        
  88.         while seeds:
  89.             r = random.randint(0,len(seeds)-1)
  90.             s = seeds[r]
  91.             ns = self.get_neighbours(s)
  92.             if all([n is not None for n in ns]):
  93.                 seeds = seeds[:r] + seeds[r+1:]
  94.                 continue
  95.             r = random.randint(0,3)
  96.             if ns[r] is not None:
  97.                 continue
  98.             x,y = self.get_coords(s.x, s.y, r)
  99.             t = Tile(x, y)
  100.             self.tiles[y][x] = t
  101.             if sum(s.ports) < 3:
  102.                 s.ports[r] = True
  103.                 t.ports[(r+2)%4] = True
  104.                 seeds.append(t)
  105.        
  106.         for row in self.tiles:
  107.             for t in row:
  108.                 t.true_ports = t.ports[:]
  109.                 t.rotate(random.randint(0,3))
  110.                 if sum(t.ports) == 1 and not t.source:
  111.                     self.sinks += 1
  112.                 if sum(t.ports) == 0:
  113.                     self.locked = True
  114.  
  115.  
  116.     def get_neighbours(self, s):
  117.         ns = [False]*4
  118.        
  119.         if self.torus:
  120.             for i in range(4):
  121.                 x,y = self.get_coords(s.x, s.y, i)
  122.                 ns[i] = self.tiles[y][x]
  123.             return ns
  124.        
  125.         if s.y > 0:
  126.             ns[0] = self.tiles[s.y-1][s.x]
  127.         if s.x > 0:
  128.             ns[1] = self.tiles[s.y][s.x-1]
  129.         if s.y < self.height-1:
  130.             ns[2] = self.tiles[s.y+1][s.x]
  131.         if s.x < self.width-1:
  132.             ns[3] = self.tiles[s.y][s.x+1]
  133.         return ns
  134.  
  135.  
  136.     def get_coords(self, x, y, num):
  137.         if num == 0:
  138.             x,y = x, y-1
  139.         elif num == 1:
  140.             x,y = x-1, y
  141.         elif num == 2:
  142.             x,y = x, y+1
  143.         else:
  144.             x,y = x+1, y
  145.        
  146.         x = x % self.width
  147.         y = y % self.height
  148.         return x, y
  149.  
  150.  
  151.     def update(self):
  152.         adj = []
  153.         for i in range(self.height):
  154.             adj.append([-0.01]*self.width)
  155.        
  156.         for i in range(self.height):
  157.             for j in range(self.width):
  158.                 if self.tiles[i][j] in [None,False]:
  159.                     continue
  160.                 if self.tiles[i][j].source:
  161.                     adj[i][j] = 1 - self.tiles[i][j].heat
  162.                 else:
  163.                     ns = self.get_neighbours(self.tiles[i][j])
  164.                     for k in range(4):
  165.                         if ns[k] not in [False,None] and self.tiles[i][j].ports[k] and ns[k].ports[(k + 2) % 4]:
  166.                             d = (ns[k].heat - self.tiles[i][j].heat)/2.0
  167.                             if d < 0:
  168.                                 d = 0
  169.                             adj[i][j] += d
  170.        
  171.         changed = False
  172.         self.lights = 0
  173.         self.locks = 0
  174.         for i in range(self.height):
  175.             for j in range(self.width):
  176.                 tile = self.tiles[i][j]
  177.                 if tile in [None,False]:
  178.                     continue
  179.                 tile.prev_heat = tile.heat
  180.                 tile.heat += adj[i][j]
  181.                 changed2 = adj[i][j] != 0.0
  182.                 if tile.heat < 0:
  183.                     tile.heat = 0
  184.                     changed2 = False
  185.                 if tile.heat > 1.0:
  186.                     tile.heat = 1.0
  187.                     changed2 = False
  188.                 changed |= changed2
  189.                
  190.                 if sum(tile.ports) == 1 and not tile.source:
  191.                     if tile.heat > 0.0 and tile.heat >= tile.prev_heat:
  192.                         self.lights += 1
  193.                
  194.                 if tile.locked:
  195.                     self.locks += 1
  196.        
  197.         return changed
  198.  
  199.  
  200.     def solve(self):
  201.         for row in self.tiles:
  202.             for t in row:
  203.                 t.ports = t.true_ports
  204.  
  205.  
  206.     def scroll(self, dx, dy):
  207.         dx = (dx + self.width) % self.width
  208.         dy = (dy + self.height) % self.height
  209.         if dy != 0:
  210.             self.tiles = self.tiles[dy:] + self.tiles[:dy]
  211.         if dx != 0:
  212.             for i in range(self.height):
  213.                 self.tiles[i] = self.tiles[i][dx:] + self.tiles[i][:dx]
  214.        
  215.         for row in self.tiles:
  216.             for tile in row:
  217.                 tile.x = (tile.x - dx) % self.width
  218.                 tile.y = (tile.y - dy) % self.height
  219.  
  220.  
  221. class Window(object):
  222.  
  223.     def __init__(self, map, width, height, display):
  224.         self.map = map
  225.         self.width = width
  226.         self.height = height
  227.         self.display = display
  228.         self.font = pygame.font.SysFont(None, 25)
  229.         self.scroll_x = 0
  230.         self.scroll_y = 0
  231.         self.frame = 0
  232.  
  233.  
  234.     def scroll(self, dx, dy):
  235.         self.scroll_x += dx
  236.         self.scroll_y += dy
  237.         if not self.map.torus:
  238.             max_x = self.map.width - self.width
  239.             max_y = self.map.height - self.height
  240.             if self.scroll_x < 0:
  241.                 self.scroll_x = 0
  242.             if self.scroll_y < 0:
  243.                 self.scroll_y = 0
  244.             if self.scroll_x > max_x:
  245.                 self.scroll_x = max_x
  246.             if self.scroll_y > max_y:
  247.                 self.scroll_y = max_y
  248.         else:
  249.             self.scroll_x = self.scroll_x % self.map.width
  250.             self.scroll_y = self.scroll_y % self.map.height
  251.  
  252.  
  253.     def draw(self):
  254.         self.draw_map()
  255.         #pygame.image.save(self.display, 'network%04d.png' % self.frame)
  256.         self.frame += 1
  257.  
  258.     def draw_tile(self, tile, row, col):
  259.         if tile is None or not any(tile.ports):
  260.             pygame.draw.rect(self.display, (50, 50, 100), pygame.Rect(col*50+1, row*50+1, 48, 48))
  261.             return
  262.         if tile.locked:
  263.             pygame.draw.rect(self.display, (50, 100, 150), pygame.Rect(col*50+1, row*50+1, 48, 48))
  264.         else:
  265.             pygame.draw.rect(self.display, (150, 100, 50), pygame.Rect(col*50+1, row*50+1, 48, 48))
  266.         wire_col = [tile.heat*200, 50, 200-tile.heat*200]
  267.         if tile.heat < tile.prev_heat:
  268.             wire_col[1] = 200
  269.         if tile.ports[0]:
  270.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*50+21, row*50+1, 8, 24))
  271.         if tile.ports[1]:
  272.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*50+1, row*50+21, 24, 8))
  273.         if tile.ports[2]:
  274.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*50+21, row*50+25, 8, 24))
  275.         if tile.ports[3]:
  276.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*50+25, row*50+21, 24, 8))
  277.         if tile.source:
  278.             pygame.draw.circle(self.display, (200, 150, 50), (col*50+25, row*50+25), 12)
  279.         if any(tile.ports):
  280.             pygame.draw.circle(self.display, wire_col, (col*50+25, row*50+25), 4)
  281.         if sum(tile.ports) == 1 and not tile.source:
  282.             pygame.draw.rect(self.display, (100, 200, 100), pygame.Rect(col*50+11, row*50+11, 28, 28))
  283.             if tile.heat > 0.0 and tile.heat >= tile.prev_heat:
  284.                 pygame.draw.rect(self.display, (250, 250, 0), pygame.Rect(col*50+21, row*50+21, 8, 8))
  285.         #text = self.font.render("%0.2f" % tile.heat, False, (255,255,255))
  286.         #self.display.blit(text, (col*50, row*50))
  287.         extra_str = ""
  288.         if tile.mark:
  289.             extra_str = extra_str + 'M'
  290.         if tile.dead_end:
  291.             extra_str = extra_str + 'D'
  292.         if tile.live_end:
  293.             extra_str = extra_str + 'L'
  294.         text = self.font.render(extra_str, False, (255,255,255))
  295.         self.display.blit(text, (col*50, row*50 + 50 - text.get_height()))
  296.  
  297.     def draw_mini_tile(self, tile, row, col, offset_x, offset_y):
  298.         if tile is None or not any(tile.ports):
  299.             return
  300.         bg_col = (100, 100, 100)
  301.         if (col - self.scroll_x) % self.map.width < self.width and (row - self.scroll_y) % self.map.height < self.height:
  302.             bg_col = (150, 150, 150)
  303.         if tile.mark:
  304.             bg_col = (255, 255, 255)
  305.         pygame.draw.rect(self.display, bg_col, pygame.Rect(col*6+offset_x, row*6+offset_y, 6, 6))
  306.         wire_col = [tile.heat*200, 50, 200-tile.heat*200]
  307.         if tile.heat < tile.prev_heat:
  308.             wire_col[1] = 200
  309.         if tile.ports[0]:
  310.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*6+offset_x+2, row*6+offset_y, 2,4))
  311.         if tile.ports[1]:
  312.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*6+offset_x, row*6+offset_y+2, 4,2))
  313.         if tile.ports[2]:
  314.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*6+offset_x+2, row*6+offset_y+2, 2,4))
  315.         if tile.ports[3]:
  316.             pygame.draw.rect(self.display, wire_col, pygame.Rect(col*6+offset_x+2, row*6+offset_y+2, 4,2))
  317.         if sum(tile.ports) == 1 and not tile.source:
  318.             pygame.draw.rect(self.display, (100, 200, 100), pygame.Rect(col*6+offset_x+2, row*6+offset_y+2, 2,2))
  319.             if tile.heat > 0.0 and tile.heat >= tile.prev_heat:
  320.                 pygame.draw.rect(self.display, (250, 250, 0), pygame.Rect(col*6+offset_x+2, row*6+offset_y+2, 2,2))
  321.  
  322.     def draw_map(self):
  323.         for i in range(self.height):
  324.             for j in range(self.width):
  325.                 t = self.map.tiles[(self.scroll_y + i) % self.map.height][(self.scroll_x + j) % self.map.width]
  326.                 self.draw_tile(t, i, j)
  327.        
  328.         info_str = '%d locks, %d lights' % (self.map.locks, self.map.lights)
  329.         if self.map.lights == self.map.sinks:
  330.             info_str = info_str + ' , Won!'
  331.         text = self.font.render(info_str, False, (255,255,255))
  332.         self.display.blit(text, (0,0))
  333.            
  334.         offset_x = self.display.get_size()[0] - 6 * self.map.width - 10
  335.         offset_y = 6
  336.         for i in range(self.map.height):
  337.             for j in range(self.map.width):
  338.                 self.draw_mini_tile(self.map.tiles[i][j], i, j, offset_x, offset_y)
  339.    
  340.     def locate_click(self, x, y):
  341.         x = x / 50
  342.         y = y / 50
  343.         return (x + self.scroll_x) % self.map.width, (y + self.scroll_y) % self.map.height
  344.  
  345.  
  346. solve_stack = []
  347. def solve_one(map, extra=False):
  348.     if len(solve_stack) == 0:
  349.         for i in range(map.height):
  350.             for j in range(map.width):
  351.                 solve_stack.append((i, j))
  352.         random.Random().shuffle(solve_stack)
  353.    
  354.     while len(solve_stack) > 0:
  355.         i, j = solve_stack.pop()
  356.         tile = map.tiles[i][j]
  357.         ns = map.get_neighbours(tile)
  358.        
  359.         if tile.source and not tile.live_end:
  360.             tile.live_end = True
  361.        
  362.         if sum(tile.ports) == 1 and not tile.dead_end:
  363.             tile.dead_end = True
  364.        
  365.         # If most connected neighbours are dead ends, so's this.
  366.         if not tile.dead_end:
  367.             not_deads = 0
  368.             for k in range(4):
  369.                 if not tile.necessaries[k]:
  370.                     continue
  371.                 if not ns[k].dead_end:
  372.                     not_deads += 1
  373.             if not_deads <= 1:
  374.                 tile.dead_end = True
  375.        
  376.         # If at least one connected neighbours is a live end, so's this.
  377.         if not tile.live_end:
  378.             lives = 0
  379.             for k in range(4):
  380.                 if not tile.necessaries[k]:
  381.                     continue
  382.                 if ns[k].live_end:
  383.                     lives += 1
  384.             if lives >= 1:
  385.                 tile.live_end = True
  386.        
  387.         if tile.locked or not any(tile.ports):
  388.             tile.possibles = tile.ports
  389.             tile.necessaries = tile.ports
  390.             continue
  391.         if sum(tile.possibles) == sum(tile.ports):
  392.             tile.ports = tile.possibles
  393.             if not tile.locked:
  394.                 tile.locked = True
  395.                 for n in ns:
  396.                     solve_stack.append((n.y, n.x))
  397.                 continue
  398.         if sum(tile.necessaries) == sum(tile.ports):
  399.             tile.ports = tile.necessaries
  400.             if not tile.locked:
  401.                 tile.locked = True
  402.                 for n in ns:
  403.                     solve_stack.append((n.y, n.x))
  404.                 continue
  405.        
  406.         # If all but one of my possible neighbours is dead, then the one remaining
  407.         # one is necessary.
  408.         num_deads = 0
  409.         not_dead = None
  410.         for k in range(4):
  411.             if not tile.possibles[k]:
  412.                 continue
  413.             if ns[k].dead_end:
  414.                 num_deads += 1
  415.             elif not tile.necessaries[k]:
  416.                 not_dead = k
  417.         if not_dead is not None and num_deads == sum(tile.possibles) - 1:
  418.             if extra:
  419.                 tile.necessaries[not_dead] = True
  420.                 tile.mark = True
  421.                 return False
  422.        
  423.         ns = map.get_neighbours(tile)
  424.         for k in range(4):
  425.             # Copy necessary and possible information from neighbours
  426.             if not ns[k].possibles[(k + 2) % 4]:
  427.                 tile.possibles[k] = False
  428.             if ns[k].necessaries[(k + 2) % 4]:
  429.                 tile.necessaries[k] = True
  430.             if tile.ports in [[True,False,True,False], [False,True,False,True]]:
  431.                 # If it's a horizontal or vertical pipe, the opposite of necessary is necessary
  432.                 if ns[k].necessaries[(k + 2) % 4]:
  433.                     tile.necessaries[(k + 2) % 4] = True
  434.                 # And the opposite of impossible is impossible
  435.                 if not ns[k].possibles[(k + 2) % 4]:
  436.                     tile.possibles[(k + 2) % 4] = False
  437.             elif sum(tile.ports) == 2:
  438.                 # If it's a corner, the opposite of a necessary is impossible
  439.                 if ns[k].necessaries[(k + 2) % 4]:
  440.                     tile.possibles[(k + 2) % 4] = False
  441.                 # And the opposite if impossible is necessary
  442.                 if not ns[k].possibles[(k + 2) % 4]:
  443.                     tile.necessaries[(k + 2) % 4] = True
  444.             elif sum(tile.ports) == 1:
  445.                 # If it's an endpoint and the neighbour is an endpoint, that connection is impossible
  446.                 if sum(ns[k].ports) == 1:
  447.                     tile.possibles[k] = False
  448.            
  449.             # If i'm live, and the neighbour not necessary and also live, then
  450.             # it's impossible.
  451.             if tile.live_end and ns[k].live_end and not tile.necessaries[k]:
  452.                 tile.possibles[k] = False
  453.            
  454.             # Check if nearest three edges are necessary, if so this is impossible
  455.             if extra:
  456.                 neccs = True
  457.                 n = tile
  458.                 for i in range(3):
  459.                     n = map.get_neighbours(n)[(k+i) % 4]
  460.                     if not n.necessaries[(k+i+1) % 4]:
  461.                         neccs = False
  462.                         break
  463.                 if neccs:
  464.                     tile.possibles[k] = False
  465.    
  466.     return True
  467.  
  468.  
  469. def main(args=None):
  470.     if args is None:
  471.         args = sys.argv
  472.    
  473.     WINDOW_WIDTH=9
  474.     WINDOW_HEIGHT=9
  475.    
  476.     pygame.init()
  477.     display = pygame.display.set_mode((WINDOW_WIDTH * 50, WINDOW_HEIGHT * 50))
  478.     pygame.time.set_timer(pygame.USEREVENT+1, 250)
  479.  
  480.     map = Map(20, 20, True, 3)
  481.     window = Window(map, WINDOW_WIDTH, WINDOW_HEIGHT, display)
  482.  
  483.     map.generate()
  484.     window.draw()
  485.     pygame.display.flip()
  486.    
  487.     auto_solve = False
  488.     extra_solve = False
  489.  
  490.     while True:
  491.         ev = pygame.event.wait()
  492.         if ev.type == pygame.KEYDOWN:
  493.             if ev.key == pygame.K_ESCAPE:
  494.                 break
  495.             elif ev.key == pygame.K_r:
  496.                 map.clear()
  497.                 map.generate()
  498.                 window.draw()
  499.                 pygame.display.flip()
  500.             elif ev.key == pygame.K_f:
  501.                 map.solve()
  502.                 window.draw()
  503.                 pygame.display.flip()
  504.             elif ev.key == pygame.K_a:
  505.                 auto_solve = not auto_solve
  506.             elif ev.key == pygame.K_x:
  507.                 extra_solve = not extra_solve
  508.             elif ev.key == pygame.K_s:
  509.                 solve_one(map)
  510.                 window.draw()
  511.                 pygame.display.flip()
  512.             elif ev.key == pygame.K_UP:
  513.                 window.scroll(0,-1)
  514.                 window.draw()
  515.                 pygame.display.flip()
  516.             elif ev.key == pygame.K_DOWN:
  517.                 window.scroll(0,1)
  518.                 window.draw()
  519.                 pygame.display.flip()
  520.             elif ev.key == pygame.K_LEFT:
  521.                 window.scroll(-1,0)
  522.                 window.draw()
  523.                 pygame.display.flip()
  524.             elif ev.key == pygame.K_RIGHT:
  525.                 window.scroll(1,0)
  526.                 window.draw()
  527.                 pygame.display.flip()
  528.         if ev.type == pygame.MOUSEBUTTONDOWN:
  529.             x, y = ev.pos
  530.             x, y = window.locate_click(x, y)
  531.             if x >= 0 and y >= 0 and x < map.width and y < map.height:
  532.                 if ev.button == 1 and not map.tiles[y][x].locked:
  533.                     map.tiles[y][x].rotate(3)
  534.                 elif ev.button == 3 and not map.tiles[y][x].locked:
  535.                     map.tiles[y][x].rotate(1)
  536.                 elif ev.button == 2:
  537.                     map.tiles[y][x].locked = not map.tiles[y][x].locked
  538.                     if not map.tiles[y][x].locked:
  539.                         map.tiles[y][x].possibles = [True]*4
  540.                         map.tiles[y][x].necessaries = [False]*4
  541.                 window.draw()
  542.                 pygame.display.flip()
  543.         elif ev.type == pygame.USEREVENT+1:
  544.             if auto_solve:
  545.                 auto_solve = solve_one(map, extra_solve)
  546.             if map.update():
  547.                 window.draw()
  548.                 pygame.display.flip()
  549.  
  550.     pygame.quit
  551.  
  552.  
  553. try:
  554.     import psyco
  555.     psyco.full()
  556.     #print 'Optimised'
  557. except ImportError:
  558.     #print 'Not optimised'
  559.     pass
  560.  
  561.  
  562. if __name__ == '__main__':
  563.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement