John Conway’s Game of Life

John Conway’s Game of Life also know as Life is a zero-player game. Here the link to a site where you can play life on. Life is Turing complete.

Last year, 2020, John Horton Conway, the creator of this game, died at the age of 82 due to COVID – 19.

Alert: Conway’s Game of Life is not to be confused with the Parker brother’s Game of Life.

How to Play Life

The game takes place square 2-D board with infinite area. The board is divided into square cells. Each cell is either dead or alive. Usually dead cells are black, and live cells are white. Each cell has has four diagonal neighbors , two horizontal neighbors and two vertical neighbors. Depending on how many live neighbors a cell has, it either it stays alive or dead, or it is born(from a dead state becomes alive), or it dies. The rules that determine a cell’s future are ˸ˑ-

  1. A cell with less that two neighbors dies as if by under population.
  2. A cell with 4 or more neighbors dies as if by over population.
  3. A cell with two or three neighbors remains the same.

For a cell that is dead:

  1. A cell with three neighbors is born as if by reproduction.

It is important to understand that, cells that are born cannot change the future of neighboring cells in the generation they are born in. Only after that generation, do they help decide weather their neighboring cells live or die.

The player generates the initial pattern of cells called the seed. This is the only input given by the player. After that, the rules of the game decide what happens to each cell. That is why, Life is a zero-player game.

The original seed is generation 0. Following the rules, adjust all the cells. Now you are at generation 1. Repeat the process, and you will see how your initial pattern grows, fades away (shrinks or dies) or stabilises in multiple generations.

A stable pattern, can oscillate between two or more forms, or remain in a single form. A stable not oscillating form is a formation of cells that does not need to be changed by these rules unless mixed up with another formation of cells.

An example of a simple stable form is a block of four alive cells. It dos not oscillate between any number of forms.

A basic oscillator is a straight line(original orientation does not matter) made of 3 cells. If the original line is vertical then it switches between a vertical line and a horizontal line. Otherwise it is the reverse. Here is an example of what a line made of three cells does.

Beehive

A beehive is a stable form, but it does not oscillate between any number of forms. It is the second most common still life, the most common being a 2×2 block of alive cells.

A honey farm is made up of four beehives in fixed positions. Here is an image of a honey farm. It is a variety of still life like the 2X2 block and the beehive.

Honeycomb

A honeycomb is also known as a super beehive. If you look at the dead cells inside the honeycomb you will see the shape of a beehive.

R-petromino

The R-petromino is a famous initial cell configuration. It takes 1103 generations to reach a stable formation.

Some other interesting cell structures are super mango, eleven loop, sidewalk, loaf and pond.

History

After starting his work on cellular automaton rules in 1968, it took a year and a half for Conway to make sure the population of cells can reach a stable form and do not die out too fast.

Life first got some publicity when Martian Gardner published an article about it in the Scientific American magazine, in October 1970. Here is the link to Martian Gardner’s publication.

When Conway was making his game, his main goal was to make the game Turing complete and to make a realistic simulation of over population, under population and birth. He also wanted to make the game totally unpredictable.

Conway found in necessary not to allow explosive growth, but he wanted small patterns to slowly become big. While he wanted the rules to have these constraints, he wanted them to be as simple as possible. Hence, after experimenting with various possibilities, he decided upon the current rules.

The game coming into existence at around the same time as inexpensive computers, increased its publicity, as people could run this game for hours and so the computers would be used at night as well as in the day.

Before Conway made the game with these rules he had made another game with rules for sexual reproduction.

Before being published by Martian Gardner, Conway had created cell structures called spaceships. He had discovered three such structures.

Spaceships

The glider is one of the many, many spaceships. A spaceship is a configuration of cells which move forever, in a certain direction, unless they collide with another configuration of cells. The glider is one of the four discovered spaceships that move diagonally. It was discovered by John Conway and his friend Richard K. Guy. This is an example of a glider.

Each time a glide completes one cycle it moves one cell, diagonally.

The glider is a light weight spaceship. That means that it has very few cells. The glider is the most simple spaceship.

