This is a generalisation of langton's ant and probably a specialisation of turmites.
An ant lives in a world where each cell in the grid has space for food on multiple planes.
The ant has a position in x and y as well as the plane it's currently on. It also has a direction in N, E, S or W.
When the ant encounters a field, it will put down a piece of food if there's space left, or eat all food if there's no space left.
If the ant ate food, it will go down a plane. If it was already on the lowest plane, it will go back to the uppermost plane.
If it put down a piece of food, it will count the food that's now in the space. If the number is even, it will turn right, otherwise left. Then it will go one field forwards.
A palette is automatically generated by taking evenly spaced hue values for each plane and displaying more food with a higher value of that hue mixed into the whole color.
The MultiPlaneAnt constructor takes a numpy dtype that specifies the intersection of a field into subcells.
The dtype will automatically be padded to fit a power of two.
planes_and_values is a dictionary from the name of a subcell to the allowed values as a list.
If no cell names are given in the original dtype, numpy will assign f0, f1, ... to them by itself.
The shape parameter specifies the size of the world and if random is True, the world will start with food distributed throughout all cells and planes.
In [15]:
import numpy as np
from zasim import simulator
from zasim.subcell import pad_dtype, analyze_dtype, reinterpret_as_integers, uninterpret
from zasim.config import RandomConfigurationFromPalette
def cycle_in_arr(needle, haystack):
position_now = haystack.index(needle)
return (haystack + haystack[:1])[position_now + 1]
movement = dict(north=( 0,-1),
east =( 1, 0),
south=( 0, 1),
west =(-1, 0))
directions = "north east south west".split(" ")
turn_right = {old:cycle_in_arr(old, directions) for old in directions}
turn_left = {old:cycle_in_arr(old, list(reversed(directions))) for old in directions}
from zasim.display.qt import make_multiaxis_palette, make_palette_32, normalize_palette
class MultiPlaneAnt(simulator.SimulatorInterface):
def __init__(self, arrdtype, planes_and_values, shape=(5, 5), random=False):
super(MultiPlaneAnt, self).__init__()
self.planes = dict(planes_and_values)
# first, a lot of boilerplate to generate a palette
padded_dtype, extra_values = pad_dtype(arrdtype)
pav = dict(self.planes)
pav.update(extra_values)
self.possible_values = list(analyze_dtype(padded_dtype, pav))
values_with_hues = []
part_of_circle = 360. / len(self.planes)
for idx in range(len(self.planes)):
value = self.planes[arrdtype.descr[idx][0]]
values_with_hues.append((value, idx * part_of_circle))
raw_palette_qc = normalize_palette(make_multiaxis_palette(values_with_hues))
# the key tuples must have the padding in them.
raw_palette_qc = {((0,) * len(extra_values)) + key: value for key, value in raw_palette_qc.iteritems()}
print raw_palette_qc
raw_palette = make_palette_32(raw_palette_qc)
fixed_palettes = [{reinterpret_as_integers(np.array(key, dtype=padded_dtype)).item(): color
for key, color in raw_palette.iteritems()} for raw_palette in [raw_palette_qc, raw_palette]]
self.palette_info = dict(qcolors=fixed_palettes[0], colors32=fixed_palettes[1])
if random:
self.conf = RandomConfigurationFromPalette(self.possible_values).generate(shape, dtype=padded_dtype)
else:
self.conf = RandomConfigurationFromPalette(self.possible_values[:1]).generate(shape, dtype=padded_dtype)
self.integer_view = reinterpret_as_integers(self.conf)
self.shape = shape
self.ant_x = shape[0] / 2
self.ant_y = shape[1] / 2
self.ant_dir = "north"
self.ant_plane = self.planes.keys()[0]
print self.planes, "are the planes"
self.next_plane = {old: cycle_in_arr(old, self.planes.keys()) for old in self.planes.keys()}
def step(self):
# save the current position to updateinfo as a rectangle so that the painter
# can paint our configuration faster
self.changeinfo = (self.ant_x, self.ant_y, 1, 1)
# the first step is to advance the state at this space to the next one
# what's the current field's value on the plane our ant is on?
curr_state = self.conf[self.ant_x, self.ant_y][self.ant_plane]
# advance it by one and turn it back to the first one if it's gone over the end.
new_state = cycle_in_arr(curr_state, self.planes[self.ant_plane])
# set the state now.
self.conf[self.ant_x, self.ant_y][self.ant_plane] = new_state
# have we reached the first one again? XXX this will only work if plane values are growing monotonously
if new_state < curr_state:
# if so, change planes.
self.ant_plane = self.next_plane[self.ant_plane]
else:
# depending on the value we are sitting on, turn left or right.
if new_state % 2 == 0:
self.ant_dir = turn_right[self.ant_dir]
else:
self.ant_dir = turn_left[self.ant_dir]
antmove = movement[self.ant_dir]
self.ant_x, self.ant_y = self.ant_x + antmove[0], self.ant_y + antmove[1]
self.ant_x, self.ant_y = self.ant_x % self.shape[0], self.ant_y % self.shape[1]
#print self.conf[self.ant_x, self.ant_y]
#print self.integer_view[self.ant_x, self.ant_y]
self.updated.emit()
def get_config(self):
return self.integer_view.copy()
In [16]:
sim = MultiPlaneAnt(np.dtype([("f0", "int8"), ("f1", "int8"), ("f2", "int16")]),
dict(f0 =[0, 1, 2, 3],
f1 =[4, 5],
f2 =[6, 7, 8]),
shape=(200, 200))
In [17]:
from zasim.gui.displaywidgets import DisplayWidget
from zasim.gui.control import ControlWidget
dsp = DisplayWidget(sim)
dsp.set_scale(3)
cnt = ControlWidget(sim)
from PySide.QtGui import QDialog, QVBoxLayout
window = QDialog()
window.setWindowTitle("Langton's Ant on multiple planes")
l = QVBoxLayout()
l.addWidget(dsp)
l.addWidget(cnt)
window.setLayout(l)
window.show()
# wait for the window to be closed
window.exec_()
# then shut everything down
cnt.stop()
del window
del dsp
del cnt
del sim