In the previous blog we created the Cell object that can basically be able to be alive (drawn as black) or dead (drawn as white). However, in order to build the Game of Life we will actually need a group of cells that can interact with each other. So, in this blog we will se how to construct a Colony for the Game of Life.
We will need another object, basically, an aggregation and manager of Cells. It needs to:
- Create the Cell objects with the adequate dimensions
- Draw the Cells on screen
- Keep track of the states of the Cells
- Be able to perform GoL steps (we will focus on that on the next blog)
After this blog, we should end up with a screen that contains a colony of cells that we can click on to change their states.
Creating a Colony
For now, when creating the colony, we will pass the number of rows and columns of cells that we will have and the size of each of them. We will also have three arrays
states
will keep track of dead and alive cells using 0 and 1 codescells
will keep the actual cell objects to facilitate us iterating over themindices
will be used in the GoL step (see building the GoL in Python blog)
import pygame
import numpy as np
from .cell import Cell
class Colony():
def __init__(self, m, n, cell_size) -> None:
self.rows, self.cols = m, n
self.states = np.zeros((m, n), dtype=int)
self.cell_size = cell_size
self.cells = np.empty((m, n), dtype=object)
self.area = pygame.rect.Rect(0, 0, m * cell_size, n * cell_size)
self.indices = np.arange(m*n).reshape(m,n)
for i in range(m):
for j in range(n):
self.cells[i, j] = Cell(cell_size, j * cell_size, i * cell_size)
Notice that we are using Numpy. To add the dependency to our project using Poetry, simply run the following on the CLI
poetry add numpy
In order to draw the cells, we will iterate over them and call their draw
method, making sure that they are drawn in the correct state (we call the set_state
method before)
def draw(self, screen):
for cell, state in zip(self.cells.flatten(), self.states.flatten()):
cell.set_state(state)
cell.draw(screen)
Now, to keep track of which Cells are changed when we left click them we can get the coordinates of the cursor when the left click is pressed and find out the (i,j)
indices of the cell they correspond. Then, we just have to access that cell and execute the Cell.change()
function.
def change_cell(self, x, y):
if 0 <= x < self.area.right and 0 <= y < self.area.bottom:
i = y // self.cell_size
j = x // self.cell_size
print(f'changing cell ({i}, {j}) at position ({x}, {y})')
self.cells[i, j].change()
self.states[i, j] = 1 if self.states[i, j] == 0 else 0
else:
print('out of bounds')
Let’s break it down. We will get the (x,y)
position of the cursor on the screen. If the cursor is in the colony area, then find the (i,j)
indices by simple integer division of the position of the cursor and the cell size. Finally, once we have the (i,j)
cell, change its state. The prints simply show what is happening on the terminal, but they do not actually make a difference for the game itself.
Main Loop
Now that we have both Cells and a way to manage them, the Colony, we can create the main Pygame loop.
First of all, the general game setup. We will need to create some variables to configure Pygame.
import pygame
from objects.colony import Colony
# pygame setup
pygame.init()
screen = pygame.display.set_mode((800, 900))
clock = pygame.time.Clock()
running = True
dt = 0
colony = Colony(40, 40, 20)
As you can see, for now, I have hardcoded the sizes of the screen (800×900 pixels) and the Colony (40×40 cells of 20×20 pixels each). Then, we go create the main loop.
while running:
# fill the screen with a color to wipe away anything from last frame
screen.fill("gray")
colony.draw(screen)
for event in pygame.event.get():
# Click X button to close screen
if event.type == pygame.QUIT:
running = False
# If left click, get cursor position and send it to the change_cell
# method of the colony
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
colony.change_cell(*pygame.mouse.get_pos())
# flip() the display to put your work on screen
pygame.display.flip()
# limits FPS to 60
clock.tick(60)
As you can see, the logic is pretty easy. For each frame, we will look at the player’s events. If the player left clicks, the colony will try to find the corresponding cell to change its state.
End result
Putting it all together, this is what we get.
Cool enough! However, it is still only a screen with some square that you can click on. Next up, we will adapt what we did in the Challenge: The Game of Life in Python so that we finally have a fully working verswion of The Game of Life.
As you can see, I have purposely left some empty space at the bottom. The idea is at some point to add a couple buttons to stop and resume the simulation and maybe even change the simulation speed. But this is a problem for future Eloi.
See the rest of the blogs on my experience creating the Game of Life game in Python: