Gosper glider gun

Conway’s game of life implemented into python

All the python code for this game of life project can be downloaded here.

What is the game of life?

The game of life was invented  in 1970 by John Horton Conway, a British mathematician. Conway had developed an interest in the American-Hungarian John von Neumann’s work aiming to find a machine that could create identical copies of itself. John von Neumann succeeded in creating such a machine virtually using complex cellular automaton. The game of life was just Conway’s way of simplifying Neuman’s ideas.

Rules of the game of life

The game of life consists of a theoretically infinite grid with each cell that can be in two possible states: Alive or Dead. The game is designed around four simple rules as follow:

  • Any live cell with under 2 live neighbours dies from exposure
  • Any live cell with over 3 live neighbours dies from overpopulation
  • Any live cell with 2 or 3 live neighbours lives
  • Any dead cell with exactly 3 live neighbours comes to life

The game works step by step and every step the four rules are applied to every single cell in the grid simultaneously.

Interesting structures

There are 4 categories of structures in the game of life: still lifesoscillatorsspaceships and guns.

Still lifes

 

Oscillators

Spaceships

Guns

 

gosper glider gun

Gosper glider gun

 

Logic gates in the game of life

One of the most interesting aspects of the game of life is that it is Turing complete, meaning that it can theoretically simulate a Turing machine: a machine invented by Alan Turing which can execute any computer algorithm!

This means that the game of life can implement the three fundamental logic gates which allow any computer to run: andor and not. Here is a brief explanation of what these logic gates do:

It is surprisingly simple to implement these logic gates in the game of life. This video by Alex Bellos does a really good job in explaining them:

My favourite use of this Turing completeness is this one: A game of life coded by the game of life itself. Game of life-ception!

Now that we’ve seen how the game of life works and what you can use it for it’s time to create our own version of the game of life in python!

Creating our own game of life!

Our game of life will be divided into two files: GameofLife.py and GameofLifeGUI.py.

GameofLife.py will be the environment class meaning that it will:

  • Hold the grid
  • Update the grid
  • Have all the functions for manipulating the grid like:
    • Clearing the grid
    • Filling the grid
    • Randomising the grid

GameofLifeGUI.py will handle the front end it will:

  • Draw the grid
  • Get the user’s input and act upon it

Game environment

Our environment will be stored in a class called universe which will have the following methods:

  • __init__()  to initialise the universe
  • __str__() to give a string representation of the grid
  • generate_grid() to create the grid
  • evolve() to update a particular cell according to the rules of the game
  • step() to apply evolve() to every cell on the grid
  • toggle_state() to invert the state of a particular cell
  • set_cell() to set the state of a particular cell
  • clear() to set every cell to the dead state
  • fill() to set every cell to the alive state
  • random() to randomise every cell on the grid
  • invert() to toggle the state of every cell on the grid

Here is the game environment:

from copy import deepcopy
from random import randint

class universe(object):
    def <strong>init</strong>(self, grid=None, steps=0):
        """ Intitialise the universe with the passed grid or an empty one """
        self.steps = steps
        if grid:
            self.grid = deepcopy(grid)
            self.width = len(grid)
            self.height = len(grid[0])
        else:
            self.generate_grid(40, 20)

<pre><code>def generate_grid(self, width, height):
    &quot;&quot;&quot; Create an empty grid of size: width*height &quot;&quot;&quot;
    self.width = width
    self.height = height
    self.grid = [[0 for row in range(height)] for column in range(width)]

def __str__(self):
    &quot;&quot;&quot; String representation of the grid &quot;&quot;&quot;
    string = ''.join(['-' for i in range(self.width)]) + '\n' # List comprehension to string
    for row in range(self.height):
        for column in range(self.width):
            cell = self.grid[column][row]
            if cell == 0:
                string += ' '
            else:
                string += '|'
        string += '\n'

    string += '\n' + ''.join(['-' for i in range(self.width)])
    return string