My Analysis

I wrote a program which is a simpler version of the Life. It is played on a fixed sized grid so, eventually, spaceships will come out of the grid.

The code does not use user input to generate the original configuration. Instead it uses a random integer generator that has to choose between 0 and 1. If it chooses 0 the cell is dead and shows up black and if it chooses, 1 the cell is alive and shows up white. Although the code generates the initial pattern of dead and alive cells the user chooses the size of the grid, how many generation they want to run the program for and the time delay between two generation. To explain time delay, suppose we chose the time delay as 1 second. Then after 1 second the first generation configuration gets switched to the second generation configuration. Then again after 1 second the second generation configuration will be switched to the third generation configuration and so on.

Then so that all the cells from the original seed have the same number of nearest neighbors the program adds a string of dead cells on each of the 4 sides of the square.

Here is the code

import random
import math
import os
import time
i = 0
j = 0
one = '\u2588'
i = int(i)
j = int(j)
size = input("How big do you want the seed to be? ")
gens = input(" How many generations do you want to run the Game of Life for? ")
speed = input("How much time delay do you want between two generations  ")
speed = float(speed)
gens = int(gens)
size = int(size)
seed = [[0 for i in range(size)] for j in range(size)]
b = [[0 for i in range(size + 2)] for j in range(size + 2)]
sn = [[0 for i in range(size)] for j in range(size)]
nn = [[[0 for k in range(8)] for i in range(size)] for j in range(size)]
for i in range(size):
    for j in range(size):
        seed[i][j] = random.randint(0,1)
for i in range(size):
    for j in range(size):
        print((one + one) if seed[i][j] else '  ', end='')
    print()
print()

def update(seed):

    for j in range(size + 2):
        b[0][j] = 0
        b[size + 1][j] = 0
    for i in range(size + 2):
        b[i][0] = 0
        b[i][size + 1] = 0

    for i in range(size):
        for j in range(size):
            b[i + 1][j + 1] = seed[i][j]

    for i in range(1, size + 1):
        for j in range(1, size + 1):
            nn[i-1][j-1] = [b[i - 1][j - 1], b[i - 1][j], b[i - 1][j + 1], b[i][j - 1], b[i][j + 1], b[i + 1][j - 1], b[i + 1][j], b[i + 1][j + 1]]

    for i in range(size):
        for j in range(size):
            sn[i][j] = sum(nn[i][j])

def game(seed):

I ran the code a few times. In the simulations below, I have chosen the number of generations as 50 and the time delay as 0.2 in seconds.

First I ran it for a small sized grid. It was 20X20 cells big. In the simulations below, I have recorded my terminal screen.

Note: In the program I chose the cell size to be double the size of the cursor, so that it would be a square. However when I recorded it, the cells were split into half by faint coloured lines you might notice if you look very closely. Please ignore the faint coloured lines if you can see them.

Later I did it for a larger sized grid. It was 40X40.

After doing some more research, I found out about a game which is similar to Life, but you can choose the number of rows and columns of nearest neighbors that effect each cell’s evolution. Suppose we choose the number of nearest neighbors as 3. Here is the program.

import random
import math
import os
import time
i = 0
j = 0
one = '\u2588'
i = int(i)
j = int(j)
size = input("The size of the seed has to be at least a few squares more that the amount of nieghbors you want each cell to have. How big do you want the seed to be?   ")
gens = input(" How many generations do you want to run the Game of Life for?   ")
speed = input("How much time delay do you want between two generations in seconds?   ")
n = input("How neighbors do you want each cell to interact with? The number has to be a positive integer between 2 and 10.    ")
n = int(n)
print(f"{math.floor(((2*n+1)*(2*n+1))/4)}")
c = 0
d = 0
e = 0
f = 0
c = int(c)
newn = 0-n
speed = float(speed)
gens = int(gens)
size = int(size)
seed = [[0 for i in range(size)] for j in range(size)]
b = [[0 for i in range(size + 2*n)] for j in range(size + 2*n)]
sn = [[0 for i in range(size)] for j in range(size)]

