John Conways "Game of Life" in Python

A so-called "spaceship pattern" in Game of Life

The Game of Life

The "Game of Life" developed by the English mathematician John Horton Conway is a cellular automaton. Cellular automata are discrete models that consist of a regular grid in which each cell has a defined state. The simulation proceeds in discrete time steps. The new state of a cell only depends on the state of the neighboring cells in the previous time step. The simulation can create complex patterns using simple rules. A detailed description of the "Game of Life" can be found in a separate article on this website.

John Conway found a set of simple rules with which it is possible to create structures within the simulation that can replicate, move, or interact with each other. It was later proven that the game of life with this set of rules is "Turing Complete". With a Turing complete machine, you can theoretically perform any calculation. So you could say that a machine or software that implements the "Game of Life" is in itself a kind of computer, albeit one that is extraordinarily complicated to program.

Rules

The simulation starts in the first time step with a specified initial state. Each cell in the game has one of two states: "Alive" or "Dead". In the Python example, these states are expressed by the numbers 0 and 1. For the next time step, the states of the cells are calculated according to the following rules:

  • A living cell dies if it has fewer than two living neighboring cells.
  • A living cell with two or three living neighbors lives on.
  • A living cell with more than three living neighboring cells dies in the next time step.
  • A dead cell is revived if it has exactly three living neighboring cells.

Python source code

The Python source code for Game of Life can be downloaded from GitHub.

import pygame
import numpy as np

col_about_to_die = (200, 200, 225)
col_alive = (255, 255, 215)
col_background = (10, 10, 40)
col_grid = (30, 30, 60)

def update(surface, cur, sz):
    nxt = np.zeros((cur.shape[0], cur.shape[1]))

    for r, c in np.ndindex(cur.shape):
        num_alive = np.sum(cur[r-1:r+2, c-1:c+2]) - cur[r, c]

        if cur[r, c] == 1 and num_alive < 2 or num_alive > 3:
            col = col_about_to_die
        elif (cur[r, c] == 1 and 2 <= num_alive <= 3) or (cur[r, c] == 0 and num_alive == 3):
            nxt[r, c] = 1
            col = col_alive

        col = col if cur[r, c] == 1 else col_background
        pygame.draw.rect(surface, col, (c*sz, r*sz, sz-1, sz-1))

    return nxt

def init(dimx, dimy):
    cells = np.zeros((dimy, dimx))
    pattern = np.array([[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
                        [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]);
    pos = (3,3)
    cells[pos[0]:pos[0]+pattern.shape[0], pos[1]:pos[1]+pattern.shape[1]] = pattern
    return cells

def main(dimx, dimy, cellsize):
    pygame.init()
    surface = pygame.display.set_mode((dimx * cellsize, dimy * cellsize))
    pygame.display.set_caption("John Conway's Game of Life")

    cells = init(dimx, dimy)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return

        surface.fill(col_grid)
        cells = update(surface, cells, cellsize)
        pygame.display.update()

if __name__ == "__main__":
    main(120, 90, 8)

Explanations

Die Gosper Gleiterkanone

This program uses the "pygame" and "numpy" libraries. Those libraries must be installed in the Python development environment using PIP.


import pygame
import numpy as np

The variables are initialized in the init function. The pattern selected as the initial state of the simulation is the "Gosper glider gun" . This simple starting pattern creates smaller patterns, so-called gliders, which move in a straight line away from the "glider gun". The state of the cells is saved in a two-dimensional field called cells .

The pattern of the glider cannon is saved in the variable named pattern and copied to the top left corner of the field continaing the initial state of the simulation. Finally the cells field is returned.


def init(dimx, dimy):
    cells = np.zeros((dimy, dimx))
    pattern = np.array([[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0],
                        [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
                        [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]);
    pos = (3,3)
    cells[pos[0]:pos[0]+pattern.shape[0], pos[1]:pos[1]+pattern.shape[1]] = pattern
    return cells

The update function is the core of the simulation. This function calculates the status of the cells in the next time step and applies the rules of the game of life to all cells. At the beginning of the function, a new two-dimensional field called nxt is created and initialized with zeros. The new state of the cells is saved in this field later on so that it can be returned at the end of the function.

The function then iterates over all the cells in a loop. The number of living neighboring cells is determined for each cell. Then the rules are applied:

  • A living cell dies if it has fewer than two living neighboring cells.
  • A living cell with two or three living neighbors lives on.
  • A living cell with more than three living neighboring cells dies in the next time step.
  • A dead cell is revived if it has exactly three living neighboring cells.

Since the field nxt is already initialized with zeros, dead cells are not written into it. Finally, each cell is assigned a color that corresponds to its status. After the calculation is complete, the new state of the simulation is returned. The time step calculation is complete.


def update(surface, cur, sz):
    nxt = np.zeros((cur.shape[0], cur.shape[1]))

    for r, c in np.ndindex(cur.shape):
        num_alive = np.sum(cur[r-1:r+2, c-1:c+2]) - cur[r, c]

        if cur[r, c] == 1 and num_alive < 2 or num_alive > 3:
            col = col_about_to_die
        elif (cur[r, c] == 1 and 2 <= num_alive <= 3) or (cur[r, c] == 0 and num_alive == 3):
            nxt[r, c] = 1
            col = col_alive

        col = col if cur[r, c] == 1 else col_background
        pygame.draw.rect(surface, col, (c*sz, r*sz, sz-1, sz-1))

    return nxt

The main loop of the program is short. It initializes the program and contains an event loop with which pygame checks whether the program has been ended by the user. As long as the program has not ended, the update function is called and the simulation continues.


def main(dimx, dimy, cellsize):
    pygame.init()
    surface = pygame.display.set_mode((dimx * cellsize, dimy * cellsize))
    pygame.display.set_caption("John Conway's Game of Life")

    cells = init(dimx, dimy)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return

        surface.fill(col_grid)
        cells = update(surface, cells, cellsize)
        pygame.display.update()

Weblinks

  • A shorter version of the implementation of Game of Life in Python is described by Jack Diederich in the video Stop Writing Classes from the Youtube channel Next Day Video.