This is post is about Langton’s ant. Like
Conway’s game of life, It’s considered Cellular automaton
.
A cellular automaton consists of a regular grid of cells, each in one of a finite number of states, such as on and off (in contrast to a coupled map lattice). The grid can be in any finite number of dimensions. For each cell, a set of cells called its neighborhood is defined relative to the specified cell. An initial state (time t = 0) is selected by assigning a state for each cell. A new generation is created (advancing t by 1), according to some fixed rule (generally, a mathematical function)[3] that determines the new state of each cell in terms of the current state of the cell and the states of the cells in its neighborhood.
For me, I just like the idea of emerging patters from set of rules and an initial random state. And most importantly watching the animation playing :)
The rules are simple(from wiki):
Squares on a plane are colored variously either black or white. We arbitrarily identify one square as the “ant”. The ant can travel in any of the four cardinal directions at each step it takes. The “ant” moves according to the rules below:
At a white square, turn 90° clockwise, flip the color of the square, move forward one unit At a black square, turn 90° counter-clockwise, flip the color of the square, move forward one unit
Implementation Link to heading
I used the same infrastructure i did for Conway’s game. So,I just needed to write the generation
Two implementation details:
-
Ant state: I wrote this after midnight, So I went for the simplest representation for the ant state. A tuple with (x,y, direction) where direction is 0 to 3 for up, right, down and left.
-
Boundary movement: I made a assumption that ant moves beyond the boundary, it show up on the other side(implemented with
calc_ij
)
def calc_ij(N, i , j):
ii = i % N
jj = j % N
return (ii, jj)
def rotate_ant(self,rot):
i , j , dir = self.ant
if rot == "CW":
new_dir = (dir + 1) % 4
if rot == "ACW":
new_dir = (dir - 1) % 4
self.ant = (i,j,new_dir)
def move_ant(self):
i , j , dir = self.ant
if dir == 0:
ii, jj = calc_ij(self.N, i-1,j)
if dir == 1:
ii, jj = calc_ij(self.N, i,j+1)
if dir == 2:
ii, jj = calc_ij(self.N, i+1,j)
if dir == 3:
ii, jj = calc_ij(self.N, i,j-1)
self.ant = (ii, jj , dir)
and f-ant-astic animation ready to go.
putting it all together Link to heading
import argparse
import random
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.colors import ListedColormap
def calc_ij(N, i , j):
ii = i % N
jj = j % N
return (ii, jj)
class Game():
def __init__(self, N=10, G=1, shape='random'):
self.N = N
self.G = G
self.grid = None
self.ant = (random.randint(0, N-1),random.randint(0, N-1), random.randint(0, 3)) # TODO randomize
if shape == 'random':
self.grid = np.random.choice(a=[False, True], size=(N, N))
elif shape == 'allw':
self.grid = np.zeros((N, N), dtype='bool')
def rotate_ant(self,rot):
i , j , dir = self.ant
if rot == "CW":
new_dir = (dir + 1) % 4
if rot == "ACW":
new_dir = (dir - 1) % 4
self.ant = (i,j,new_dir)
def move_ant(self):
i , j , dir = self.ant
if dir == 0:
ii, jj = calc_ij(self.N, i-1,j)
if dir == 1:
ii, jj = calc_ij(self.N, i,j+1)
if dir == 2:
ii, jj = calc_ij(self.N, i+1,j)
if dir == 3:
ii, jj = calc_ij(self.N, i,j-1)
self.ant = (ii, jj , dir)
def step(self):
i, j , _ = self.ant
print("before", self.ant)
print(self.grid[i,j])
print(self.grid)
if self.grid[i,j] == 0:
self.rotate_ant("CW")
self.grid[i,j] = 1
self.move_ant()
elif self.grid[i,j] == 1:
self.rotate_ant("ACW")
self.grid[i,j] = 0
self.move_ant()
else:
print("error")
print("After", self.ant)
print(self.grid[i,j])
print(self.grid)
return self.grid
def generations(self):
for g in range (self.G):
yield self.step()
def run(self):
fig, ax = plt.subplots()
mat = ax.matshow(self.grid,cmap=ListedColormap(['w', 'k']))
ani = animation.FuncAnimation(fig, mat.set_data, self.generations, interval=1000, repeat=False)
plt.show()
def main():
parser = argparse.ArgumentParser(description='Langton Ant')
parser.add_argument('N', type=int, help='Grid Size')
parser.add_argument('G', type=int, help='Generations Count')
args = parser.parse_args()
g = Game(args.N,args.G, 'random')
g.run()
if __name__ == "__main__":
main()