Skip to main content

As I explained at the beginning of the previous blog, I am now creating a set of blogs about my journey on creating a Pygame that builds the Game Of Life. On the previous blog, I set up Poetry as my virtual environment manager, installed Pygame and created the file structure of the project.

My idea, for now, is that we end up with a game that shows a grid of white squares that can be clicked on to set them up as alive (black) and it has at least a button to play or stop the simulation of the game.

So, in this blog, I will try to simply understand the main Pygame logic and how we will create the colony and its cells for our Game of Life game.

Main Pygame logic

Actually, Pygame makes a very good job at showing the very main idea on how it works. Let’s take the example that I used in the previous blog to make sense of it. The script starts like this

Set up Block

# Example file showing a circle moving on screen
import pygame

# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0

player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)

The first 5 lines initialize the environment.

pygame.init() initializes all loaded Pygame modules

screen = pygame.display.set_mode((1280, 720)) creates a screen object of size 1280×720 pixels. Basically, the window where our game will be rendered.

clock = pygame.time.Clock() creates a clock object that will manage the framerate of the game and lets us check how much time passes between each frame.

running = True will control if the game is running or not.

dt = 0 will be used for controlling the physics of the moving ball we will see. More about this later on.

player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2) creates a 2D vector with the (x,y) position of the circle (player) on the middle of the screen.

Game Loop

Once the set up is done, we can start with the game loop. This loop will be run at every frame and handle all the game logic that we create. Check the comments explaining each block. It is actually pretty simple.

while running:
    # poll for events
    # pygame.QUIT event means the user clicked X to close your window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # fill the screen with a color to wipe away anything from last frame
    screen.fill("purple")
    
    # the ball that the player moves
    pygame.draw.circle(screen, "red", player_pos, 40)

    # if a key is pressed, move the ball in the correct direction according to the
    # x = x_0 
    keys = pygame.key.get_pressed()
    if keys[pygame.K_w]:
        player_pos.y -= 300 * dt
    if keys[pygame.K_s]:
        player_pos.y += 300 * dt
    if keys[pygame.K_a]:
        player_pos.x -= 300 * dt
    if keys[pygame.K_d]:
        player_pos.x += 300 * dt

    # flip() the display to put your work on screen
    pygame.display.flip()

    # limits FPS to 60
    # dt is delta time in seconds since last frame, used for framerate-
    # independent physics.
    dt = clock.tick(60) / 1000

pygame.quit()

Running this will produce a purple screen with a red ball that we can move around using the WASD keys. Notice that, at every frame (60 per second), we check which keys are pressed and the required actions.

Game Of Life Loop

In our case, we will need the following in the game loop:

  • Check for mouse clicks. If the primary button is pressed on a cell, it should change it from alive to dead and viceversa.
  • Check for activation or stopping of the simulation.
  • If the simulation is active, and it is a simulation frame (meaning, we will not update the simulation at every frame) we should perform a GoL step.

If we could get that to work, I would be satisfied with our work

Creating a Cell

For this, we will create a new Python class, the Cell. This Cell object will have the information required to draw it

  • Dimensions
  • Color
class Cell(pygame.rect.Rect):
    def __init__(self, width, left, top) -> None:
        self.left = left
        self.top = top
        self.width = width
        self.color = 'white'

When drawing the Colony, I will want to put each cell together in a rectangular grid. In order to have a separation between the cells, I will actually leave a 5% margin to the initially given space. So, the method for drawing will be

def draw(self, surf):
    left = self.left + round(0.05 * self.width)
    top = self.top + round(0.05 * self.width)
    width = round(0.9 * self.width)

    pygame.draw.rect(surf, self.color, (left, top, width, width))

Finally, we want the cell to be change states (when clicking on it) and the be set giving a dead (0) or alive (1) input. So, we add two more methods.

def change(self):
    self.color = 'white' if self.color == 'black' else 'black'

def set_state(self, state):
    self.color = 'white' if state == 0 else 'black'

Nice, we have a Cell with all the functionalities that we want, but a Cell in the GoL does nothing by itself.

Let’s test them out creating our own game loop.

import pygame
import random

from objects.colony import Cell


# pygame setup
pygame.init()
screen = pygame.display.set_mode((800, 900))
clock = pygame.time.Clock()
running = True

colony = []

for i in range(20):
    colony.append(Cell(
        int(50 * random.random()),
        int(700 * random.random()),
        int(800 * random.random()),
    ))

while running:

    # fill the screen with a color to wipe away anything from last frame
    screen.fill("gray")

    # Draw the cells on screen
    for cell in colony:
        cell.draw(screen)

    # Get events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        # If press left button, change random cell
        elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
            colony[random.randint(0, len(colony) - 1)].change()

        # If press right button, change all cells
        elif event.type == pygame.MOUSEBUTTONUP and event.button == 3:
            for cell in colony:
                cell.change()


    # flip() the display to put your work on screen
    pygame.display.flip()

    clock.tick(60)

pygame.quit()

The important lines are the following:

  • Line 15: Create an array of 20 cells at random positions and of random size.
  • Line 37: Check if left mouse button is pressed. If so, change a random cell state.
  • Line 41: Check if right mouse button is clicked. If so, change all cell states.

Below we can see the end result when executing and clicking.

Obviously, this does not resemble, at all, the Game of Life. In the next blog, we will continue working on this and we will create a rectangular colony of multiple cells.

See the rest of the blogs on my experience creating the Game of Life game in Python:

Auteur