Peter Norvig
2017 | 2020

Conway's Game of Life

The cellular automata game Life, invented by the mathematician John H. Conway, makes a fun programming exercise. Let's review the rules:

The world of Life is an infinite two-dimensional orthogonal grid of cells, each of which is in one of two possible states, live or empty. Each cell has eight neighbors, the cells that are horizontally, vertically, or diagonally adjacent. We go from one generation to the next with these rules:

  • Any live cell with two or three live neighbors remains live in the next generation.
  • Any empty cell with exactly three live neighbors becomes live in the next generation.
  • All other cells are empty in the next generation.

For example, in the diagram below, "@" cells are live. In the transition from Generation 0 to 1, the cell marked "," becomes empty (dies off) because it has zero live neighbors. In the next transition, a fourth @ becomes live, because it has 3 live neighbors. All other cells stay the same.

 . . . . .        . . . . .        . . . . .
 . . . @ .        . . . , .        . . . . .
 . @ . . .        . @ . . .        . @ @ . .
 . @ @ . .        . @ @ . .        . @ @ . .
 . . . . .        . . . . .        . . . . .
   Gen 0            Gen 1            Gen 2



The world continues to evolve by these rules for as long as you care to observe. Life is a zero-player infinite game.

Developing a Life Program

To create a program to play Life, go through the inventory of concepts and decide how to implement each one:

  • World and Cell: A state of the world must represent which cells are empty and which are live. That looks like a job for a two-dimensional array with values 1 for live and 0 for empty. The tricky part is that the number of cells is infinite, and we can't store an infinite array in a finite computer. I can think of three ways around this issue:
    • Use a fixed-size two-dimensional array but change the rules: cells at the edge have fewer neighbors, or they wrap around.
    • Use a sparse matrix that can change size, expanding in any direction to contain all the live cells.
    • Use a set of live cells, where a cell is represented as an (x, y)-coordinate pair. I think this is the easiest choice.
      Example: world = {(3, 1), (1, 2), (1, 3), (2, 3)}; cell = (1, 2)
  • Live and Empty: A cell is live if it is a member of a world, i.e. if cell in world is true.
  • Neighbors: The cell (x, y) has eight neighbors, formed by adding or subtracting 1 from x or y or both.
    Example: neighbors((1, 2))((0, 1), (1, 1), (2, 1), (0, 2), (2, 2), (0, 3), (1, 3), (2, 3))
  • Next Generation: The function next_generation(world) returns a new world with the new set of live cells according to the rules.
    Example: next_generation({(3, 1), (1, 2), (1, 3), (2, 3)}){(1, 2), (1, 3), (2, 3)}
  • Sequence of Generations: The generator function life(world, n) yields n generations starting from the given world.
  • Display: We will need some way to display the generations. Let's defer that for now.
  • Live Neighbor Counts: To determine the next generation, we need to know how many live neighbors each cell has. A good way to represent this is a mapping of {cell: count}. An easy way to produce this mapping is with a Counter, passing it every neighbor of every live cell. This may feel like we're doing the counting "backwards." Instead of asking "for each cell, how many live neighbors does it have?" we are saying "for each live cell, increment the count of each of its neighbors." The two amount to the same thing because neighbor is symmetric—if P is a neighbor of Q, then Q is a neighbor of P. Below we see the neighbor counts for each of the three generations of the example above; in each generation the top diagram gives the neighbor counts for the empty cells, and the bottom diagram gives the counts for the live cells. This is just to make the diagram easier to read; in the code the counts are all in one Counter.
 . . 1 1 1        . . . . .        . . . . .
 1 1 2 @ 1        1 1 1 , .        1 2 2 1 .
 2 @ 4 2 1        2 @ 3 1 .        2 @ @ 2 .
 2 @ @ 1 .        2 @ @ 1 .        2 @ @ 2 .
 1 2 2 1 .        1 2 2 1 .        1 2 2 1 .
   Gen 0            Gen 1            Gen 2
 . . . . .        . . . . .        . . . . .
 . . . 0 .        . . . , .        . . . . .
 . 2 . . .        . 2 . . .        . 3 3 . .
 . 2 2 . .        . 2 2 . .        . 3 3 . .
 . . . . .        . . . . .        . . . . .


