Sunday, July 19, 2026

coding Game of Life

Code is at the bottom, with extensive comments

Notes on how to code Game of Life (GOL) including an animation, using numpy and matplotlib.

I learn through doing inspirational projects and Conway’s Game of Life cellular automata is certainly that. More about that in a later blog.

I’m also learning NumPy, as a step in learning the maths behind neural nets, so initially I’m following Nicolas P. Rougier’s online book “From Python to Numpy” (2017), Ch 4.2, where he has both a python and numpy version of Game of Life. The numpy version uses vectorization, rather than for loops.

The GOL rules are:

  • Survival: if an existing alive cell has 2 or 3 neighbours then it survives.
  • Birth: if an empty cell has 3 neighbours then it becomes alive.
  • All other neighbourhood counts lead to dead cells

Each cell has 8 neighbouring cells. By slicing the universe grid for each neighbouring cell and adding the values we obtain a neighbourhood count (new_u) for each cell. Then the rules can be implemented using boolean masks which reference both the new_u neighbourhood count grids and the universe grids.

Rougier doesn’t do the animation in his book. He stops after showing how to compute neighbours and does one iteration. So I found a version on the web by finxter using python for loops and FuncAnimation

The code works but the finxter explanation is cursory.

It is essential to make a new copy of the universe. Not doing this held my up for a while. Part of the problem was that I didn’t fully understand matplotlib’s FuncAnimation so I researched that for a bit.

Here is a good explanation:
The FuncAnimation class allows us to create an animation by passing a function that iteratively modifies the data of a plot. This is achieved by using the setter methods on various Artist (examples: Line2D, PathCollection, etc.). A usual FuncAnimation object takes a Figure that we want to animate and a function func that modifies the data plotted on the figure. It uses the frames parameter to determine the length of the animation. The interval parameter is used to determine time in milliseconds between drawing of two frames.
- source

Which setter method, plotting method and Artist do I need?

  • data set method: set_data()
  • plotting method: Axes.imshow()
  • Artist: image.AxesImage
FuncAnimation looks like this:

class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)
  • makes an animation by repeatedly calling a function func
  • You must store the created Animation in a variable (called ani in my code) that lives as long as the animation should run. Otherwise, the Animation object will be garbage-collected and the animation stops.
  • func callable: The function to call at each frame. The first argument will be the next value in frames.
  • frames: Source of data to pass func and each frame of the animation
  • fargs: Additional arguments to pass to each call to func.
  • interval: delay between frames in milliseconds
  • repeat: bool, default True

Here is how to generate the animation gif which I used for this blog:
ani.save("GOL_random.gif")

Then I worked out how to count the population of each frame and the number of generations of the game and added that to the xlabel inside the animation function

Finally, I noticed that one Conway site had an extensive pattern library with hundreds of interesting seeds which can be run WITH DYNAMICALLY CHANGING COLOURS. I looked around at how to achieve this but so far haven’t worked it out.

 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
"""
Game of Life
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

def create_universe(N=50, p=0.5):
    return np.random.choice([0, 1], size=(N, N), p=[1-p, p])
# generates random 1 and 0s with p probability in a N*N 2D array
N = 50 # array size

universe = create_universe(N=N, p=0.5)
# universe determines image

generations = 0 # measure the number of generations

# animation function
def animate(frame, universe, img):
    global generations
    new_u = np.zeros((N, N), dtype = int)
    # new_u initialises as same size as universe, array of all 0s 
    # then calculates neighbour count
    
    # set edges of universe to zero
    # to avoid edge calculation complications
    universe[0,:] = universe[-1,:] = universe[:,0] = universe[:,-1] = 0
    # universe is 2D array with 1s and 0s
    
    # count the neighbours of the universe cells (by grid displacement)
    # new_u is neighbour counts
    new_u[1:-1,1:-1] = (universe[ :-2, :-2] + universe[ :-2,1:-1] + universe[ :-2,2:] +
                     universe[1:-1, :-2]                + universe[1:-1,2:] +
                     universe[2:  , :-2] + universe[2:  ,1:-1] + universe[2:  ,2:])

    # boolean masks to implement GOL rules
    birth = (new_u==3)[1:-1,1:-1] & (universe[1:-1,1:-1]==0) 
    # 3 neighbours for an empty cell -> birth
    survive = ((new_u==2) | (new_u==3))[1:-1,1:-1] & (universe[1:-1,1:-1]==1)
    # 2 or 3 neighbours in occupied cell -> survive
    
    # reset universe to zeros, make a new copy, then update 1s & 0s
    universe[:] = np.zeros((N, N), dtype = int) # new copy essential
    universe[1:-1,1:-1][birth | survive] = 1
    # count the population
    population = np.count_nonzero(universe)
    # update image with the data set method
    img.set_data(universe)
    # display generations and population
    ax.set_xlabel('generations = {}, population = {}'.format(generations, population))
    generations += 1 # update generations

fig = plt.figure(figsize=(5, 5)) # figure size
ax = plt.axes()
ax.set_yticklabels([]) # turn off axis ticks (but keep x axis label above)
ax.set_xticklabels([])
img = ax.imshow(universe, interpolation='nearest')
# imshow is the plotting method for AxesImage artist
ani = FuncAnimation(fig, animate, fargs=(universe, img,),
                    frames=200, interval=200)
# ani lives while animation runs, otherwise garbage collected
# fargs: arguments to pass to each call of the function 
# interval in milliseconds
ani.save("GOL_random.gif") # save the animations as a gif
plt.show()