#!/usr/bin/env python """ A toroidal world with diskoids that can move around in it trying to satisfy their hunger but can't learn anything. """ import random, math from tkinter import * ### UTILITY FUNCTION def xy_dist(angle, dist): '''The x and y distances (ints) corresponding to a distance dist in angle.''' radian_angle = math.radians(360 - angle) return int(dist * math.cos(radian_angle)), int(dist * math.sin(radian_angle)) class WorldFrame(Frame): '''A Frame in which to display the world.''' def __init__(self, root, width=450, height=450): '''Give the frame a canvas, a world, and dimensions and display it.''' Frame.__init__(self, root) self.world = World(self, width=width, height=height) root.title('The World') self.grid() class World(Canvas): """The arena where everything happens, including both graphics and thing representation.""" color = 'black' """Color for the Canvas background.""" steps_per_run = 100 """Number of steps to run when the 'Run' button is pushed.""" def __init__(self, frame, width=450, height=450): """Initialize dimensions and create things.""" Canvas.__init__(self, frame, bg=World.color, width=width, height=height) self.frame = frame self.width = width self.height = height self.things = [] self.graphic_objs = {} # The fertile part self.create_rectangle(0, 0, 100, 100, fill='#030', outline='') self.grid() def add_thing(self, tp, coords=None): '''Create a thing of a given type at a random location.''' coords = coords or self.get_thing_coords() thing = tp(self, coords) self.things.append(thing) self.graphic_objs[thing.graphic_id] = thing return thing def get_thing_coords(self): '''Coordinates for a new thing.''' return random.randint(0, self.width), random.randint(0, self.height) def adjust_coords(self, coords): '''Adjust coordinates of moved critter, assuming the world wraps around.''' x, y = coords if x < 0: x = self.width + x elif x > self.width: x = x - self.width if y < 0: y = self.height + y elif y > self.height: y = y - self.height return x, y def step(self): """Step each of the things.""" for thing in self.things: thing.step() def run(self): """Run step() steps_per_run times on everything, and print the world.""" for s in range(World.steps_per_run): self.step() self.update_idletasks() def clear(self): """Get rid of all of the things in the world.""" self.things = [] for obj in self.graphic_objs.keys(): self.delete(obj) def dump(self, filename): """Write the state of all of the things to a file.""" fileobj = open(filename, 'w') for thing in self.things: fileobj.write(str(thing) + '\n') fileobj.close() class Diskoid: '''Critters that move around.''' mouth_angle = 30 """Angle of the diskoid's mouth.""" width = 12 """Radius of diskoid.""" move_dist = 8 """How far diskoid moves.""" turn_prob = .2 """Probability that diskoid turns (rather than moves).""" color = 'yellow' """Color of diskoid.""" def __init__(self, world, coords): """Set the coordinates, world, and heading, and create the Canvas object.""" self.coords = coords self.world = world self.hunger = 0 self.heading = random.randint(0, 359) self.make_graphical_object() def __str__(self): """Print name for diskoid.""" return str(self.coords) + ' ' + str(self.heading) + ' ' + str(self.hunger) def move(self): """Move in a random direction.""" # How far to move dist = self.get_move_dist() if dist > 0: # Actually move x_incr, y_incr = xy_dist(self.heading, dist) self.coords = self.coords[0] + x_incr, self.coords[1] + y_incr self.coords = self.world.adjust_coords(self.coords) x, y = self.coords self.world.coords(self.graphic_id, x - Diskoid.width, y - Diskoid.width, x + Diskoid.width, y + Diskoid.width) def get_move_dist(self): """The distance moved, depending on hunger.""" if self.hunger < 3: # Not hungry enough to look around return 0 else: # Look for something to eat return math.sqrt(self.hunger) def turn(self, angle): """Turn a random angle.""" self.heading = (self.heading + angle) % 360 self.world.itemconfigure(self.graphic_id, start=self.heading + self.mouth_angle / 2) def satiate(self): """How much hunger changes, depending on coords.""" if self.coords[0] < 100 and self.coords[1] < 100: # In the fertile region self.hunger *= .7 # Get hungrier by default self.hunger += 1 def step(self): """Step once (turn or move).""" if random.random() < Diskoid.turn_prob: # Turn a random amount self.turn(int(random.random() * 360)) # Change hunger self.satiate() # Possibly move self.move() def kill(self, event): """Remove the diskoid from the world.""" self.world.things.remove(self) self.world.delete(self.graphic_id) def make_graphical_object(self): """Create the Canvas object for the diskoid: an arc.""" x, y = self.coords self.graphic_id = self.world.create_arc(x - Diskoid.width, y - Diskoid.width, x + Diskoid.width, y + Diskoid.width, # A little mouth start=self.heading + self.mouth_angle / 2, extent=360 - self.mouth_angle, fill=self.color, outline='blue') # Create the root ... root = Tk() # ... and the frame for the world canvas frame = WorldFrame(root) # ... and start the loop root.mainloop()