# Exploring pygame 4 - Game of Life

Published on

A while ago, I made a post on how to organize a coding dojo that was published right after the Python DF community anniversary dojo where I prepared a challenge to create the game of life from John Horton Conway.

To let the challenge more dynamic, I prepared a graphic simulator using pygame so everyone could see the code running visually. The simulator uses all the concepts that we saw on the last posts of this series.

## The Game

Game of Life is a game without players. It represents the dynamic of interaction between a group of living beings at a bidimensional plane starting from an initial state and following 4 simple interaction rules.

This plane looks like a table divided into cells, and each cell can be in one of these two states, dead or alive. Besides that, the plane has an infinite dimension but, on our implementation, we going to have a limited space to simplify things.

The starting state we call seed and can you can define it manually or randomly.

The interaction cycle we call generation, and it is the result of the 4 interaction rules applied.

1. Every cell with less than two live neighbors dies by underpopulation; 2. Every live cell with two or three live neighbors survives to the next generation; 3. Every live cell with more than three neighbors dies of overpopulation; 4. Every dead cell with exactly three neighbors turns alive by reproduction. There’s no interaction between generations, so if a new cell is born when you go to compute the next cell, it won’t count since it wasn’t alive on this generation.

## Implementation

The simulator was made to test the game logic, and it was placed at the `simulator.py` file while the `game.py` file has the game of life logic.

The dojo challenge was the following, write a function `game_of_life` that receives a seed as a 50x50 matrix and return a matrix with the next generation computed:

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 `````` ``````def cell_check(section): ''' Run the game of life rules at a 3x3 section to compute the middle cell ''' # neighbor counter neighbors = 0 # reference to the center of the section center = section # sums all elements of the section for row in section: for cell in row: neighbors += cell # remove the center cell value to get only the # neighbor count neighbors -= center # applying the game of life rules # see that rule number two isn't effectively # applied since it doesn't change the state of the center cell if neighbors <= 1: # less than two neighbors, the cell dies of underpopulation center = 0 elif neighbors == 3: # exactly three neighbors, the cell is born by reproduction center = 1 elif neighbors >= 4: # more than three neighbors, the cell dies of overpopulation center = 0 # returns the center cell value return center def get_section(matrix, row, col): ''' Extracts a 3x3 section from the plane given the center cell coordinate ''' # builds an empty 3x3 section to make the plane section copy section = [[0 for _ in range(3)] for _ in range(3)] # runs through the plane section copying its values for sec_r, r in enumerate(range(row-1, row+2)): for sec_c, c in enumerate(range(col-1, col+2)): if r >= 0 and c >= 0 and r < 50 and c < 50: section[sec_r][sec_c] = matrix[r][c] # returns the copied 3x3 section return section def game_of_life(seed): ''' Receives the 50x50 plane seed, runs the game of life rules and returns the next generation ''' # creates an empty plane to store the new generation # we don't mutate the seed to prevent side effects next_gen = [[0 for _ in range(50)] for _ in range(50)] # walks through the plane computing the next generation # of each cell for r, row in enumerate(seed): for c, col in enumerate(row): next_gen[r][c] = cell_check(get_section(seed, r, c)) # returns the next generation return next_gen``````

As you can see the `game.py` code computes only the next generation of the seed which is perfect to use at the `simulator.py` because we can compute the next generation to display on the screen and then feed the computed generation as a seed to the next one and so forth:

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 `````` ``````import random import time import pygame from game import game_of_life # empty plane SEED = [[0 for _ in range(50)] for _ in range(50)] # Glider SEED = 1 SEED = 1 SEED = SEED = SEED = 1 # Glider gun # SEED = 1 # SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = 1 # SEED = SEED = 1 # SEED = SEED = 1 # Rich's p16 # SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = SEED = 1 # SEED = SEED = SEED = SEED = 1 # SEED = SEED = 1 # Random # SEED = [[random.choice([0, 1]) for _ in range(50)] for _ in range(50)] pygame.init() screen = pygame.display.set_mode((550, 550)) def draw_matrix(matrix): ''' Function to draw the plane given the matrix ''' # fills the screen with black screen.fill([0, 0, 0]) # walks through the plane drawing its cells for r, row in enumerate(matrix): for c, cell in enumerate(row): if cell: # if the cell is alive draw it as a white square pygame.draw.rect( screen, (255, 255, 255), (11*c, 11*r, 10, 10) ) # sets the seed as one of the preset values at the top seed = SEED # draws the initial state of the seed draw_matrix(seed) pygame.display.flip() # waits one second so we can see the initial state before # starting the interaction loop time.sleep(1) while True: # INPUT PROCESSING event = pygame.event.poll() if event.type == pygame.QUIT: # quits the program if clicked to close the window break elif (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): # quits the program on pressing ESC break # GAME UPDATE # runs the game of life to get the next generation seed = game_of_life(seed) # DRAWING # draws the new generation at the screen draw_matrix(seed) pygame.display.flip() # waits a brief moment until going to the next generation time.sleep(0.05)``````

The first drawing is separate from the game loop so the user can see the starting state before running the interactions on a faster pace. Entering inside the game loop we have the input processing step where quit conditions are checked so, moving to the game update step, the next generation is computed, and then, on the drawing step, the new generation is displayed at the screen.

## Examples

To demonstrate the simulator working, I choose the glider there is the most famous pattern inside the game of life. The hacker community absorbed it as their emblem, and it is the smaller cyclic pattern that moves on the plane:

Note there are some commented patterns at the simulator code, you can uncomment them to see different patterns in action.

## Conclusion

The Game of Life is a great topic to learn programming concepts, and I recommend you to watch the interview with its creator by the Numberphile youtube channel.

The code of this chapter can be found at the repo exploring-pygame.