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 160×120 squares, with squares that are 4×4 pixels (so that it fits inside a 640×480 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

7 Responses to “Langton Ants in PyGame”

  1. James Paige Says:

    > …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? :)

  2. K Says:

    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)

  3. Will Says:

    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.

  4. Niki Says:

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

  5. Will Says:

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

  6. jorge vargas Says:

    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.

  7. Will Says:

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

Leave a Reply


Close
E-mail It
Socialized through Gregarious 42