Conway’s Game of live

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.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)

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 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)

The step function iterates through the grid and gives birth or kills cells based oon their neigbourhood.

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)

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 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)

The coord_array are stored in a dict called creatures:

Code
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)]
}

To obtain a quick visualisation of the world, the Vis function creates a string from the 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

Finally the main function runs the simualtion loop and returns the visualisation.

def main(xdim,ydim,steps,creature):
    grid = create_grid(xdim,ydim)
    visus = []
    populate(grid,creatures[creature])
    for s in range(1,steps):
        visus.append(Vis(step(grid)))
    return(visus)