Langtons Ant
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.