#!/usr/bin/python import random from math import * from Gui import * class World(Gui): """A World is a Gui that has animals and an interpreter. the World class provides method for interacting with animals that live in the world""" def __init__(self): Gui.__init__(self) self.exists = True self.animals = [] self.inter = Interpreter(self, top_globals, top_locals) def register(self, animal): "add a new animal to the list" self.animals.append(animal) def unregister(self, animal): "remove an animal from the list" self.animals.remove(animal) def clear(self): "remove all animals from the list and clear the canvas" for animal in self.animals: animal.undraw() self.animals = [] self.canvas.delete('all') def step(self): "move all the animals one step" for animal in self.animals: animal.step() def run(self): "keep moving the animals until someone presses Stop or Quit" self.running = 1 while self.exists and self.running: self.step() self.update() time.sleep(self.delay) def stop(self): "pressing Stop sets running to False" self.running = False def quit(self): "pressing Qut sets exists to False and exits" self.running = False self.exists = False Gui.quit(self) sys.exit() def map_animals(self, callable): "apply the given callable to all the animals" map(callable, self.animals) def run_text(self): """get the contents of the text entry and execute them in the interpreter""" source = self.te_code.get(1.0, END) self.inter.run_code(source, '') def run_file(self): """get a filename from the entry, read the contents, and execute it in the interpreter""" filename = self.en_file.get() fp = file(filename) source = fp.read() self.inter.run_code(source, filename) class Interpreter: """an Interpreter maintains local and global state and executes code at run time""" def __init__(self, world, globals=globals(), locals=locals()): self.world = world self.globals = globals self.locals = locals self.globals['world'] = world def run_code(self, source, filename): "run the given code in the environment of this interpreter" code = compile(source, filename, 'exec') try: exec code in self.globals, self.locals except KeyboardInterrupt: self.world.quit() def run_code_thread(self, *args): """run the given code in a new thread note: the Thread class I am using here is defined in Gui.py""" Thread(self.run_code, *args) class TurmiteWorld(World): """a TermiteWorld has a canvas with cells """ def __init__(self): World.__init__(self) self.delay = 0.0 # time in seconds to sleep after an update self.ca_width = 600 # canvas width and height self.ca_height = 600 self.csize = 5 # cell size in pixels self.cells = {} # a dictionary that maps coordinates to cells self.setup() self.mainloop() def setup(self): # the left frame contains the canvas self.fr(LEFT) self.canvas = self.ca(width=self.ca_width, height=self.ca_height, bg='white') # uncomment the following line to apply a swirly transform # to the space. # self.canvas.add_transform(SwirlTransform(pi/400), 0) self.endfr() # the right frame contains various buttons self.fr(LEFT, fill=BOTH, expand=1) self.fr() self.bu(LEFT, text='Make Turmite', command=self.make_turmite) self.bu(LEFT, text='Print canvas', command=self.canvas.dump) self.bu(LEFT, text='Quit', command=self.quit) self.endfr() self.fr() self.bu(LEFT, text='Run', command=self.run) self.bu(LEFT, text='Stop', command=self.stop) self.bu(LEFT, text='Step', command=self.step) self.bu(LEFT, text='Clear', command=self.clear) self.endfr() self.bu(BOTTOM, text='run this code', command=self.run_text) self.te_code = self.te(BOTTOM, height=20, width=40) self.te_code.insert(END, 't1 = Turmite(world)\n') self.te_code.insert(END, 't2 = Turmite(world)\n') self.te_code.insert(END, 't3 = Turmite(world)\n') self.te_code.insert(END, 't2.lt()\n') self.te_code.insert(END, 't3.rt()\n') self.te_code.insert(END, 'world.run()\n') self.endfr() # you can pre-make a grid of cells, or create them on demand # low = [-20, -20] # high = [20, 20] # self.make_cells([low, high], **self.unmarked_options) def clear(self): """delete the cells and then call World.clear to handle the rest""" for cell in self.cells.values(): cell.undraw() World.clear(self) def get_bounds(self): return self.trans.invert_list([[0, 0], [self.ca_width, self.ca_height]]) def cell_bounds(self, x, y): """compute the coordinates of the four corners of the cell at index (x, y) and return them as a list of tuples""" p1 = [x, y] p2 = [x+1, y] p3 = [x+1, y+1] p4 = [x, y+1] bounds = [(p[0]*self.csize, p[1]*self.csize) for p in [p1, p2, p3, p4]] return bounds def make_cells(self, limits): """make a grid of cells with the specified limits. limits is a list of pairs, [[lowx, lowy], [highx, highy]]""" low, high = limits for x in range(low[0], high[0]): col = [] for y in range(low[1], high[1]): bounds = self.cell_bounds(x, y) self.cells[x,y] = Cell(self, bounds) def get_cell(self, x, y): """find and return the cell at (x, y), creating one if necessary""" x, y = int(round(x)), int(round(y)) cell = self.cells.get((x,y), None) if cell==None: bounds = self.cell_bounds(x, y) cell = Cell(self, bounds) self.cells[x,y] = cell return cell def make_turmite(self): "add a new turmite to this world" turmite = Turmite(self) class Cell: """a Cell object represents a cell in a CellWorld. marked_options specifies how the cell is drawn when it is marked; unmarked_options specifies how it is drawn otherwise. If Cells have more than two states, this part of the code will have to be redesigned. tag is the Tk tag the refers to the item on the canvas.""" def __init__(self, world, bounds): self.world = world self.marked_options = dict(fill='black', outline='gray80') self.unmarked_options = dict(fill='yellow', outline='gray80') self.tag = world.canvas.polygon(bounds, **self.unmarked_options) self.marked = 0 def __del__(self): "when this object is deleted, delete it's canvas item" self.undraw() def undraw(self): "delete this cell's canvas item" self.world.canvas.delete(self.tag) def config_cell(self, **options): "change the configuration of this cell's canvas item" self.world.canvas.itemconfig(self.tag, **options) def cget_cell(self, x, y, option): "get the current configuration of this cell's canvas item" return self.world.canvas.itemconfig(self.tag, option) def mark(self): "mark this cell and reconfigure it accordingly" self.marked = 1 self.config_cell(**self.marked_options) def unmark(self): "unmark this cell and reconfigure it accordingly" self.marked = 0 self.config_cell(**self.unmarked_options) def is_marked(self): return self.marked class Animal: """Animal is the parent class for Turmites and other animals Most animals override step, draw and undraw. """ def __init__(self, world): self.world = world self.x = 0 self.y = 0 self.delay = 0 def __del__(self): "when this object is deleted, delete it's canvas item" self.undraw() def step(self): pass def draw(self): pass def undraw(self): try: self.world.canvas.delete(self.tag) except AttributeError: pass def die(self): self.world.unregister(self) self.undraw() def redraw(self): self.undraw() self.draw() def update(self): self.world.update() time.sleep(self.delay) class Turmite(Animal): """a turmite is an animal that has a direction (4-state) This particular turmite is Langton's ant, described at http://mathworld.wolfram.com/LangtonsAnt.html """ def __init__(self, world): Animal.__init__(self, world) self.dir = 0 # have to draw yourself before registering! self.draw() world.register(self) def draw(self): """a turmite appears as a small red triangle that points in the direction the turmite is facing""" bounds = world.cell_bounds(self.x, self.y) bounds = rotate(bounds, self.dir) mid = vmid(bounds[1], bounds[2]) self.tag = self.world.canvas.polygon([bounds[0], mid, bounds[3]], fill='red') def fd(self, r=1): "move r steps in the direction the turmite is facing" if self.dir==0: self.x += r elif self.dir==1: self.y += r elif self.dir==2: self.x -= r else: self.y -=r self.redraw() def bk(self, r=1): "move backward r steps" self.fd(-r) def rt(self): "turn right 90 degrees" self.dir = (self.dir-1) % 4 def lt(self): "turn left 90 degrees" self.dir = (self.dir+1) % 4 def get_cell(self): "find the cell this turmite is on (creating it if necessary)" return self.world.get_cell(self.x, self.y) def mark(self): "mark the cell this turmite is on" cell = self.get_cell() cell.mark() def unmark(self): "unmark the cell this turmite is on" cell = self.get_cell() cell.unmark() def toggle(self): "toggle the cell this turmite is on" if self.is_on_mark(): self.unmark() else: self.mark() def is_on_mark(self): "check whether this turmite is on a marked cell" cell = self.get_cell() return cell.is_marked() def step(self): "take one step, following the rules of Langton's ant" if self.is_on_mark(): self.lt() else: self.rt() self.toggle() self.fd() # the following are some random linear-algebra utilities # written as functions (not methods) def vadd(p1, p2): "add vectors p1 and p2 (returns a new vector)" return [x+y for x,y in zip(p1, p2)] def vscale(p, s): "multiply p by a scalar (returns a new vector)" return [x*s for x in p] def vmid(p1, p2): "return a new vector that is the pointwise average of p1 and p2" return vscale(vadd(p1, p2), 0.5) def rotate(v, n=1): "rotate the list v by n places (returns a new list)" n %= len(v) return v[n:] + v[:n] # top_globals and top_locals are used to create the # Interpreter that executes user-provided code. # It is necessary # to provide a top-level environment so that user- # defined functions are top-level functions top_globals = globals() top_locals = locals() if __name__ == '__main__': world = TurmiteWorld() world.mainloop()