Now we're ready to start implementing. First some imports and type declarations:

In [4]:
from collections     import Counter
from typing          import Set, Tuple, Dict, Iterator
from itertools       import islice
from IPython.display import clear_output, display_html
from time            import sleep
import sys

Cell  = Tuple[int, int]
World = Set[Cell] 

Now the complete implementation, except for display:

In [9]:
def life(world, n=sys.maxsize) -> Iterator[World]:
    """Yield `n` generations, starting from the given world."""
    for g in range(n):
        yield world
        world = next_generation(world)

def next_generation(world) -> World:
    """The set of live cells in the next generation."""
    return {cell for cell, count in neighbor_counts(world).items()
            if count == 3 or (count == 2 and cell in world)}

def neighbor_counts(world) -> Dict[Cell, int]:
    """A Counter of the number of live neighbors for each cell."""
    return Counter(xy for cell in world 
                      for xy in neighbors(cell))

def neighbors(cell) -> List[Cell]:
    """All 8 adjacent neighbors of cell."""
    (x, y) = cell
    return [(x + dx, y + dy) 
            for dx in (-1, 0, 1) 
            for dy in (-1, 0, 1) 
            if not (dx == 0 == dy)]

We can see how this works:

In [3]:
world = {(3, 1), (1, 2), (1, 3), (2, 3)}
next_generation(world)
Out[3]:
{(1, 2), (1, 3), (2, 3)}
In [4]:
list(life(world, 4))
Out[4]:
[{(1, 2), (1, 3), (2, 3), (3, 1)},
 {(1, 2), (1, 3), (2, 3)},
 {(1, 2), (1, 3), (2, 2), (2, 3)},
 {(1, 2), (1, 3), (2, 2), (2, 3)}]
In [5]:
neighbor_counts(world)
Out[5]:
Counter({(0, 1): 1,
         (0, 2): 2,
         (0, 3): 2,
         (1, 1): 1,
         (1, 3): 2,
         (2, 1): 2,
         (2, 2): 4,
         (2, 3): 2,
         (2, 0): 1,
         (3, 0): 1,
         (3, 2): 2,
         (4, 0): 1,
         (4, 1): 1,
         (4, 2): 1,
         (1, 2): 2,
         (1, 4): 2,
         (2, 4): 2,
         (3, 3): 1,
         (3, 4): 1,
         (0, 4): 1})
In [11]:
neighbors((1, 2))
Out[11]:
[(0, 1), (0, 2), (0, 3), (1, 1), (1, 3), (2, 1), (2, 2), (2, 3)]

Display

To display a world, we'll specify a rectangular window on the infinite plane with ranges of Xs and Ys coordinates. The function picture returns a string depicting the world within that window:

In [7]:
LIVE  = '@'
EMPTY = '.'
PAD   = ' '
        
def picture(world, Xs: range, Ys: range) -> str:
    """Return a picture of the world: a grid of characters representing the cells in this window."""
    def row(y): return PAD.join(LIVE if (x, y) in world else EMPTY for x in Xs)
    return '\n'.join(row(y) for y in Ys)
In [8]:
g = life(world)
next(g), next(g)
Out[8]:
({(1, 2), (1, 3), (2, 3), (3, 1)}, {(1, 2), (1, 3), (2, 3)})
In [9]:
print(picture(world, range(5), range(5)))
. . . . .
. . . @ .
. @ . . .
. @ @ . .
. . . . .

Animated Display

The function animate_life displays n generations: display world, pause for pause seconds, then clear the screen and display the next generation.

