Python with numpy and matplotlib
765 characters
This script is quite optimized, since it uses no loops: only numpy functions or animations loops (that are like recursion). So even if its quite long, I think that it was interesting to put it here (and it could be really shorter, but I just took my classic program, and simplified it).
import matplotlib.pyplot as P;import numpy as N;from matplotlib.animation import FuncAnimation;neighbours=lambda G:N.sum([N.roll(N.roll(G,y,1),x,0)for x in(-1,0,1)for y in(-1,0,1)if x+y],axis=0);A=N.array
class G:
o=1
def __init__(Z,e,r):Z.g=N.random.rand(e,r)<.3;Z.r=lambda a,v:N.logical_or(N.any(A([v==3]),axis=0),N.logical_and(a,N.any(A([N.logical_or(v==2,v==3)]),axis=0)))
def S(Z):Z.g=(Z.r(Z.g,neighbours(Z.g)),Z.g)[Z.o];return Z.g
def P(Z,x):Z.o^=1
def G(Z,n):
for _ in[0]*n:Z.S()
return Z.g
def R(Z,skip=0,t=0):l=P.figure();im=P.imshow(Z.g,cmap="Greys");l.canvas.mpl_connect('button_press_event',Z.P);FuncAnimation(l,lambda I:(im.set_array(Z.G(skip+1)),[im])[-1],interval=1,blit=1);P.show()
life=G(100,100);life.R(t=1)
If you want to change the grid, you can simply replace the statement:
life.fill()
That fills randomly the center part of the grid, by:
life.g=matrix
Where matrix is your grid, a numpy booleans array.
This code comes from a more complex code, this one :
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
out = __import__('sys').stdout.write
neighbours = lambda grid: np.sum([
np.roll(np.roll(grid, y, 1), x, 0)
for x in (-1, 0, 1) for y in (-1, 0, 1)
if x or y
], axis=0)
neighbours.__doc__ = """# function that returns sum of neighbours of all the
cells for a given grid"""
class GOL:
"""Object to run a game of life - type Cellular Automaton"""
on = True # variable: bool; is the simulation runing ?
i = 0 # generation number
def __init__(self, dim_x=800, dim_y=400, R=None, show_gen=False):
"""dim_x and dim_y are the batch size
R is the rule, specified as here: B___/S___
example: B3/S23 for the classic game of life.
default value: B3/S23"""
if R is None:
self.B, self.S = ([3], [2, 3])
else:
self.B, self.S = [list(map(int, p)) for p in R.split("/")]
self.shape = (dim_y, dim_x) # variable:tuple(int, int); grid shape
self.g = np.zeros(self.shape) == 1 # variable:np.array()
self.showGen = show_gen # variable:bool; print the gen number ?
if R is None: # default rule: a bit speedier (no loops)
# function to compute next generation for the conway's game of life
self.rule = lambda actuel, voisins: \
np.logical_or(
np.any(
np.array([voisins == 3]),
axis=0
),
np.logical_and(
actuel,
np.any(
np.array([
np.logical_or(
voisins == 2,
voisins == 3
)
]),
axis=0)
))
else: # other rules
# function to compute the next generation for any given rule
self.rule = lambda actuel, voisins: \
np.logical_or(
np.any(
np.array([voisins == b for b in self.B]),
axis=0
),
np.logical_and(
actuel,
np.any(
np.array([voisins == s for s in self.S]),
axis=0)
))
def __str__(self):
"""how to print the automata (info : rule and rule number)"""
return "I'm a life-like automata" \
'\nmy rule : B' + ''.join(map(str, self.B)) +\
'/S' + ''.join(map(str, self.S)) \
+ "\nmy hexa-code : " + str(self.rule_number())
def fill(self, mode, d=.3, n=10):
"""Random filling of the grid
mode : {'rNoise', 'cloud', 'parse'}
rNoise:
fill all the grid with the density d
cloud:
fill a third of the grid with the density d
parse:
fill n cells exactly in the grid (no overlappings)
"""
self.g &= False
s0, s1 = self.shape
if mode == "rNoise":
self.g = np.random.rand(*self.g.shape) < d
elif mode == "cloud":
self.g[s0 // 3: s0 // 3 * 2, s1 // 3: s1 // 3 * 2] = (
np.random.rand(s0 // 3, s1 // 3) < d)
elif mode == "parse":
for _ in range(n):
x, y = np.random.randint(s1), np.random.randint(s0)
while self.g[y, x]:
x, y = np.random.randint(s1), np.random.randint(s0)
self.g[y, x] = True # it's a boolean list !!!
def step(self):
"""Compute the next generation and refresh the grid"""
if self.on:
self.i += 1
# if self.showGen and not self.i % 10:
# out("generation " + str(self.i))
self.g = self.rule(self.g, neighbours(self.g))
return self.g
def pause(self, x):
"""Action for the animation : swich the value of self.on"""
self.on ^= True
def gen(self, n):
"""Generate n generations """
for _ in [0] * n:
self.step()
return self.g
def run(self, skip=0, t=0, showGen=False):
"""Run the CA with a matplotlib animation graphics"""
self.showGen = showGen
fig = plt.figure()
im = plt.imshow(self.g, cmap="Greys")
fig.canvas.mpl_connect('button_press_event', self.pause)
FuncAnimation(fig,
lambda i: (im.set_array(self.gen(skip + 1)), [im])[-1],
interval=t,
blit=True)
plt.show()
def rule_number(self):
return sum(2 ** (np.hstack((np.array(self.B), np.array(self.S) + 9))))
new_rule = lambda: (
np.arange(8)[np.random.rand(8) > .5],
np.arange(8)[np.random.rand(8) > .5]
)
new_rule.__doc__ == """returns a random rule"""
life = GOL(600, 600, R="3/23")
life.fill("cloud")
print(life)
life.run(t=1, showGen=True)
This long version allows you to chose any life-like rule, and to fill the grid with different random modes.
;
before}
s. Alsovar
s can be eliminated at times (if it doesn't break your code). And for one-linefor
s,if
s etc, you can eliminate the{ }
completely:for(...) for(...) dosomething()
. \$\endgroup\$