Conway’s Game of Life is a zero-player game introduced by the mathematician John Horton Conway in 1970. Well, It has it’s own wiki and all.
The rules are simple. You start with a grid of cells with initial state of either living or dead. Cells interact with neighbors to define the next generation of cells.
- Any live cell with fewer than two live neighbors dies, as if by under-population.
- Any live cell with two or three live neighbors lives on to the next generation.
- Any live cell with more than three live neighbors dies, as if by overpopulation.
- Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction
Implementation Link to heading
It’s using the following packages
- numpy and scipy for matrix operations
- matplotlib for plotting and animation

Animation Link to heading
matplotlib is new to me, so it took me some time to understand the basic building blocks. but what i want to highlight below is FuncAnimation
def generations(self):
for g in range (self.G):
yield self.step()
def run(self):
fig, ax = plt.subplots()
mat = ax.matshow(self.grid)
ani = animation.FuncAnimation(fig, mat.set_data, self.generations, interval=500, repeat=False)
plt.show()
docs says
fig: Figure The figure object that is used to get draw, resize, and any other needed events.
func: callable The function to call at each frame. The first argument will be the next value in frames. Any additional positional arguments can be > supplied via the fargs parameter.
frames: iterable, int, generator function, or None, optional If an iterable, then simply use the values provided. If the iterable has a length, it will override the save_count kwarg.
So, the second parameter is callable, in this case i am passing mat.set_data so, it will be called by matplolib to update frames.
but the most important part is that third parameter is iterable or int or generator function. and this parameter will be passed to the callable in parameter 2.
def func(frame, *fargs)
which effectively is doing the following considering the generator will run G generations before stopping.
def mat.set_data(this.grid)
gotcha Link to heading
well, the game is straightforward to write but there was something that bugged me little. Mainly, calculating the number of neighbors living cells.
I initially implemented it by manually counting ucelsls sing (i,j) index and it worked but it was ugly because of the boundary checks. Then I found convolve2d from scipy. It’s neat because using the right kernel, i can count the neighbors for all cells with one line. more details about convolution wiki
W = np.array([[1, 1, 1],
[1, 0, 1],
[1, 1, 1]])
self.neighbour = signal.convolve2d(self.grid, W, 'same')
putting it all together Link to heading
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import argparse
class Conway():
def __init__(self, N=10, G=1, shape='random'):
self.N = N
self.G = G
self.grid = None
self.neighbour = None
if shape == 'random':
self.grid = np.random.choice(a=[False, True], size=(N, N))
else:
self.grid = np.zeros((N, N), dtype='bool')
def calc(self):
W = np.array([[1, 1, 1],
[1, 0, 1],
[1, 1, 1]])
self.neighbour = signal.convolve2d(self.grid, W, 'same')
def step(self):
self.calc()
for i in range(self.N):
for j in range(self.N):
# Rule1: if dead cell have exactly 3 surrouding living cell, it become a live
if (self.grid[i,j] == 0 and self.neighbour[i,j] == 3):
self.grid[i,j] = 1
# Rule2 and Rule3: if living cell has > 3 or < 2 surrounding, it dies
if (self.grid[i,j] == 1 and(self.neighbour[i,j] < 2 or self.neighbour[i,j] > 3) ):
self.grid[i,j] = 0
# livining cell has 2 or 3, it staying alive
return self.grid
def generations(self):
for g in range (self.G):
yield self.step()
def run(self):
fig, ax = plt.subplots()
mat = ax.matshow(self.grid)
ani = animation.FuncAnimation(fig, mat.set_data, self.generations, interval=500, repeat=False)
plt.show()
def main():
parser = argparse.ArgumentParser(description='Conway game of life')
parser.add_argument('N', type=int, help='Grid Size')
parser.add_argument('G', type=int, help='Generations Count')
args = parser.parse_args()
g = Conway(args.N,args.G)
g.run()
if __name__ == "__main__":
main()