Langtons Ant

Tags: large, artistic, bext, simulation

Langton’s Ant is a cellular automata simulation on a two-dimensional grid, similar to Project 13, “Conway’s Game of Life.” In the simulation, an “ant” begins on a square that is one of two colors. If the space is the first color, the ant switches it to the second color, turns 90 degrees to the right, and moves forward one space. If the space is the second color, the ant switches it to the first color, turns 90 degrees to the left, and moves forward one space. Despite the very simple set of rules, the simulation displays complex emergent behavior. Simulations can feature multiple ants in the same space, causing interesting interactions when they cross paths with each other. Langton’s Ant was invented by computer scientist Chris Langton in 1986. More information about Langton’s Ant can be found at https://en.wikipedia.org/wiki/Langton%27s_ant.

This program uses two senses of “direction.” On the one hand, the dictionaries that represent each ant store cardinal directions: north, south, east, and west. However, turning left or right (or counterclockwise and clockwise, since we are viewing the ants from above) is a rotational direction. Ants are supposed to turn left or right in response to the tile they’re standing on, so lines 78 to 100 set a new cardinal direction based on the ant’s current cardinal direction and the direction they are turning.

langtons_ant.py
  1"""Langton's Ant, by Al Sweigart al@inventwithpython.com
  2A cellular automata animation. Press Ctrl-C to stop.
  3More info: https://en.wikipedia.org/wiki/Langton%27s_ant
  4This code is available at https://nostarch.com/big-book-small-python-programming
  5Tags: large, artistic, bext, simulation"""
  6
  7import copy, random, sys, time
  8
  9try:
 10    import bext
 11except ImportError:
 12    print('This program requires the bext module, which you')
 13    print('can install by following the instructions at')
 14    print('https://pypi.org/project/Bext/')
 15    sys.exit()
 16
 17# Set up the constants:
 18WIDTH, HEIGHT = bext.size()
 19# We can't print to the last column on Windows without it adding a
 20# newline automatically, so reduce the width by one:
 21WIDTH -= 1
 22HEIGHT -= 1  # Adjustment for the quit message at the bottom.
 23
 24NUMBER_OF_ANTS = 100 # (!) Try changing this to 1 or 50.
 25PAUSE_AMOUNT = 0.1  # (!) Try changing this to 1.0 or 0.0.
 26
 27# (!) Try changing these to make the ants look different:
 28ANT_UP = '^'
 29ANT_DOWN = 'v'
 30ANT_LEFT = '<'
 31ANT_RIGHT = '>'
 32
 33# (!) Try changing these colors to one of 'black', 'red', 'green',
 34# 'yellow', 'blue', 'purple', 'cyan', or 'white'. (These are the only
 35# colors that the bext module supports.)
 36ANT_COLOR = 'red'
 37BLACK_TILE = 'green'
 38WHITE_TILE = 'black'
 39
 40NORTH = 'north'
 41SOUTH = 'south'
 42EAST = 'east'
 43WEST = 'west'
 44
 45
 46def main():
 47    bext.fg(ANT_COLOR)  # The ants' color is the foreground color.
 48    bext.bg(WHITE_TILE)  # Set the background to white to start.
 49    bext.clear()
 50
 51    # Create a new board data structure:
 52    board = {'width': WIDTH, 'height': HEIGHT}
 53
 54    # Create ant data structures:
 55    ants = []
 56    for i in range(NUMBER_OF_ANTS):
 57        ant = {
 58            'x': random.randint(0, WIDTH - 1),
 59            'y': random.randint(0, HEIGHT - 1),
 60            'direction': random.choice([NORTH, SOUTH, EAST, WEST]),
 61        }
 62        ants.append(ant)
 63
 64    # Keep track of which tiles have changed and need to be redrawn on
 65    # the screen:
 66    changedTiles = []
 67
 68    while True:  # Main program loop.
 69        displayBoard(board, ants, changedTiles)
 70        changedTiles = []
 71
 72        # nextBoard is what the board will look like on the next step in
 73        # the simulation. Start with a copy of the current step's board:
 74        nextBoard = copy.copy(board)
 75
 76        # Run a single simulation step for each ant:
 77        for ant in ants:
 78            if board.get((ant['x'], ant['y']), False) == True:
 79                nextBoard[(ant['x'], ant['y'])] = False
 80                # Turn clockwise:
 81                if ant['direction'] == NORTH:
 82                    ant['direction'] = EAST
 83                elif ant['direction'] == EAST:
 84                    ant['direction'] = SOUTH
 85                elif ant['direction'] == SOUTH:
 86                    ant['direction'] = WEST
 87                elif ant['direction'] == WEST:
 88                    ant['direction'] = NORTH
 89            else:
 90                nextBoard[(ant['x'], ant['y'])] = True
 91                # Turn counter clockwise:
 92                if ant['direction'] == NORTH:
 93                    ant['direction'] = WEST
 94                elif ant['direction'] == WEST:
 95                    ant['direction'] = SOUTH
 96                elif ant['direction'] == SOUTH:
 97                    ant['direction'] = EAST
 98                elif ant['direction'] == EAST:
 99                    ant['direction'] = NORTH
100            changedTiles.append((ant['x'], ant['y']))
101
102            # Move the ant forward in whatever direction it's facing:
103            if ant['direction'] == NORTH:
104                ant['y'] -= 1
105            if ant['direction'] == SOUTH:
106                ant['y'] += 1
107            if ant['direction'] == WEST:
108                ant['x'] -= 1
109            if ant['direction'] == EAST:
110                ant['x'] += 1
111
112            # If the ant goes past the edge of the screen,
113            # it should wrap around to other side.
114            ant['x'] = ant['x'] % WIDTH
115            ant['y'] = ant['y'] % HEIGHT
116
117            changedTiles.append((ant['x'], ant['y']))
118
119        board = nextBoard
120
121
122def displayBoard(board, ants, changedTiles):
123    """Displays the board and ants on the screen. The changedTiles
124    argument is a list of (x, y) tuples for tiles on the screen that
125    have changed and need to be redrawn."""
126
127    # Draw the board data structure:
128    for x, y in changedTiles:
129        bext.goto(x, y)
130        if board.get((x, y), False):
131            bext.bg(BLACK_TILE)
132        else:
133            bext.bg(WHITE_TILE)
134
135        antIsHere = False
136        for ant in ants:
137            if (x, y) == (ant['x'], ant['y']):
138                antIsHere = True
139                if ant['direction'] == NORTH:
140                    print(ANT_UP, end='')
141                elif ant['direction'] == SOUTH:
142                    print(ANT_DOWN, end='')
143                elif ant['direction'] == EAST:
144                    print(ANT_LEFT, end='')
145                elif ant['direction'] == WEST:
146                    print(ANT_RIGHT, end='')
147                break
148        if not antIsHere:
149            print(' ', end='')
150
151    # Display the quit message at the bottom of the screen:
152    bext.goto(0, HEIGHT)
153    bext.bg(WHITE_TILE)
154    print('Press Ctrl-C to quit.', end='')
155
156    sys.stdout.flush()  # (Required for bext-using programs.)
157    time.sleep(PAUSE_AMOUNT)
158
159
160# If this program was run (instead of imported), run the game:
161if __name__ == '__main__':
162    try:
163        main()
164    except KeyboardInterrupt:
165        print("Langton's Ant, by Al Sweigart al@inventwithpython.com")
166        sys.exit()  # When Ctrl-C is pressed, end the program.

https://inventwithpython.com/bigbookpython/project39.html