Note
This notebook can be downloaded here: GameOfLife.ipynb
Code author: Ludovic Charleux <ludovic.charleux@univ-smb.fr>
Game of Life and other cellular automatons¶
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib nbagg
from matplotlib import animation, rc, cm
import IPython, io, urllib
rc('animation', html='html5')
Introduction¶
This notebook was inspired by the great video proposed by David Louapre available on his Youtube channel “Science Etonnante”:
IPython.display.YouTubeVideo('S-W0NX97DB0')
The Game of Life (GoL) is a good way to learn about the use of object oriented programming, numpy and matplotlib. It is also a very interresting scientific and mathematical problem. The GoL belongs to the wider group of problems called Cellular Automatons. A lot of alternative sets of interresting rules have been created/discovered over time since the invention of the GoL. In this tutorial, we introduce a simple class that can solve all those problems called Life Like Cellular Automatons (LLCA).
Further readings:
A generic class to simulate LLCAs¶
class LLCA:
"""
A Life Like Cellular Automaton (LLCA)
Inputs:
* C: a binary matrix representing the cells where 1 stands for alive and 0 for dead.
* rule: the rule of the in the format 'BXSY' where X and Y are the birth and survival conditions.
Example: GOL rule is "B3S23".
"""
def __init__(self, C = np.random.rand(50, 50), rule = "B3S23"):
self.C = np.array(C).astype(np.bool)
self.rule = rule
def parse_rule(self):
"""
Parses the rule string
"""
r = self.rule.upper().split("S")
B = np.array([int(i) for i in r[0][1:] ]).astype(np.int64)
S = np.array([int(i) for i in r[1] ]).astype(np.int64)
return B, S
def neighbors(self):
"""
Returns the number of living neigbors of each cell.
"""
C = self.C
N = np.zeros(C.shape, dtype = np.int8) # Neighbors matrix
N[ :-1, : ] += C[1: , : ] # Living cells south
N[ : , :-1] += C[ : ,1: ] # Living cells east
N[1: , : ] += C[ :-1, : ] # Living cells north
N[ : , 1: ] += C[ : , :-1] # Living cells west
N[ :-1, :-1] += C[1: ,1: ] # Living cells south east
N[1: , :-1] += C[ :-1,1: ] # Living cells north east
N[1: , 1: ] += C[ :-1, :-1] # Living cells north west
N[ :-1, 1: ] += C[1: , :-1] # Living cells south west
return N
def iterate(self):
"""
Iterates one time.
"""
B, S = self.parse_rule()
N = self.neighbors()
C = self.C
C1 = np.zeros(C.shape, dtype = np.int8)
for b in B: C1 += ((C == False) & (N == b))
for s in S: C1 += (C & (N == s))
self.C[:] = C1 > 0
The orginal Game of Life (rule B3S23)¶
# INITIAL CONFIGURATION
N = 100
t = np.linspace(0., 1., N+1)
X, Y = np.meshgrid(t, t)
f = 4
C0 = np.sin(2. * np.pi * f * X ) * np.sin(2. * np.pi * 2 * f * Y ) > -.1
g = LLCA(C0, rule = "B3S23")
# ANIMATION
def updatefig(*args):
g.iterate()
im.set_array(g.C)
return im,
fig, ax = plt.subplots()
ax.axis('off')
im = plt.imshow(g.C, interpolation = "nearest", cmap = cm.binary, animated=True)
anim = animation.FuncAnimation(fig, updatefig, frames=200, interval=50, blit=True)
plt.close()
anim
#plt.show()
<IPython.core.display.Javascript object>
Alternative rule: Day and Night (B3678S34678)¶
N = 100
t = np.linspace(0., 1., N+1)
X, Y = np.meshgrid(t, t)
f = 10
C0 = np.sin(2. * np.pi * f * X ) * np.sin(2. * np.pi * 2 * f * Y ) > 0.
g = LLCA(C0, rule = "B3678S34678")
def updatefig(*args):
g.iterate()
im.set_array(g.C)
return im,
fig, ax = plt.subplots()
ax.axis('off')
im = plt.imshow(g.C, interpolation = "nearest", cmap = cm.binary, animated=True)
anim = animation.FuncAnimation(fig, updatefig, frames=200, interval=50, blit=True)
plt.close()
anim
#plt.show()
<IPython.core.display.Javascript object>
Alternative rule: fractal-like B1S123¶
N = 200
C0 = np.zeros((N, N))
C0[1,1] = 1
g = LLCA(C0, rule = "B1S123")
def updatefig(*args):
g.iterate()
im.set_array(g.C)
return im,
fig, ax = plt.subplots()
ax.axis('off')
im = plt.imshow(g.C, interpolation = "nearest", cmap = cm.binary, animated=True)
anim = animation.FuncAnimation(fig, updatefig, frames=200, interval=40, blit=True)
plt.close()
anim
#plt.show()
<IPython.core.display.Javascript object>
Existing structures in GoL¶
Let’s simulate an existing puffer:
http://www.conwaylife.com/patterns/hivenudger2_106.lif
def life_parser(path, bottom_margin = 10, top_margin = 10, left_margin = 10, right_margin = 10):
"""
A life 1.06 file parser
http://www.conwaylife.com/wiki/Life_1.06
"""
data = "".join([l for l in open(path).readlines() if not l.startswith("#")])
data = pd.read_csv(io.StringIO(data), header = None, sep = " ").values
xmin, xmax = data[:,0].min(), data[:,0].max()
ymin, ymax = data[:,1].min(), data[:,1].max()
C0 = np.zeros( (xmax - xmin + top_margin + bottom_margin,
ymax - ymin + left_margin + right_margin) )
data[:,0] += -xmin + top_margin
data[:,1] += -ymin + left_margin
C0[data[:,0] , data[:,1]] = 1
return C0
path = "_data/hivenudger2_106.lif"
C0 = life_parser(path, top_margin = 100).T[:, ::-1]
fig, ax = plt.subplots()
plt.imshow(C0, cmap = cm.binary)
ax.axis('off')
plt.show()
<IPython.core.display.Javascript object>
g = LLCA(C0, rule = "B3S23") # B2S23 means Birth if 2 living neighbours and survival if 2 or 3 living neighbours
def updatefig(*args):
g.iterate()
im.set_array(g.C)
return im,
fig, ax = plt.subplots()
im = plt.imshow(g.C, interpolation = "nearest", cmap = cm.binary, animated=True)
anim = animation.FuncAnimation(fig, updatefig, frames=200, interval=50, blit=True)
#ax.axis('off')
plt.close()
anim
#plt.show()
<IPython.core.display.Javascript object>