def step(self):
    &quot;&quot;&quot; apply evolve() to every cell on the grid &quot;&quot;&quot;
    updated = False
    game = universe(self.grid, self.steps+1)

    for row in range(self.height):
        for column in range(self.width):
            live_neighbours = 0
            if row &gt; 0:
                live_neighbours += self.grid[column][row-1] # Check on top
                if column &gt; 0:
                    live_neighbours += self.grid[column-1][row-1] # Check top left
                if column &lt; self.width-1:
                    live_neighbours += self.grid[column+1][row-1] # Check top right
            if row &lt; self.height-1: live_neighbours += self.grid[column][row+1] # Check at bottom if column &gt; 0:
                    live_neighbours += self.grid[column-1][row+1] # Check bottom left
                if column &lt; self.width-1: live_neighbours += self.grid[column+1][row+1] # Check bottom right if column &gt; 0:
                live_neighbours += self.grid[column-1][row] # Check left
            if column &lt; self.width-1:
                live_neighbours += self.grid[column+1][row] # Check right

            if game.evolve(column, row, live_neighbours):
                updated = True

    self.grid = game.grid
    self.steps += 1
    return updated

def evolve(self, column, row, live_neighbours):
    updated = False
    state = self.grid[column][row]
    #print(live_neighbours)
    if live_neighbours not in [2,3] and state != 0: # Exposure and overpopulation
        self.grid[column][row] = 0
        updated = True
    elif live_neighbours == 3 and state != 1:
        self.grid[column][row] = 1
        updated = True
    return updated


def toggle_state(self, column, row):
    &quot;&quot;&quot; invert the state of a particular cell&quot;&quot;&quot;
    self.grid[column][row] = 1 - self.grid[column][row] # Change between 0-1 and 1-0

def set_cell(self, column, row, state):
    &quot;&quot;&quot; set the state of a particular cell&quot;&quot;&quot;
    self.grid[column][row] = state

def clear(self):
    self.grid = [[0 for row in range(self.height)] for column in range(self.width)]
    self.steps = 0

def fill(self):
    self.grid = [[1 for row in range(self.height)] for column in range(self.width)]
    self.steps = 0

def random(self):
    self.grid = [[randint(0, 1) for row in range(self.height)] for column in range(self.width)]
    self.steps = 0

def invert(self):
    self.grid = [[1 - self.grid[column][row] for row in range(self.height)] for column in range(self.width)]
    self.steps = 0
</code></pre>

if <strong>name</strong> == '<strong>main</strong>':
    game = universe()
    game.random()
    print(game)

I think that all the code is self-explanatory. Leave a comment if you require a deeper explanation

Graphical User Interface

Our GUI will be stored in a class called GUI (how original) which will have 5 methods:

  • __init__() to start the universe and initialise pygame
  • process_inputs() to process the user inputs and act accordingly
  • update() to update the game state
  • draw() to draw everything to the screen
  • loop() to call the three methods

from GameofLife import universe
import pygame
from pygame.locals import *
import time
from copy import deepcopy

class GUI(object):
    def <strong>init</strong>(self):
        self.universe = universe()
        width = int(input("Grid width: "))
        height = int(input("Grid height: "))
        self.cell_size = int(input("Cell size: "))
        self.universe.generate_grid(width,height)

<pre><code>    pygame.init()
    self.screen = pygame.display.set_mode((self.universe.width*(self.cell_size+1)-1, self.universe.height*(self.cell_size+1)-1))
    pygame.display.set_caption('Conway\'s game of life Step: {}'.format(self.universe.steps))
    self.redraw = True
    self.end = False

    self.last_time = time.time()
    self.speed = 0.1 # Time between each update in seconds

    self.pause = True
    self.old_grid = deepcopy(self.universe.grid)

def loop(self):
    &quot;&quot;&quot; Screen loop &quot;&quot;&quot;
    print('[+] Beginning loop')
    while not self.end:
        self.process_inputs()
        if not self.pause:
            self.update()
        if self.redraw:
            self.draw()
            self.redraw = False
    print('[-] Loop ended')
    pygame.quit()

