#!/usr/bin/python import random from math import * from Gui import * class World(Gui): def __init__(self): Gui.__init__(self) self.exists = True self.animals = [] self.inter = Interpreter(self, top_globals, top_locals) def quit(self): self.exists = False Gui.quit(self) def register(self, animal): self.animals.append(animal) def unregister(self, animal): self.animals.remove(animal) def clear(self): for animal in self.animals: animal.undraw() self.animals = [] self.canvas.delete('all') def step(self): for animal in self.animals: animal.step() def run(self): self.running = 1 while self.exists and self.running: self.step() self.update() time.sleep(self.delay) def stop(self): self.running = 0 def map_animals(self, callable): map(callable, self.animals) def run_text(self): source = self.te_code.get(1.0, END) self.inter.run_code(source, '') def run_file(self): filename = self.en_file.get() fp = file(filename) source = fp.read() self.inter.run_code(source, filename) class Interpreter: def __init__(self, world, globals=globals(), locals=locals()): self.world = world self.globals = globals self.locals = locals self.globals['world'] = world def run_code_thread(self, *args): Thread(self.run_code, *args) def run_code(self, source, filename): code = compile(source, filename, 'exec') try: exec code in self.globals, self.locals except KeyboardInterrupt: self.world.quit class TurtleWorld(World): def __init__(self): World.__init__(self) self.delay = 0.1 # time in seconds to sleep after an update self.setup() def setup(self): self.ca_width = 400 self.ca_height = 400 # left frame self.fr(LEFT) self.canvas = self.ca(width=self.ca_width, height=self.ca_height, bg='white') self.endfr() # right frame self.fr(LEFT, fill=BOTH, expand=1) self.fr() self.bu(LEFT, text='Make Turtle', command=self.make_turtle) self.bu(LEFT, text='Print canvas', command=self.canvas.dump) self.bu(LEFT, text='Quit', command=self.quit) self.endfr() self.fr(fill=X) self.bu(LEFT, text='Run file', command=self.run_file) self.en_file = self.en(LEFT, text='turtle_code.py', width=5, fill=X, expand=1) self.endfr() self.bu(BOTTOM, text='run this code', command=self.run_text) self.te_code = self.te(BOTTOM, height=10, width=40) self.te_code.insert(END, 'world.clear()\n') self.te_code.insert(END, 'bob = Turtle(world)\n') # leave the right frame open so that Turtles can add TurtleControls # self.endfr() def setup_run(self): 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() def make_turtle(self): turtle = Turtle(self) control = TurtleControl(turtle) class TurtleControl: def __init__(self, turtle): self.turtle = turtle self.setup() def setup(self): w = self.turtle.world w.fr(bd=3, relief=SUNKEN) w.la(text='Turtle Control') # ROW 1 w.fr(fill=X, expand=1) w.bu(LEFT, text=' fd ', command=self.fd) self.en_r = w.en(LEFT, fill=X, expand=1, width=5, text='10') w.bu(LEFT, text='lt', command=self.turtle.lt) w.bu(LEFT, text='rt', command=self.turtle.rt) w.endfr() # ROW 2 w.fr(fill=X, expand=1) w.bu(LEFT, text='pu', command=self.turtle.pu) w.bu(LEFT, text='pd', command=self.turtle.pd) colors = 'red', 'orange', 'yellow', 'green', 'blue', 'violet' self.mb = w.mb(text=colors[0], fill=X, relief=SUNKEN) self.mb.pack(pady=1) for color in colors: w.mi(self.mb, color, command=Callable(self.color, color)) w.endfr() w.endfr() def color(self, color): self.mb.config(text=color) self.turtle.set_color(color) def fd(self): r = int(self.en_r.get()) self.turtle.fd(r) def turn(self): theta = int(self.en_theta.get()) self.turtle.turn(theta) class TurmiteWorld(World): def __init__(self): World.__init__(self) self.delay = 0.0 # time in seconds to sleep after an update self.ca_width = 500 self.ca_height = 500 self.csize = 5 self.cells = {} self.setup() self.mainloop() def setup(self): # left frame self.fr(LEFT) self.canvas = self.ca(width=self.ca_width, height=self.ca_height, bg='white') self.canvas.add_transform(SwirlTransform(pi/400), 0) self.endfr() # right frame self.fr(LEFT, fill=BOTH, expand=1) self.fr() self.bu(LEFT, text='Make Turmite', command=self.make_turmite) 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() low = [-20, -20] high = [20, 20] # self.make_cells([low, high], **self.unmarked_options) def clear(self): for animal in self.animals: animal.undraw() for cell in self.cells.values(): cell.undraw() self.animals = [] self.cells = {} def quit(self): self.running = 0 World.quit(self) def get_bounds(self): return self.trans.invert_list([[0, 0], [self.ca_width, self.ca_height]]) def cell_bounds(self, x, y): 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): 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): 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): turmite = Turmite(self) # assemble a set of keyword arguments into a dictionary def makedict(**kwds): return kwds class Cell: def __init__(self, world, bounds): self.world = world self.marked_options = makedict(fill='black', outline='gray80') self.unmarked_options = makedict(fill='yellow', outline='gray80') self.tag = world.canvas.polygon(bounds, **self.unmarked_options) self.marked = 0 def __del__(self): self.undraw() def undraw(self): self.world.canvas.delete(self.tag) def config_cell(self, **options): self.world.canvas.itemconfig(self.tag, **options) def cget_cell(self, x, y, option): return self.world.canvas.itemconfig(self.tag, option) def mark(self): self.marked = 1 self.config_cell(**self.marked_options) def unmark(self): self.marked = 0 self.config_cell(**self.unmarked_options) def is_marked(self): return self.marked class Animal: def __init__(self, world): self.world = world self.x = 0 self.y = 0 self.delay = 0 def __del__(self): 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 Turtle(Animal): def __init__(self, world, delay=0.5): Animal.__init__(self, world) self.r = 5 self.heading = 0 self.pen = 1 self.color = 'red' self.delay = delay self.draw() world.register(self) def step(self): self.fd() def polar(self, x, y, r, theta): rad = theta * pi/180 s = sin(rad) c = cos(rad) return [ x + r * c, y + r * s ] def draw_line(self, scale, dtheta, **options): r = scale * self.r theta = self.heading + dtheta head = self.polar(self.x, self.y, r, theta) tail = self.polar(self.x, self.y, -r, theta) self.world.canvas.line([tail, head], **options) def draw(self): self.tag = 'Turtle%d' % id(self) lw = self.r/2 self.draw_line(2.5, 0, tags=self.tag, width=lw, arrow=LAST) self.draw_line(1.8, 40, tags=self.tag, width=lw) self.draw_line(1.8, -40, tags=self.tag, width=lw) self.world.canvas.circle(self.x, self.y, self.r, self.color, tags=self.tag) self.update() def fd(self, r=1): x = self.x y = self.y p1 = [x, y] p2 = self.polar(x, y, r, self.heading) self.x = p2[0] self.y = p2[1] if (self.pen): self.world.canvas.line([p1, p2], fill='black') self.redraw() def bk(self, r=1): self.fd(-r) def rt(self, angle=90): self.heading = self.heading - angle self.redraw() def lt(self, angle=90): self.heading = self.heading + angle self.redraw() def pd(self): self.pen = 1 def pu(self): self.pen = 0 def set_color(self, color): self.color = color self.redraw() class Turmite(Animal): 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): 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): 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): self.fd(-r) def rt(self): self.dir = (self.dir-1) % 4 def lt(self): self.dir = (self.dir+1) % 4 def get_cell(self): return self.world.get_cell(self.x, self.y) def mark(self): cell = self.get_cell() cell.mark() def unmark(self): cell = self.get_cell() cell.unmark() def toggle(self): if self.is_on_mark(): self.unmark() else: self.mark() def is_on_mark(self): cell = self.get_cell() return cell.is_marked() def step(self): if self.is_on_mark(): self.lt() else: self.rt() self.toggle() self.fd() class AmoebaWorld(World): def __init__(self): World.__init__(self) self.ca_width = 400 self.ca_height = 400 self.animals = [] self.thread = None self.fr(LEFT) self.canvas = self.ca(width=self.ca_width, height=self.ca_height, bg='white', transforms=[]) trans = CanvasTransform(self.canvas, [20, 20]) self.canvas.add_transform(trans) self.endfr() # draw the grid dash = {True:'', False:'.'} (xmin, xmax) = (-10, 10) (ymin, ymax) = (-10, 10) for x in range(xmin, xmax+1, 1): self.canvas.line([[x, ymin], [x, ymax]], dash=dash[x==0]) for y in range(ymin, ymax+1, 1): self.canvas.line([[xmin, y], [xmax, y]], dash=dash[y==0]) def control_panel(self): self.fr(LEFT, fill=BOTH, expand=1) self.fr(TOP) self.bu(LEFT, text='Run', command=self.run_thread) self.bu(LEFT, text='Stop', command=self.stop) self.bu(LEFT, text='Quit', command=self.quit) self.endfr() self.fr(fill=X, expand=1) self.la(LEFT, text='end time') self.en_end = self.en(LEFT, width=5, fill=X, expand=1, text='10') self.la(LEFT, text='seconds') self.endfr() self.en_xoft = self.entry('x(t) = ') self.en_yoft = self.entry('y(t) = ') self.endfr() def entry(self, label): self.fr(fill=X, expand=1) self.la(LEFT, text=label) entry = self.en(LEFT, width=5, fill=X, expand=1, text=' t') self.endfr() return entry def run_thread(self): # if there is already a thread, kill it and wait for it to die if self.thread: self.running = 0 self.thread.join() # find out how long to run end = self.en_end.get() end = int(end) # create a thread and start it self.thread = Thread(self.run, end) def run(self, end=10): self.running = 1 start_time = time.time() t = 0 while self.exists and self.running and t < end: for amoeba in self.animals: x = amoeba.xoft(t) y = amoeba.yoft(t) print 't = %.1f x = %.1f y = %.1f' % (t, x, y) amoeba.redraw(x, y) time.sleep(0.1) t = time.time() - start_time def xoft(self, t): return eval(self.en_xoft.get()) def yoft(self, t): return eval(self.en_yoft.get()) class Amoeba: def __init__(self, world, xoft=None, yoft=None): self.world = world self.xoft = xoft or self.xoft self.yoft = yoft or self.yoft self.size = 0.5 self.color1 = 'violet' self.color2 = 'medium orchid' world.register(self) def xoft(self, t): return t def yoft(self, t): return t def undraw(self): try: self.world.canvas.delete(self.tag) except AttributeError: pass def redraw(self, x, y): self.undraw() self.draw(x, y) def draw(self, x, y): thetas = range(0, 360, 30) self.tag = 'Amoeba%d' % id(self) slime = 'lavender' coords = self.poly_coords(x, y, thetas, self.size) self.world.canvas.polygon(coords, fill=slime, outline=slime) self.world.canvas.polygon(coords, fill=self.color1, outline=self.color2, tags=self.tag) coords = self.poly_coords(x, y, thetas, self.size/2) self.world.canvas.polygon(coords, fill=self.color2, outline=self.color1, tags=self.tag) def poly_coords(self, x, y, thetas, size): rs = [size+random.uniform(0, size) for theta in thetas] coords = [self.polar(x, y, r, theta) for (r, theta) in zip(rs, thetas)] return coords def polar(self, x, y, r, theta): rad = theta * pi/180 s = sin(rad) c = cos(rad) return [ x + r * c, y + r * s ] class GuiAmoeba(Amoeba): def xoft(self, t): return self.world.xoft(t) def yoft(self, t): return self.world.yoft(t) # the following are some random linear-algebra utilities # written as functions (not methods) def vadd(p1, p2): return [x+y for x,y in zip(p1, p2)] def vscale(p, s): return [x*s for x in p] def vmid(p1, p2): return vscale(vadd(p1, p2), 0.5) def rotate(v, n=1): n %= len(v) return v[n:] + v[:n] # add the turtle functions to the global dictionary # so they can be invoked as simple functions (not methods) fd = Turtle.fd bk = Turtle.bk lt = Turtle.lt rt = Turtle.rt pu = Turtle.pu pd = Turtle.pd die = Turtle.die set_color = Turtle.set_color # 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 = TurtleWorld() world.mainloop()