Conways game of life is a basic cellular automata simulation. In this gridbased simulation the state of a cell, dead or alive, is defined by the number of its direct neigbors:
#| standalone: true
#| viewerHeight: 700
from shiny import *
xdim=80
ydim=25
steps=100
def create_grid(xdim,ydim):return( [[x]*xdim for x in [0]* ydim] )
def neigbors(grid):
neib = create_grid(xdim,ydim)
for i in range(ydim):
for j in range(xdim):
for y in range(i-1,i+2):
for x in range(j-1,j+2):
neib[i][j] += grid[y%ydim][x%xdim]
neib[i][j] = neib[i][j]-grid[i][j]
return(neib)
def step(grid):
neibs = neigbors(grid)
for i in range(ydim):
for j in range(xdim):
if grid[i][j] == 1:
if neibs[i][j] in [2,3]:
grid[i][j] = 1
else:
grid[i][j] = 0
else:
if neibs[i][j] == 3:
grid[i][j] = 1
return(grid)
def populate(grid,coord_array):
coord_range = [(y, x) for y in range(ydim) for x in range(xdim)]
for (y,x) in coord_range:
if (y,x) in coord_array:
grid[y][x]=1
return(grid)
def Vis(grid):
vis = str()
for y in range(ydim):
for x in range(xdim):
if grid[y][x] == 0: vis += '░'
else: vis += '█'
vis += '\n'
return vis
creatures = {
"glider" : [(0,1),(1,2),(2,0),(2,1),(2,2),(10,1),(11,2),(12,0),(12,1),(12,2)],
"blinker" : [(10,10),(10,11),(10,12)],
"ggun" :[(5,1),(5,2),(6,1),(6,2),(5,11),(6,11),(7,11),(4,12),(8,12),(3,13),(9,13),
(3,14),(9,14),(6,15),(4,16),(8,16),(5,17),(6,17),(7,17),(6,18),(3,21),(4,21),
(5,21),(3,22),(4,22),(5,22),(2,23),(6,23),(2,25),(7,25),(1,25),(6,25),(3,35),(4,35),(3,36),(4,36)]
}
# [(15,9),(15,10),(16,9),(16,10),(16,23),(13,21),(13,22),(14,20),(15,19),(16,19),(17,19),(19,21),(19,22),(18,20),(15,25),(16,25),(16,26),(17,25),(18,24),(14,24),(13,29),(13,30),(14,29),(14,30),(15,29),(15,30),(16,31),(12,31),(16,33),(11,33),(17,33),(12,33),(13,43),(13,44),(14,43),(14,44)]
def main(xdim,ydim,steps,creature):
grid = create_grid(xdim,ydim)
populate(grid,creatures[creature])
visus = [Vis(grid)]
for s in range(1,steps):
visus.append(Vis(step(grid)))
return(visus)
app_ui = ui.page_fluid(
ui.row(
ui.input_slider("n", "Step", 0, steps, 0, animate=True),
ui.input_select(
"id", "Creature",
choices={"ggun":"Glidergun","glider":"Glider","blinker":"Blinker"}
) ),
ui.row(
ui.column(
12,
ui.output_text_verbatim("GOL")
)
)
)
def server(input, output, session):
@reactive.Calc
def create_GOL():
return main(xdim,ydim,steps,creature=str(input.id()))
@output
@render.text
def GOL():
return create_GOL()[input.n()]
app = App(app_ui, server)
Shinylive
Code
# Shinylive for Python facilitates the deveolpment of shinyapps using python, that# can be compiled for the browser using [Webassembly](https://de.wikipedia.org/wiki/WebAssembly).app_ui = ui.page_fluid( ui.row( ui.input_slider("n", "Step", 0, steps, 0, animate=True), ui.input_select("id", "Creature", choices={"ggun":"Glidergun","glider":"Glider","blinker":"Blinker"} ) ), ui.row( ui.column(12, ui.output_text_verbatim("GOL") ) ))def server(input, output, session):@reactive.Calcdef create_GOL():return main(xdim,ydim,steps,creature=str(input.id()))@output@render.textdef GOL():return create_GOL()[input.n()]app = App(app_ui, server)
Python Implementation
The function create_grid returns a 2d array of specified dimensions.
def create_grid(xdim,ydim):return( [[x]*xdim for x in [0]* ydim] )
To count living cells in the neighbourhood of all cells the function neighbors creates an other grid that stores the counts for each cell in the grid.
def neigbors(grid): neib = create_grid(xdim,ydim)for i inrange(ydim):for j inrange(xdim):for y inrange(i-1,i+2):for x inrange(j-1,j+2): neib[i][j] += grid[y%ydim][x%xdim] neib[i][j] = neib[i][j]-grid[i][j]return(neib)
The step function iterates through the grid and gives birth or kills cells based oon their neigbourhood.
To populate our World the populate fuction adds living cells at defined locations in the grid, specified by an array of coordinate tuples.
def populate(grid,coord_array): coord_range = [(y, x) for y inrange(ydim) for x inrange(xdim)] for (y,x) in coord_range:if (y,x) in coord_array: grid[y][x]=1return(grid)
The coord_array are stored in a dict called creatures: