Skip to main content

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 codes
  • cells will keep the actual cell objects to facilitate us iterating over them
  • indices 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:

Auteur