In [10]:
def animate_life(world, n=10, Xs=range(10), Ys=range(10), pause=1/5):
    """Display the evolving world for `n` generations."""
    for g, world in enumerate(life(world, n)):
        clear_output(wait=True)
        display_html(pre(f'Generation: {g:2}, Population: {len(world):2}\n' +
                         picture(world, Xs, Ys)), raw=True)
        sleep(pause)
        
def pre(text) -> str: return '<pre>' + text + '</pre>'
In [11]:
animate_life(world, 4, range(5), range(5), 1)
Generation:  3, Population:  4
. . . . .
. . . . .
. @ @ . .
. @ @ . .
. . . . .

Interesting Worlds

Now let's take a look at some configurations of cells that Life enthusiasts have discovered. It would be tedious to write out a set of (x, y) coordinates, so we will define the function shape that takes a picture as input and returns a world; shape and picture are more-or-less inverses.

In [12]:
def shape(picture, dx=3, dy=3) -> World:
    """Convert a graphical picture (e.g. '@ @ .\n. @ @') into a world (set of cells)."""
    cells = {(x, y) 
             for (y, row) in enumerate(picture.splitlines())
             for (x, c) in enumerate(row.replace(PAD, ''))
             if c == LIVE}
    return slide(cells, dx, dy)

def slide(cells, dx, dy):
    """Translate/slide a set of cells by a (dx, dy) offset."""
    return {(x + dx, y + dy) for (x, y) in cells}

blinker     = shape("@@@")
block       = shape("@@\n@@")
beacon      = block | slide(block, 2, 2)
toad        = shape("[email protected]@@\n@@@.")
glider      = shape("[email protected]\n[email protected]\n@@@")
rpentomino  = shape("[email protected]@\n@@.\n[email protected]", 36, 20)
line        = shape("[email protected]@@@@@@@[email protected]@@@@[email protected]@@[email protected]@@@@@@[email protected]@@@@", 10, 10)
growth      = shape("@@@[email protected]\n@\n[email protected]@\n[email protected]@[email protected]\n@[email protected]@", 15, 20)
zoo         = (slide(blinker, 5, 25) | slide(glider, 8, 13) | slide(blinker, 20, 25) |
               slide(beacon, 24, 25) | slide(toad, 30, 25)  | slide(block, 13, 25) | slide(block, 17, 33))

Here is how shape and slide work:

In [13]:
shape("""@ @ .
         . @ @""")
Out[13]:
{(3, 3), (4, 3), (4, 4), (5, 4)}
In [14]:
print(picture(_, range(7), range(7)))
. . . . . . .
. . . . . . .
. . . . . . .
. . . @ @ . .
. . . . @ @ .
. . . . . . .
. . . . . . .
In [15]:
block
Out[15]:
{(3, 3), (3, 4), (4, 3), (4, 4)}
In [16]:
slide(block, 100, 200)
Out[16]:
{(103, 203), (103, 204), (104, 203), (104, 204)}

Let's run some examples. If you are viewing a static notebook, you will only see the last generation; rerun each cell to see all the generations.

