Beyond Conway — Life-like and Multi-State Rules
The previous chapter ended on a causal direction: the rule produces the structures, and only then do we give the structures names. Change the rule and the names change with it.
That matters because Conway’s Game of Life is famous enough that it can accidentally become the definition of cellular automata.
It is not.
Life is one point in a much larger design space.
Keep the same two-dimensional grid and the same eight-cell Moore neighborhood, then change only the birth and survival counts.
You immediately get a family of Life-like cellular automata.
Then add more than two cell states and the design space expands again.
This chapter turns the rule itself into data so we can explore that space systematically.
Generalize B3/S23
Life is:
B3/S23
That slash notation is a community convention, not a formal standard: B lists the neighbor counts that cause birth, S lists the counts that allow survival. Binary rules of this birth/survival form on the Moore neighborhood are commonly called Life-like rules. The notation is worth adopting because the whole family then fits in strings like "B36/S23" — but remember it only covers outer-totalistic rules, where the center cell is treated separately from the neighbor sum. It cannot express arbitrary 512-row tables.
Represent the birth and survival counts as sets:
LIFE_BIRTH = {3}
LIFE_SURVIVE = {2, 3}
Then write one general transition function:
def life_like_step(grid, birth, survive):
n = neighbor_count(grid)
born = (grid == 0) & np.isin(n, list(birth))
stays_alive = (grid == 1) & np.isin(n, list(survive))
return (born | stays_alive).astype(np.uint8)
Conway’s rule becomes:
grid = life_like_step(grid, birth={3}, survive={2, 3})
The engine no longer knows anything about Conway.
It knows only how to apply a totalistic birth/survival rule. (Verified: identical output to life_step on the same inputs.)
The family is large: birth can be any subset of {0..8} and survival any subset independently, giving 2^9 × 2^9 = 262,144 Life-like rules. B3/S23 is one address among them — which is why the interesting question is never “which rule looks novel” but “which address behaves differently, and by what measure.”
HighLife
HighLife uses:
B36/S23
The only difference from Conway’s Life is that dead cells are also born when they have six live neighbors.
next_grid = life_like_step(
grid,
birth={3, 6},
survive={2, 3},
)
One extra birth condition changes the available long-term structures. HighLife is particularly well known for supporting a small replicator: a pattern that copies itself along a diagonal every 12 generations. Note what that claim is — an observed dynamical behavior of a specific pattern under a specific rule, cataloged by the Life community — not a property of the rule in general.
That is exactly the kind of experiment cellular automata are good at:
small rule change
|
v
large behavioral change
Seeds
Seeds uses:
B2/S
Cells never survive.
A dead cell is born if it has exactly two live neighbors.
next_grid = life_like_step(
grid,
birth={2},
survive=set(),
)
This creates a very different world because every live cell is guaranteed to disappear at the next generation.
Persistence must therefore exist as propagation rather than individual survival.
That distinction is worth noticing.
A stable high-level process does not require stable low-level components.
Compare rules under the same initial condition
rng = np.random.default_rng(42)
grid = (rng.random((120, 160)) < 0.25).astype(np.uint8)
Run the same initial grid under several rules:
rules = {
"Life": ({3}, {2, 3}),
"HighLife": ({3, 6}, {2, 3}),
"Seeds": ({2}, set()),
}
The canonical figure holds the initial condition, grid, neighborhood, boundaries and number of generations constant. Only the rule changes — three points in the 262,144-rule family, compared on behavior rather than screenshots:
| Rule | Birth set | Survival set | What changes vs Life |
|---|---|---|---|
| Life (B3/S23) | {3} | {2, 3} | baseline: stills, oscillators, gliders |
| HighLife (B36/S23) | {3, 6} | {2, 3} | one extra birth; diagonal replicator |
| Seeds (B2/S) | {2} | {} (none survive) | persistence only as propagation |
python scripts/figures/cellular-automata/part01_foundations.py 07

That controlled comparison is much more informative than looking at three unrelated screenshots.
One caveat travels with the figure: the grid is finite with periodic boundaries (inherited from neighbor_count), so structures near opposite edges interact through wraparound. Hold the boundary constant across rules — as the figure script does — and treat edge-touching structures with suspicion.
Record population curves
def population_curve(initial, birth, survive, steps=200):
state = initial.copy()
values = []
for _ in range(steps):
values.append(int(state.sum()))
state = life_like_step(state, birth, survive)
return values
Now rule comparison becomes an experiment rather than a visual impression.
Population is only one observable. Two rules can have similar population curves while producing very different spatial organization, which is why later chapters add richer measurements. The guardrail for this whole chapter:
different-looking pattern
≠ fundamentally different dynamical regime
A new screenshot is a candidate difference. A measured difference in densities, periods, displacements, or outcome distributions is an actual one.
Add a third state
Binary states are not required.
Consider a cell with three states:
0 = ready
1 = firing
2 = refractory
A simple excitable automaton can update like this:
def excitable_step(grid):
firing = (grid == 1).astype(np.uint8)
firing_neighbors = neighbor_count(firing)
next_grid = np.zeros_like(grid)
# firing -> refractory
next_grid[grid == 1] = 2
# refractory -> ready
next_grid[grid == 2] = 0
# ready -> firing if exactly two neighbors fire
activate = (grid == 0) & (firing_neighbors == 2)
next_grid[activate] = 1
return next_grid
This is the update structure of Brian’s Brain: ready cells fire when exactly two neighbors are firing, firing cells become refractory, and refractory cells return to ready.
The third state gives each cell a one-step local memory.
A cell can now distinguish:
ready to activate
from:
recently active and temporarily unable to activate
That extra state dramatically changes propagation behavior.
State is local memory
This gives us a broader interpretation of cell state.
A state value is not merely a color.
It can represent the local memory needed by the update rule.
For example:
forest fire
0 empty
1 tree
2 burning
traffic
0 empty
1 vehicle
predator-prey
0 empty
1 prey
2 predator
neural CA
vector of visible + hidden channels
As we increase the state space, each cell can carry more context between updates.
Rules as configuration
We can make Life-like rules parseable:
def parse_rule(text):
birth_text, survive_text = text.upper().split("/")
birth = {int(x) for x in birth_text.removeprefix("B")}
survive = {int(x) for x in survive_text.removeprefix("S")}
return birth, survive
Then:
birth, survive = parse_rule("B36/S23")
(Verified: ({3, 6}, {2, 3}); "B2/S" parses to ({2}, set()). Counts here are single digits 0–8, which is all the Moore neighborhood admits.)
Now a whole family of automata can be stored as strings, files or experiment parameters.
That is a useful engineering transition:
rule hard-coded in function
->
rule represented as data
Once rules are data we can search them, mutate them, compare them and optimize them.
Where the book goes next
We now have the complete foundation:
1D binary CA
-> encoded elementary rules
-> Rule 30 and complexity
-> Rule 110 and computation
-> 2D Life
-> recurring structures
-> families of rules
-> multiple states
The rest of Part II uses these mechanisms for worlds with meaning:
- stochastic rules,
- forest-fire simulations,
- traffic flow,
- diffusion and reaction-diffusion,
- ecosystems,
- cave generation,
- terrain and textures.
Later we will remove another restriction and allow state to become continuous, leading to Lenia and learned neural cellular automata.
But the conceptual engine will still be recognizable:
local state + local perception + shared rule + repeated updates.
That is the machine we have built. The first addition is probability: the next chapter makes randomness an explicit part of the rule.
Research
Zenil, H. & Martinez, G. J. — Cellular Automata (Scholarpedia). Grounds the chapter’s taxonomy: binary outer-totalistic rules on the Moore neighborhood are the Life-like family with B/S notation (B lists birth counts, S survival counts), and Brian’s Brain is the canonical three-state example with quiescent, firing, and refractory states. Use it to check that “Life-like” is being used in the literature’s sense. http://www.scholarpedia.org/article/Cellular_automata
LifeWiki (Conway Life community archive). The pattern reference behind the HighLife claim: the B36/S23 replicator that copies itself along a diagonal every 12 generations, plus the broader cataloged ecologies of Life-like rules. Consult before asserting what any specific rule “typically” does. https://conwaylife.com/wiki/
Berto, F. & Tagliabue, J. — Cellular Automata (Stanford Encyclopedia of Philosophy). Supports the chapter’s restraint: Life is one system among many (probabilistic, asynchronous, non-uniform, and continuous variants all count), and claims about what a rule family does must be scoped to the configurations actually studied. https://plato.stanford.edu/entries/cellular-automata/