May 18, 2007 will

Langton Ants in PyGame

Many moons ago I implemented Langton Ants on a ZX Spectrum 48K, and I have been fascinated by it ever since. I thought it would be fun to implement it in PyGame.

Langton ants are simple creatures. They live in a grid of squares that can be one of two colors, and follow these two simple rules.

  • Am I on color 1? Flip the square, turn 90 degrees right, move forward 1 square.
  • Am I on color 2? Flip the square, turn 90 degrees left, move forward 1 square.

That is all they do. They don't ponder the meaning of life or current affairs, they just check the color of the square they are on and then turn and move. You would think that something this simple would quickly get in to a cyclical pattern, but it turns out that Langton ants like to make crazy complex patterns and don't repeat themselves. Humor me, while I write a Python script to test it.

First thing we need to do is define a few constants for use in the script, as follows.

GRID_SIZE = (160, 120)
GRID_SQUARE_SIZE = (4, 4)
ITERATIONS = 1
ant_image_filename = "ant.png"

The grid will be 160x120 squares, with squares that are 4x4 pixels (so that it fits inside a 640x480 pixel screen). The value of ITERATIONS is the number of moves an ant does each frame, increase it if you want the ants to move faster. Finally we have the filename of an image to represent the ant. I will be using this image: Ant.

Next we import PyGame in the usual manner.

import pygame
from pygame.locals import *

We will represent the grid with a class that contains a two dimensional list (actually a list of lists) of bools, one for each square, where False is color 1, and True is color 2. The AntGrid class is also responsible for clearing the grid, getting the square color at a coordinate, flipping a square color and drawing itself to a PyGame surface. I chose white for color 1 and dark green for color 2, but feel free to change it if you want something different.

class AntGrid(object):

    def __init__(self, width, height):

        self.width = width
        self.height = height
        self.clear()

    def clear(self):

        self.rows = []
        for col_no in xrange(self.height):
            new_row = []
            self.rows.append(new_row)
            for row_no in xrange(self.width):
                new_row.append(False)

    def swap(self, x, y):
        self.rows[y][x] = not self.rows[y][x]

    def get(self, x, y):
        return self.rows[y][x]

    def render(self, surface, colors, square_size):

        w, h = square_size
        surface.fill(colors[0])

        for y, row in enumerate(self.rows):
            rect_y = y * h
            for x, state in enumerate(row):
                if state:
                    surface.fill(colors[1], (x * w, rect_y, w, h))

Now that we have a grid, we can create a class for the ants. The move function in the Ant class implements the two rules, and the render function draws a sprite at the ants location so we can see where it is.

class Ant(object):

    directions = ( (0,-1), (+1,0), (0,+1), (-1,0) )

    def __init__(self, grid, x, y, image, direction=1):

        self.grid = grid
        self.x = x
        self.y = y
        self.image = image
        self.direction = direction

    def move(self):

        self.grid.swap(self.x, self.y)

        self.x = ( self.x + Ant.directions[self.direction][0] ) % self.grid.width
        self.y = ( self.y + Ant.directions[self.direction][1] ) % self.grid.height

        if self.grid.get(self.x, self.y):
            self.direction = (self.direction-1) % 4
        else:
            self.direction = (self.direction+1) % 4

    def render(self, surface, grid_size):

        grid_w, grid_h = grid_size
        ant_w, ant_h = self.image.get_size()
        render_x = self.x * grid_w - ant_w / 2
        render_y = self.y * grid_h - ant_h / 2
        surface.blit(self.image, (render_x, render_y))

Finally in the script, the run function handles the guts of the simulation. It sets up the screen, creates the the grid object then enters the main loop. Inside the main loop there are event handlers so that you can drop ants with the left mouse button, clear the grid with the C key and start the simulation with the SPACE key. The remaining portion of the run function moves all the ants and renders the screen.

def run():

    pygame.init()
    w = GRID_SIZE[0] * GRID_SQUARE_SIZE[0]
    h = GRID_SIZE[1] * GRID_SQUARE_SIZE[1]
    screen = pygame.display.set_mode((w, h), 0, 32)

    ant_image = pygame.image.load(ant_image_filename).convert_alpha()

    default_font = pygame.font.get_default_font()
    font = pygame.font.SysFont(default_font, 22)

    ants = []
    grid = AntGrid(*GRID_SIZE)
    running = False

    total_iterations = 0

    while True:

        for event in pygame.event.get():

            if event.type == QUIT:
                return

            if event.type == MOUSEBUTTONDOWN:

                x, y = event.pos
                x /= GRID_SQUARE_SIZE[0]
                y /= GRID_SQUARE_SIZE[1]

                ant = Ant(grid, int(x), int(y), ant_image)
                ants.append(ant)

            if event.type == KEYDOWN:

                if event.key == K_SPACE:
                    running = not running

                if event.key == K_c:
                    grid.clear()
                    total_iterations = 0
                    del ants[:]

        grid.render(screen, ((255, 255, 255), (0, 128, 0)), GRID_SQUARE_SIZE)

        if running:
            for iteration_no in xrange(ITERATIONS):
                for ant in ants:
                    ant.move()
            total_iterations += ITERATIONS

        txt = "%i iterations"%total_iterations
        txt_surface = font.render(txt, True, (0, 0, 0))
        screen.blit(txt_surface, (0, 0))

        for ant in ants:
            ant.render(screen, GRID_SQUARE_SIZE)

        pygame.display.update()

if __name__ == "__main__":
    run()

And thats our finished Langton Ant simulation. Have a play with it, then come back and explain to me how two simple rules can create such a complex pattern.

Download langtonants.zip

Update: Here's a screenshot!

Langton's Ants screenshot

Use Markdown for formatting
*Italic* **Bold** `inline code` Links to [Google](http://www.google.com) > This is a quote > ```python import this ```
your comment will be previewed here
gravatar
James Paige

> ...explain to me how two simple rules can create such a complex pattern.

because the rules don't exist in a vacuum. They exist in an enviromnent where the previous results of the rules continue to matter. These two simple rules could only produce a simple result if the whole playing field was wiped clean on every iteration.

Or was that question rhetorical? :)

gravatar
K

I modified the code to put 1000 ants on the screen just to see what happened. Interestingly only some of the ants made real patterns. Most just "wiggled" a bit, but mostly stayed on the spot?

for i in range(1,10000,10):
ant = Ant(grid, int(x) + (i % 80), int(y) + (i % 56), ant_image)
ants.append(ant)

gravatar
Will

I think that would put ants directly on top of each other, i.e on the same cell, which causes a much simpler pattern. If you place two ants on the same cell with the original code, you'll see the same thing happens. Its only when there are an odd number of ants that you get them making patterns.

gravatar
Niki

What's the meaning of 90% left/right?
From code it seems like 100% left/right.

gravatar
Will

D'oh! That should be 90 degrees left/right. Thanks for pointing it out.

gravatar
jorge vargas

Well this seems to be a simplification of Conwa'ys "Game Of Life" http://en.wikipedia.org/wiki/Conway's_Game_of_Life

as James Paige said it's patterns are like that because of the chain of events.

Anyway a nice example on pygames.

PS: a question, which syntax highligting are you using? currently all the ones I have found for wordpress suck.

gravatar
Will

I'm using 'iG:Syntax Hiliter', which does suck because it messes things up when you switch between the wysiwyg and the code editor. :-(

gravatar
alecwh

Hey, cool script. I was researching this phenomenon on Wikipedia, which linked here for a python script. Thanks, it worked wonderfully!

After messing with it, I decided to have some fun with it.

gravatar
alecwh

Also, would you recommend using pygame for projects like this? How did this go?