Conway's game of life

Game of life on wikipedia.

It is also available from the Models module as Models.game_of_life.

using Agents

1. Define the rules

Rules of Conway's game of life: DSRO (Death, Survival, Reproduction, Overpopulation). Cells die if the number of their living neighbors is <D or >O, survive if the number of their living neighbors is ≤S, come to life if their living neighbors are ≥R and ≤O.

rules = (2, 3, 3, 3)

2. Build the model

First, define an agent type. It needs to have the compulsary id and pos fields, as well as an status field that is true for cells that are alive and false otherwise.

mutable struct Cell <: AbstractAgent

The following function builds a 2D cellular automaton. rules is of type Tuple{Int,Int,Int,Int} representing DSRO.

dims is a tuple of integers determining the width and height of the grid environment. metric specifies whether cells connect to their diagonal neighbors.

This function creates a model where all cells are "off".

function build_model(; rules::Tuple, dims = (100, 100), metric = :chebyshev, seed = 120)
    space = GridSpace(dims; metric)
    properties = Dict(:rules => rules)
    model = ABM(Cell, space; properties, rng = MersenneTwister(seed))
    idx = 1
    for x in 1:dims[1]
        for y in 1:dims[2]
            add_agent_pos!(Cell(idx, (x, y), false), model)
            idx += 1
    return model

Now we define a stepping function for the model to apply the rules to agents.

function ca_step!(model)
    new_status = fill(false, nagents(model))
    for agent in allagents(model)
        nlive = nlive_neighbors(agent, model)
        if agent.status == true && (nlive ≤ model.rules[4] && nlive ≥ model.rules[1])
            new_status[] = true
        elseif agent.status == false && (nlive ≥ model.rules[3] && nlive ≤ model.rules[4])
            new_status[] = true

    for k in keys(model.agents)
        model.agents[k].status = new_status[k]

function nlive_neighbors(agent, model)
    neighbor_positions = nearby_positions(agent, model)
    all_neighbors =
        Iterators.flatten(ids_in_position(np, model) for np in neighbor_positions)
    sum(model[i].status == true for i in all_neighbors)

now we can instantiate the model:

model = build_model(rules = rules, dims = (50, 50))
AgentBasedModel with 2500 agents of type Cell
 space: GridSpace with size (50, 50), metric=chebyshev and periodic=true
 scheduler: fastest
 properties: Dict(:rules => (2, 3, 3, 3))

Let's make some random cells on

for i in 1:nagents(model)
    if rand(model.rng) < 0.2
        model.agents[i].status = true

3. Animate the model

We use the InteractiveDynamics.abm_video for creating an animation and saving it to an mp4

using InteractiveDynamics
import CairoMakie
ac(x) = x.status == true ? :black : :white
am(x) = x.status == true ? '■' : '□'
    "game of life.mp4",
    title = "Game of Life",
    ac = :black,
    as = 12,
    am = am,
    framerate = 5,