J. H. Conway’s Game of Life

See still life, glider and spaceship, oscillators, etc. Also see A New Kind of Science.

The module

"""
life.py
J. H. Conway's Game of Life.

The number of rows that you see is self.nrows.
Their index numbers run from 0 to self.nrows - 1 inclusive.
In addition, there are extra additional rows that are not displayed.
Their index numbers run from self.nrows to self.nrows + Life.extra - 1
inclusive.
Finally, there is one more row, at index number self.nrows + Life.extra,
that is always empty.
Ditto for the columns.
"""

import tkinter
import time

class Life(object):
    extra = 5      #how many extra rows or columns of boxes along each edge

    def __init__(self, picture):
        picture = picture.splitlines()
        self.nrows = len(picture)
        self.ncols = len(picture[0])
        self.b = self.emptyBoard()
        for row in range(self.nrows):
            self.b[Life.extra + row][Life.extra:Life.extra + self.ncols] = \
                [picture[row][col] == "X" for col in range(self.ncols)]

    def get(self, x, y):
        "Return the value at row y, column x."
        assert 0 <= x < self.ncols and 0 <= y < self.nrows
        return self.b[Life.extra + y][Life.extra + x]

    def emptyBoard(self):
        "Return a two-dimensional list of Bools (all False)."
        return [(self.ncols + 2 * Life.extra) * [False]
            for i in range(self.nrows + 2 * Life.extra)]

    def next(self):
        b1 = self.emptyBoard()
        for y in range(1, 2 * Life.extra + self.nrows - 1):
            for x in range(1, 2 * Life.extra + self.ncols - 1):
                #How many of the 8 neighbors of (x, y) are occupied?
                listOf8Neighbors = [
                    self.b[y1][x1]
                    for y1 in range(y - 1, y + 2)
                    for x1 in range(x - 1, x + 2)
                    if x1 != x or y1 != y
                ]

                count = listOf8Neighbors.count(True)
                if count == 2:
                    b1[y][x] = self.b[y][x] #Law of Survival
                else:
                    b1[y][x] = count == 3   #Laws of Birth and Death
        self.b = b1

def _test():
    import tkinter

    picture = """\
...........
.X......X..
..XX....X..
.XX.....X..
...........
...........
...........
...........
.XX........
.XX........
...........
"""

    life = Life(picture)
    boxSide = 12   #pixels

    root = tkinter.Tk()
    root.title("Conway's Game of Life")
    #dimensions in pixels
    root.geometry(f"{life.ncols * boxSide}x{life.nrows * boxSide}")

    canvas = tkinter.Canvas(root, highlightthickness = 0)
    canvas.pack(expand = tkinter.YES, fill = "both")

    while True:
        draw(life, canvas, boxSide)
        life.next()
        root.update()
        time.sleep(0.5)

def draw(life, canvas, boxSide):
    canvas.delete(tkinter.ALL)

    for y, row in enumerate(life.b[Life.extra:Life.extra + life.nrows]):
        for x, box in enumerate(row[Life.extra:Life.extra + life.ncols]):
            left = x * boxSide
            top  = y * boxSide

            canvas.create_rectangle(
                left,
                top,
                left + boxSide,
                top  + boxSide,
                width = 1,         #width of border
                outline = "white", #color of border
                fill = "black" if box else "gray"
            )

if __name__ == '__main__':
    _test()

Test the module:

python3 -m life

The Python program

This program imports the above module.

"""
lifeimporter.py

Run J. H. Conway's Game of Life on a tkinter Canvas widget.
"""

import tkinter
import time
import life   #the module life.py that we wrote

#Gosper glider gun.

picture = """\
......................................
.........................X............
.......................X.X............
.............XX......XX............XX.
............X...X....XX............XX.
.XX........X.....X...XX...............
.XX........X...X.XX....X.X............
...........X.....X.......X............
............X...X.....................
.............XX.......................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
"""

game = life.Life(picture)
boxSize = 12   #in pixels

def draw(game, canvas):
    canvas.delete(tkinter.ALL)

    for y in range(game.nrows):
        for x in range(game.ncols):
            left = x * boxSize
            top  = y * boxSize

            canvas.create_rectangle(
                left,
                top,
                left + boxSize,
                top  + boxSize,
                width = 1,         #width of border
                outline = "white", #color of border
                fill = "black" if game.get(x, y) else "gray"
            )

root = tkinter.Tk()
root.title("Conway's Game of Life")

#dimensions in pixels
root.geometry(f"{boxSize * game.ncols}x{boxSize * game.nrows}")

canvas = tkinter.Canvas(root, highlightthickness = 0)
canvas.pack(expand = tkinter.YES, fill = "both")

while True:
    draw(game, canvas)
    game.next()
    root.update()
    time.sleep(0.25)