def process_inputs(self):
    &quot;&quot;&quot; Process all user inputs &quot;&quot;&quot;
    for event in pygame.event.get():
        #print('[~] Processing inputs')
        if event.type == QUIT:
            print('[-] Ending loop')
            self.end = True
        elif event.type == MOUSEBUTTONDOWN:
            if event.button == 1 and self.pause == True: # Left click and paused
                print('[~] Left click')
                x, y = event.pos
                x = int(x/(self.cell_size+1))
                y = int(y/(self.cell_size+1))
                self.universe.toggle_state(x,y)
                self.redraw = True
                print('[+] Toggled cell at x: ',x,', y: ',y)
            elif event.button == 3: # Right click
                print('[~] Right click')
                self.pause = not self.pause
                print('[+] Toggled pause state')
            elif event.button == 5: # Scroll down
                print('[~] Scroll down')
                self.speed += 0.1 # Slow down
                print('[+] Slowed down')
            elif event.button == 4: # Scroll up
                print('[~] Scroll down')
                self.speed -= 0.1 # Speed up
                if self.speed &lt; 0: self.speed = 0 print('[+] Speeded up') elif event.type == KEYDOWN: if event.key == pygame.K_c and self.pause == True:#Clear print('[~] Pressed C') self.universe.clear() print('[+] Cleared universe') self.redraw = True elif event.key == pygame.K_f and self.pause == True: #Fill print('[~] Pressed F') self.universe.fill() print('[+] Filled universe') self.redraw = True elif event.key == pygame.K_n and self.pause == True: #Next step print('[~] Pressed N') self.update() print('[+] Next step') elif event.key == pygame.K_r and self.pause == True: #Random print('[~] Pressed R') self.universe.random() self.redraw = True print('[+] Randomised grid') elif event.key == pygame.K_i and self.pause == True: #Invert print('[~] Pressed I') self.universe.invert() self.redraw = True print('[+] Inverted grid') def update(self): &quot;&quot;&quot; Update environment &quot;&quot;&quot; current_time = time.time() if current_time - self.last_time &gt;= self.speed:
        #print('[~] Updating environment')
        self.last_time = current_time
        if self.universe.step():
            pygame.display.set_caption('Conway\'s game of life Step: {}'.format(self.universe.steps))
            self.redraw = True

        if self.universe.grid == self.old_grid: # Check if the grid is at a standstill
            print('[+] Paused state: universe is stable')
            self.pause = True
        self.old_grid = self.universe.grid
        #print('[+] Environment updated')

def draw(self):
    &quot;&quot;&quot; Display the environment &quot;&quot;&quot;
    #print('[~] Updating screen')
    # Fill the screen
    self.screen.fill((0,0,0))
    # Draw universe
    for row in range(self.universe.height):
        for column in range(self.universe.width):
            x = column*self.cell_size+column
            y = row*self.cell_size+row
            if self.universe.grid[column][row] == 0:
                colour = (150,150,150)
            elif self.universe.grid[column][row] == 1:
                colour = (100,100,200)
            pygame.draw.rect(self.screen, colour, (x,y,self.cell_size,self.cell_size), 0)
    pygame.display.update()
    #print('[+] Screen updated')
</code></pre>

if <strong>name</strong> == '<strong>main</strong>':
    gui = GUI()
    gui.loop()

Again, the code is self-explanatory. I will not delve into pygame but if you want to learn how to use pygame I really recommend reading More Python Programming by Jonathan S. Harbour or if you don’t feel like buying a book, this nerdprogramming.com tutorial is good as well as this thepythongamebooks.com’s tutorial. If you already know SFML for C++ I think that you will find that pygame is very similar.

By running GameofLifeGUI.py and specifying the grid dimensions you should get a window that looks something like this:

Conway's game of life empty board

As always please show your support by leaving a comment.

 

Leave a Reply