Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- """
- Conway's Game of Life v2.1
- Copyright © 2020 headkase, CC0
- To the extent possible under law, the person who associated CC0
- with this work has waived all copyright and related or
- neighboring rights to this work.
- Keyboard commands:
- Escape : Quit
- Space : Toggle pause
- Arrow Keys : Pan view
- 1 : Select R-pentomino
- 2 : Select F-heptomino
- Q : Increase cell size
- A : Decrease cell size
- """
- import pygame
- from pygame import FULLSCREEN, QUIT, KEYDOWN, KEYUP, K_ESCAPE, K_SPACE
- from pygame import K_UP, K_DOWN, K_LEFT, K_RIGHT, K_1, K_2, K_q, K_a
- from sys import exit
- class Life_Model:
- """Conway's Game of Life model."""
- def __init__(self):
- """Initialize the parameters and domain of the model."""
- # A list of pattern sets
- self.patternbank = []
- # This pattern is a R-pentomino
- # https://www.conwaylife.com/wiki/R-pentomino
- self.patternbank.append({(0, 0), (0, -1), (1, -1), (-1, 0), (0, 1)})
- # This pattern is a F-heptomino
- # https://www.conwaylife.com/wiki/F-heptomino
- self.patternbank.append({(-1, -1), (0, -1), (0, 0), (0, 1), (0, 2),
- (1, 2), (2, 2)})
- # Coordinates around a cell, given as a delta table
- self.neighbours = ((-1, -1), (-1, 0), (-1, 1), (0, -1),
- (0, 1), (1, -1), (1, 0), (1, 1))
- def process(self):
- """Iterate the Life model."""
- # If not paused then process the model
- if not self.paused:
- # For every cell in the life set check the cell and its
- # neighbours for the population conditions
- new_life = set()
- for cell in self.life:
- # Check this cell
- if self.population(cell) == 3 or self.population(cell) == 2:
- new_life.add(cell)
- # Check all the neighbours of this cell
- for new_cell in self.neighbours:
- test_cell = (cell[0] + new_cell[0], cell[1] + new_cell[1])
- if self.population(test_cell) == 3:
- new_life.add(test_cell)
- # Replace the old set with the new
- self.life = new_life
- def population(self, cell):
- """Return a count of the neighbours around a cell."""
- count = 0
- # For the delta table entries generate tuples of (x, y) and
- # then test them for membership in the life set
- for position in self.neighbours:
- position_x = cell[0] + position[0]
- position_y = cell[1] + position[1]
- if (position_x, position_y) in self.life:
- count += 1
- return count
- def reset_model(self, pattern):
- """Reset the model."""
- self.paused = True
- self.life = self.patternbank[pattern].copy()
- class Life_View:
- """Conway's Game of Life View."""
- def __init__(self, screen, model):
- """Initialize the view."""
- self.background_colour = pygame.Color('cornflowerblue')
- self.cell_colour = pygame.Color('white')
- self.screen = screen
- self.display_rect = self.screen.get_rect()
- self.model = model
- # Initialize the model pattern and viewport
- self.clear_view(0)
- def clear_view(self, pattern):
- """Bring the view to a default state."""
- self.model.reset_model(pattern)
- self.viewport_centre_x, self.viewport_centre_y = 0, 0
- self.calibrate()
- def calibrate(self, cellsize=4):
- """Recalibrate the parameters for display."""
- self.cell_size = cellsize
- world_x_units = int(self.display_rect.width / self.cell_size)
- world_y_units = int(self.display_rect.height / self.cell_size)
- self.screen_origin_x = world_x_units / 2
- self.screen_origin_y = world_y_units / 2
- self.keyup = self.keydown = self.keyleft = self.keyright = False
- def process(self):
- """Iterate the view logic."""
- self.manage_state()
- self.render()
- def manage_state(self):
- """Manage the overall state of the view."""
- # If arrow key states pan the viewport
- if self.keyup:
- self.viewport_centre_y -= 1
- if self.keydown:
- self.viewport_centre_y += 1
- if self.keyleft:
- self.viewport_centre_x -= 1
- if self.keyright:
- self.viewport_centre_x += 1
- def render(self):
- """Render the graphical output."""
- self.screen.fill(self.background_colour)
- for cell in self.model.life:
- # Unpack x and y cell coordinates
- x, y = cell[0], cell[1]
- # Given origin, viewport, x & y cell coordinates calculate
- # graphical x position and y position with a cell size
- xpos = int(((self.screen_origin_x + (self.viewport_centre_x + x))
- * self.cell_size))
- ypos = int(((self.screen_origin_y + (self.viewport_centre_y + y))
- * self.cell_size))
- # Check to see if the cell is on screen and if so draw it
- bounded = (xpos >= 0) and (xpos <= self.display_rect.width) and \
- (ypos >= 0) and (ypos <= self.display_rect.height)
- if bounded:
- self.screen.fill(self.cell_colour, pygame.Rect(
- xpos, ypos,
- self.cell_size - 1, self.cell_size - 1))
- def parse_key_down(self, key):
- """Parse the key commands to control both the model and view."""
- # Start panning viewport on arrow key down
- if key == K_UP:
- self.keyup = True
- elif key == K_DOWN:
- self.keydown = True
- elif key == K_LEFT:
- self.keyleft = True
- elif key == K_RIGHT:
- self.keyright = True
- # Increase cell size
- elif key == K_q:
- new_size = self.cell_size + 2
- if new_size > 30:
- new_size = 30
- self.calibrate(new_size)
- # Decrease cell size
- elif key == K_a:
- new_size = self.cell_size - 2
- if new_size < 4:
- new_size = 4
- self.calibrate(new_size)
- # Toggle whether the model is paused
- elif key == K_SPACE:
- self.model.paused = not self.model.paused
- # load selected pattern 0
- elif key == K_1:
- self.clear_view(0)
- # load selected pattern 1
- elif key == K_2:
- self.clear_view(1)
- def parse_key_up(self, key):
- """Key up events to help govern panning."""
- # Stop panning the viewport on arrow key up
- if key == K_UP:
- self.keyup = False
- elif key == K_DOWN:
- self.keydown = False
- elif key == K_LEFT:
- self.keyleft = False
- elif key == K_RIGHT:
- self.keyright = False
- class Life_Controller:
- """Controller that manages a model and view."""
- def __init__(self):
- """Initialize pygame, the model, and the view."""
- # Initialize pygame
- pygame.init()
- # Set the window caption
- pygame.display.set_caption("Conway's Game of Life")
- # Do not show the mouse cursor
- pygame.mouse.set_visible(0)
- # Acquire the main surface to render to
- self.screen = pygame.display.set_mode((1920, 1080), FULLSCREEN)
- # Instantiate the model
- self.model = Life_Model()
- # Instantiate the view
- self.view = Life_View(self.screen, self.model)
- def run_controller(self):
- """The main loop for the controller. It coordinates input, processing,
- and output with the model and view."""
- while True:
- # Process Pygame events
- self.process()
- # Invoke the model's process
- self.model.process()
- # Invoke the view's process
- self.view.process()
- # Flip the new image to the graphical display
- pygame.display.flip()
- def process(self):
- """Handle the window close event and keyboard commands."""
- for event in pygame.event.get():
- # Window close button clicked
- if event.type == QUIT:
- exit()
- # Parse keypresses
- elif event.type == KEYDOWN:
- # Quit
- if event.key == K_ESCAPE:
- exit()
- else:
- # Pass key downs to view
- self.view.parse_key_down(event.key)
- # Pass key ups to view
- elif event.type == KEYUP:
- self.view.parse_key_up(event.key)
- # Run the controller if we are being executed as a program
- if __name__ == '__main__':
- # Instantiate a controller
- controller = Life_Controller()
- # Invoke entry into the controller
- controller.run_controller()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement