Why use a set of coordinates?

A large empty grid does not need to be stored. Only a live cell or one of its neighbours can be alive next generation. The coordinate set can expand in every direction, including negative coordinates, without a wrapping rule or a clipped edge.

The complete example

# Unbounded B3/S23. Coordinates are integer (x, y) tuples.
from collections import Counter


def next_generation(live):
    counts = Counter(
        (x + dx, y + dy)
        for x, y in live
        for dy in (-1, 0, 1)
        for dx in (-1, 0, 1)
        if dx != 0 or dy != 0
    )
    return {
        position
        for position, count in counts.items()
        if count == 3 or (count == 2 and position in live)
    }


if __name__ == "__main__":
    live = {(0, 1), (1, 1), (2, 1)}
    for generation in range(3):
        print(generation, sorted(live))
        live = next_generation(live)
Download game_of_life.py

Run and verify it

Save the file and run python3 game_of_life.py. At generation 0 the live cells are (0,1), (1,1), (2,1). At generation 1 they are (1,0), (1,1), (1,2). Generation 2 returns to the first set.

Check the same two-step cycle in the browser.

Three checks before changing the rule

  1. Exclude the centre cell from its own neighbour count.
  2. Keep the old live set unchanged while constructing the next set.
  3. Test an isolated cell (it dies), a block (it stays unchanged), and a glider (the same shape translates after four steps).

Limits and next experiments

This direct sparse algorithm is for small experiments, not a replacement for Hashlife on huge patterns. A long-running seed can still use a lot of time and memory. The visual teaching board uses finite edges, while this example uses an unbounded plane.

Inspect the small-seed experiment · Read the B3/S23 rules · Open the advanced viewer