for i in range(size):
    for j in range(size):
        seed[i][j] = random.randint(0,1)
for i in range(size):
    for j in range(size):
        print((one) if seed[i][j] else '  ', end='')
    print()
print()

def update(seed):

    for i in range(size):
        for j in range(size):
            b[i + n][j + n] = seed[i][j]

    for i in range(n, size + n):
        for j in range(n, size + n):
            for c in range(newn, n+1):
                for d in range(newn, n+1):
                    if c == 0 and d == 0:
                        sn[i-n][j-n] = sn[i-n][j-n]
                    else:
                        sn[i-n][j-n] = sn[i-n][j-n] + b[i+c][j+d]

def game(seed):
    time.sleep(speed)
    os.system('clear')
    for i in range(size):
        for j in range(size):
            if sn[i][j] < math.floor(((2*n+1)*(2*n+1))/8):
                seed[i][j] = 0
            elif 2*math.floor(((2*n+1)*(2*n+1))/8) < sn[i][j] <= 3*math.floor(((2*n+1)*(2*n+1))/8):
                seed[i][j] = 1
            elif sn[i][j] > 3*math.floor(((2*n+1)*(2*n+1))/8):
               seed[i][j] = 0
    for i in range(size):
        for j in range(size):
            print((one + one) if seed[i][j] else '  ', end='')
        print()
    for i in range(size):
        for j in range(size):
            sn[i][j]  = 0
    update(seed)

i = 0
update(seed)
i=1
while i < gens + 1:
    print(f"{i}")
    game(seed)
    i = i + 1

When you run the program, suppose you choose the number of layers of nearest neighbors as 3. Then each cell will have 48 nearest neighbors. They will be arranged like this, three rows of seven cell above the cell in question, three rows of seven cells below the cell in question, three columns of seven cells at the right of the cell in question and three rows of seven cells to the left of the cell in question as shown in the image below.

All the cells outside the square of nearest neighbors are irrelevant while trying to calculate the life or death or birth of the green cell in the figure above.

After the program generates the grid of live and dead cells, it needs to use reasonable rules to decide the future of the cell. In the rules n is the number of layers of neighbors and sn[i][j] is the number of live neighbors in those layers. These are the rules:

  1. If sn[i][j] <= Floor(((2*n+1)*(2*n+1))/8)the cell will die as if by under population. For n = 1, this evaluates to,cells with less than 2 live neighbors, die. Check this against the previous set of rules. For n=3, this evaluates to, cells with less than 7 live neighbors, die.
  2. If Floor(((2*n+1)*(2*n+1))/8) < sn[i][j] <= 2* Floor(((2*n+1)*(2*n+1))/8) the cell will remain the same. For n = 1, this evaluates to, cells with 2 or 3 live neighbors that will remain the same. Check this against the previous set of rules. For n=3, this evaluates to, cells with more than 6 but less than 13 neighbors that remain the same.
  3. if sn[i][j] > 3*Floor(((2*n+1)*(2*n+1))/8) the cell will die as if by over population. For n = 1, this evaluates to, cells with more than 3 live neighbors that will die. Check this against the previous set of rules. For n=3, this evaluates to, cells with more than 18 live neighbors die.

For a cell that is dead:

  1. If 2* Floor(((2*n+1)*(2*n+1))/8) < sn[i][j] <= 3* Floor(((2*n+1)*(2*n+1))/8) will be born as if by reproduction. For n = 1, this evaluates to,cells with 3 live neighbors that will be born. Check this against the previous set of rules. For n=3, this evaluates to,cells with the number of live neighbors between 12 and 18 that survive.

So, now we will see a video example of the program running with 3 layers of nearest neighbors. In this video, I have chosen the matrix size as 50X50, the number of generations as 100 and the time delay as 0.2 seconds. Here is the video.

If you compare the second video and the third video then you will see they are very different. So, just changing the range of interaction (I. e., the number of cells each cell interacts with) we can generate quite a different type of complex pattern.

These simple rules of the Life allow such complex patterns to emerge. That is the most fascinating thing about it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s