In [17]:
animate_life(blinker)
Generation:  9, Population:  3
. . . . . . . . . .
. . . . . . . . . .
. . . . @ . . . . .
. . . . @ . . . . .
. . . . @ . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
In [18]:
animate_life(beacon)
Generation:  9, Population:  6
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . @ @ . . . . .
. . . @ . . . . . .
. . . . . . @ . . .
. . . . . @ @ . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
In [19]:
animate_life(toad)
Generation:  9, Population:  6
. . . . . . . . . .
. . . . . . . . . .
. . . . . @ . . . .
. . . @ . . @ . . .
. . . @ . . @ . . .
. . . . @ . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
In [20]:
animate_life(glider, 20)
Generation: 19, Population:  5
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . @ .
. . . . . . . . . @
In [21]:
animate_life(rpentomino, 130, range(48), range(40))
Generation: 129, Population: 163
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . @ @
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @
. . . . @ . . . @ @ . . @ . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . @ . @
. . . . @ . @ @ . . . . @ . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . @ . @
. . . . @ . . . . . . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ .
. . . . . . . @ . . . @ . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . .
. . . . . . . . . . @ . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . .
. . . . . . . @ . @ . . . . . . . . . . . . . . . . . . @ @ . . . . @ . . . . . . . . . . . . .
. . . . . @ . . . @ @ . . . . . . . . . . . . . . . . @ @ @ . . . @ . . . . . . . . . . . . . .
. . . . @ @ . . . @ . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . . . . . . . @ @ @
. . . . @ . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . @ . .
. . . . . @ . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . . . . . . @ @
. . . . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ @ @ . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ @ @ @ . . . @ . . . . .
@ . . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ . . . . . . @ . @ @ . . .
@ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . @ @ . . . .
. . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . @ . . . . .
. . . @ . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ @ . . . . . . . . . . .
@ . . @ . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . @ @ @ . . . . . . . . . . . .
@ @ . @ . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . @ . . . . . . . . . . . .
. @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . @ . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . @ . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . @ @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
In [22]:
animate_life(zoo, 160, range(48), range(40))
Generation: 159, Population: 105
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . @
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . @ . . . @
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . @ . @ . . @ .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . @ . . . @ @ . . . @
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . @ . . . @ . @ @ . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ @ . @ @ @ . . . @ . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . @ . . . . . . @ @ . . @ @ @ @ . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . . @ . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ @ @ . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . . @ @ . . . . . . . @ .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ . . @ . . . . . . . . @ @ .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ . . . . . . . . . @ .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . @ . . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . @ . . . . . @ . . @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
In [23]:
animate_life(growth, 200, range(40), range(40))
Generation: 199, Population: 100
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . @ @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . @ @ . . @ @ . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . @ . . @ . @ @ @ . . . @ . . . . . . . . . . . . . . . . . . . . . .
. . . . . . @ . @ . . . @ @ @ . @ . . . @ @ . . @ @ . . . . . . . . . . . . . .
. . . . . . @ . . . @ . @ @ @ . . . . . . @ @ . @ @ @ . . . . . . . . . . . . .
. . . . . . . @ . . . @ @ @ . . . . . @ . @ . @ . . . @ @ . . . . . . . . . . .
. . . . . . . . @ . . . @ . @ @ . . . . @ . . @ . @ . @ @ . . . . . . . . . . .
. . . . . . . . . . . @ . . . @ . . @ @ . . . @ . @ . @ @ . . . . . . . . . . .
. . . . . . . . . . . . . . . . @ @ @ . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . @ . @ . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . @ . . @ . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . @ . . . . . @ . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . @ . @ @ . @ . . . . . . @ @ @ . . .
. . . . . . . . . . . . . . . . . . . . . @ . . . @ @ @ . . . . . @ . . . . . .
. . . . . . . . . . . . . . . . . . . . . . @ . . . @ . . . . . . @ . . @ . . .
. . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ . . . . . . . @ @ . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . @ @ . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Outside of IPython

If you want to run this code in your terminal, outside of an Ipython/Jupyter notebook, you can remove the line:

from IPython.display import clear_output, display_html

and add these lines:

def clear_output(): print("\033[;H\033[2J") # ANSI terminal home and clear
def display_html(text, raw=False): print(text) 
def pre(text) -> str: return text

If you want to create a fancier display using some graphics packagee, be my guest. Let me know what you create.

Coding Kata

I once attended a code kata in which one of the exercises was to write Life without using any conditional (e.g. if) statements. I did it by using roughly the program shown here, but changing the lone if to a filter in next_generation:

In [24]:
def next_generation(world):
    """The set of live cells in the next generation."""
    counts = neighbor_counts(world)
    def live(cell): return counts[cell] == 3 or (counts[cell] == 2 and cell in world)
    return set(filter(live, counts))