Advertisement
headkase

Conway's Game of Life by headkase

Feb 1st, 2020
270
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.01 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. """
  4.  
  5. Conway's Game of Life v2.1
  6.  
  7. Copyright © 2020 headkase, CC0
  8.  
  9. To the extent possible under law, the person who associated CC0
  10. with this work has waived all copyright and related or
  11. neighboring rights to this work.
  12.  
  13. Keyboard commands:
  14.  Escape     : Quit
  15.  Space      : Toggle pause
  16.  Arrow Keys : Pan view
  17.  1          : Select R-pentomino
  18.  2          : Select F-heptomino
  19.  Q          : Increase cell size
  20.  A          : Decrease cell size
  21.  
  22. """
  23. import pygame
  24. from pygame import FULLSCREEN, QUIT, KEYDOWN, KEYUP, K_ESCAPE, K_SPACE
  25. from pygame import K_UP, K_DOWN, K_LEFT, K_RIGHT, K_1, K_2, K_q, K_a
  26. from sys import exit
  27.  
  28.  
  29. class Life_Model:
  30.     """Conway's Game of Life model."""
  31.     def __init__(self):
  32.         """Initialize the parameters and domain of the model."""
  33.         # A list of pattern sets
  34.         self.patternbank = []
  35.         # This pattern is a R-pentomino
  36.         # https://www.conwaylife.com/wiki/R-pentomino
  37.         self.patternbank.append({(0, 0), (0, -1), (1, -1), (-1, 0), (0, 1)})
  38.         # This pattern is a F-heptomino
  39.         # https://www.conwaylife.com/wiki/F-heptomino
  40.         self.patternbank.append({(-1, -1), (0, -1), (0, 0), (0, 1), (0, 2),
  41.                                  (1, 2), (2, 2)})
  42.         # Coordinates around a cell, given as a delta table
  43.         self.neighbours = ((-1, -1), (-1, 0), (-1, 1), (0, -1),
  44.                            (0, 1), (1, -1), (1, 0), (1, 1))
  45.  
  46.     def process(self):
  47.         """Iterate the Life model."""
  48.         # If not paused then process the model
  49.         if not self.paused:
  50.             # For every cell in the life set check the cell and its
  51.             # neighbours for the population conditions
  52.             new_life = set()
  53.             for cell in self.life:
  54.                 # Check this cell
  55.                 if self.population(cell) == 3 or self.population(cell) == 2:
  56.                     new_life.add(cell)
  57.                 # Check all the neighbours of this cell
  58.                 for new_cell in self.neighbours:
  59.                     test_cell = (cell[0] + new_cell[0], cell[1] + new_cell[1])
  60.                     if self.population(test_cell) == 3:
  61.                         new_life.add(test_cell)
  62.             # Replace the old set with the new
  63.             self.life = new_life
  64.  
  65.     def population(self, cell):
  66.         """Return a count of the neighbours around a cell."""
  67.         count = 0
  68.         # For the delta table entries generate tuples of (x, y) and
  69.         # then test them for membership in the life set
  70.         for position in self.neighbours:
  71.             position_x = cell[0] + position[0]
  72.             position_y = cell[1] + position[1]
  73.             if (position_x, position_y) in self.life:
  74.                 count += 1
  75.         return count
  76.  
  77.     def reset_model(self, pattern):
  78.         """Reset the model."""
  79.         self.paused = True
  80.         self.life = self.patternbank[pattern].copy()
  81.  
  82.  
  83. class Life_View:
  84.     """Conway's Game of Life View."""
  85.     def __init__(self, screen, model):
  86.         """Initialize the view."""
  87.         self.background_colour = pygame.Color('cornflowerblue')
  88.         self.cell_colour = pygame.Color('white')
  89.         self.screen = screen
  90.         self.display_rect = self.screen.get_rect()
  91.         self.model = model
  92.         # Initialize the model pattern and viewport
  93.         self.clear_view(0)
  94.  
  95.     def clear_view(self, pattern):
  96.         """Bring the view to a default state."""
  97.         self.model.reset_model(pattern)
  98.         self.viewport_centre_x, self.viewport_centre_y = 0, 0
  99.         self.calibrate()
  100.  
  101.     def calibrate(self, cellsize=4):
  102.         """Recalibrate the parameters for display."""
  103.         self.cell_size = cellsize
  104.         world_x_units = int(self.display_rect.width / self.cell_size)
  105.         world_y_units = int(self.display_rect.height / self.cell_size)
  106.         self.screen_origin_x = world_x_units / 2
  107.         self.screen_origin_y = world_y_units / 2
  108.         self.keyup = self.keydown = self.keyleft = self.keyright = False
  109.  
  110.     def process(self):
  111.         """Iterate the view logic."""
  112.         self.manage_state()
  113.         self.render()
  114.  
  115.     def manage_state(self):
  116.         """Manage the overall state of the view."""
  117.         # If arrow key states pan the viewport
  118.         if self.keyup:
  119.             self.viewport_centre_y -= 1
  120.         if self.keydown:
  121.             self.viewport_centre_y += 1
  122.         if self.keyleft:
  123.             self.viewport_centre_x -= 1
  124.         if self.keyright:
  125.             self.viewport_centre_x += 1
  126.  
  127.     def render(self):
  128.         """Render the graphical output."""
  129.         self.screen.fill(self.background_colour)
  130.         for cell in self.model.life:
  131.             # Unpack x and y cell coordinates
  132.             x, y = cell[0], cell[1]
  133.             # Given origin, viewport, x & y cell coordinates calculate
  134.             # graphical x position and y position with a cell size
  135.             xpos = int(((self.screen_origin_x + (self.viewport_centre_x + x))
  136.                        * self.cell_size))
  137.             ypos = int(((self.screen_origin_y + (self.viewport_centre_y + y))
  138.                        * self.cell_size))
  139.             # Check to see if the cell is on screen and if so draw it
  140.             bounded = (xpos >= 0) and (xpos <= self.display_rect.width) and \
  141.                       (ypos >= 0) and (ypos <= self.display_rect.height)
  142.             if bounded:
  143.                 self.screen.fill(self.cell_colour, pygame.Rect(
  144.                                     xpos, ypos,
  145.                                     self.cell_size - 1, self.cell_size - 1))
  146.  
  147.     def parse_key_down(self, key):
  148.         """Parse the key commands to control both the model and view."""
  149.         # Start panning viewport on arrow key down
  150.         if key == K_UP:
  151.             self.keyup = True
  152.         elif key == K_DOWN:
  153.             self.keydown = True
  154.         elif key == K_LEFT:
  155.             self.keyleft = True
  156.         elif key == K_RIGHT:
  157.             self.keyright = True
  158.         # Increase cell size
  159.         elif key == K_q:
  160.             new_size = self.cell_size + 2
  161.             if new_size > 30:
  162.                 new_size = 30
  163.             self.calibrate(new_size)
  164.         # Decrease cell size
  165.         elif key == K_a:
  166.             new_size = self.cell_size - 2
  167.             if new_size < 4:
  168.                 new_size = 4
  169.             self.calibrate(new_size)
  170.         # Toggle whether the model is paused
  171.         elif key == K_SPACE:
  172.             self.model.paused = not self.model.paused
  173.         # load selected pattern 0
  174.         elif key == K_1:
  175.             self.clear_view(0)
  176.         # load selected pattern 1
  177.         elif key == K_2:
  178.             self.clear_view(1)
  179.  
  180.     def parse_key_up(self, key):
  181.         """Key up events to help govern panning."""
  182.         # Stop panning the viewport on arrow key up
  183.         if key == K_UP:
  184.             self.keyup = False
  185.         elif key == K_DOWN:
  186.             self.keydown = False
  187.         elif key == K_LEFT:
  188.             self.keyleft = False
  189.         elif key == K_RIGHT:
  190.             self.keyright = False
  191.  
  192.  
  193. class Life_Controller:
  194.     """Controller that manages a model and view."""
  195.     def __init__(self):
  196.         """Initialize pygame, the model, and the view."""
  197.         # Initialize pygame
  198.         pygame.init()
  199.         # Set the window caption
  200.         pygame.display.set_caption("Conway's Game of Life")
  201.         # Do not show the mouse cursor
  202.         pygame.mouse.set_visible(0)
  203.         # Acquire the main surface to render to
  204.         self.screen = pygame.display.set_mode((1920, 1080), FULLSCREEN)
  205.         # Instantiate the model
  206.         self.model = Life_Model()
  207.         # Instantiate the view
  208.         self.view = Life_View(self.screen, self.model)
  209.  
  210.     def run_controller(self):
  211.         """The main loop for the controller.  It coordinates input, processing,
  212.        and output with the model and view."""
  213.         while True:
  214.             # Process Pygame events
  215.             self.process()
  216.             # Invoke the model's process
  217.             self.model.process()
  218.             # Invoke the view's process
  219.             self.view.process()
  220.             # Flip the new image to the graphical display
  221.             pygame.display.flip()
  222.  
  223.     def process(self):
  224.         """Handle the window close event and keyboard commands."""
  225.         for event in pygame.event.get():
  226.             # Window close button clicked
  227.             if event.type == QUIT:
  228.                 exit()
  229.             # Parse keypresses
  230.             elif event.type == KEYDOWN:
  231.                 # Quit
  232.                 if event.key == K_ESCAPE:
  233.                     exit()
  234.                 else:
  235.                     # Pass key downs to view
  236.                     self.view.parse_key_down(event.key)
  237.             # Pass key ups to view
  238.             elif event.type == KEYUP:
  239.                 self.view.parse_key_up(event.key)
  240.  
  241.  
  242. # Run the controller if we are being executed as a program
  243. if __name__ == '__main__':
  244.     # Instantiate a controller
  245.     controller = Life_Controller()
  246.     # Invoke entry into the controller
  247.     controller.run_controller()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement