-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWorld.py
289 lines (223 loc) · 7.91 KB
/
World.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/python
"""
This module is part of Swampy, a suite of programs available from
allendowney.com/swampy.
Copyright 2005 Allen B. Downey
Distributed under the GNU General Public License at gnu.org/licenses/gpl.html.
"""
import math
import random
import time
import threading
import sys
import Tkinter
from Gui import Gui
class World(Gui):
"""Represents the environment where Animals live.
A World usually includes a canvas, where animals are drawn,
and sometimes a control panel.
"""
current_world = None
def __init__(self, delay=0.5, time= 0, *args, **kwds):
Gui.__init__(self, *args, **kwds)
self.delay = delay
self.time = time
self.title('World')
# keep track of the most recent world
World.current_world = self
# set to False when the user presses quit.
self.exists = True
# list of animals that live in this world.
self.animals = []
# if the user closes the window, shut down cleanly
self.protocol("WM_DELETE_WINDOW", self.quit)
def wait_for_user(self):
"""Waits for user events and processes them."""
try:
self.mainloop()
except KeyboardInterrupt:
print 'KeyboardInterrupt'
def quit(self):
"""Shuts down the World."""
# tell other threads that the world is gone
self.exists = False
# destroy closes the window
self.destroy()
# quit terminates mainloop (but since mainloop can get called
# recursively, quitting once might not be enough!)
Gui.quit(self)
def sleep(self):
"""Updates the GUI and sleeps.
Calling Tk.update from a function that might be invoked by
an event handler is generally considered a bad idea. For
a discussion, see http://wiki.tcl.tk/1255
However, in this case:
1) It is by far the simplest option, and I want to keep this
code readable.
2) It is generally the last thing that happens in an event
handler. So any changes that happen during the update
won't cause problems when it returns.
Sleeping is also a potential problem, since the GUI is
unresponsive while sleeping. So it is probably a good idea
to keep delay less than about 0.5 seconds.
"""
self.update()
time.sleep(self.delay)
def register(self, animal):
"""Adds a new animal to the world."""
self.animals.append(animal)
def unregister(self, animal):
"""Removes an animal from the world."""
self.animals.remove(animal)
def clear(self):
"""Undraws and removes all the animals.
And deletes anything else on the canvas.
"""
for animal in self.animals:
animal.undraw()
self.animals = []
try:
self.canvas.delete('all')
except AttributeError:
print 'Warning: World.clear: World must have a canvas.'
def step(self):
"""Invoke the step method on every animal."""
for animal in self.animals:
if animal.alive:
animal.step()
self.time += 1
def run(self):
"""Invoke step intermittently until the user presses Quit or Stop."""
self.running = True
while self.exists and self.running:
self.step()
if self.animals == []:
self.running = False
self.update()
def stop(self):
"""Stops running."""
self.running = False
def map_animals(self, callable):
"""Apply the given callable to all animals.
Args:
callable: any callable object, including Gui.Callable
"""
return map(callable, self.animals)
def make_interpreter(self, gs=None):
"""Makes an interpreter for this world.
Creates an attribute named inter.
"""
self.inter = Interpreter(self, gs)
def run_text(self):
"""Executes the code from the TextEntry in the control panel.
Precondition: self must have an Interpreter and a text entry.
"""
source = self.te_code.get(1.0, Tkinter.END)
self.inter.run_code(source, '<user-provided code>')
def run_file(self):
"""Read the code from the filename in the entry and runs it.
Precondition: self must have an Interpreter and a filename entry.
"""
filename = self.en_file.get()
fp = open(filename)
source = fp.read()
self.inter.run_code(source, filename)
class Interpreter(object):
"""Encapsulates the environment where user-provided code executes."""
def __init__(self, world, gs=None):
self.world = world
# if the caller didn't provide globals, use the current env
if gs == None:
self.globals = globals()
else:
self.globals = gs
def run_code_thread(self, *args):
"""Runs the given code in a new thread."""
return MyThread(self.run_code, *args)
def run_code(self, source, filename):
"""Runs the given code in the saved environment."""
code = compile(source, filename, 'exec')
try:
exec code in self.globals
except KeyboardInterrupt:
self.world.quit()
except Tkinter.TclError:
pass
class MyThread(threading.Thread):
"""Wrapper for threading.Thread.
Improves the syntax for creating and starting threads.
"""
def __init__(self, target, *args):
threading.Thread.__init__(self, target=target, args=args)
self.start()
class Animal(object):
"""Abstract class, defines the methods child classes need to provide.
Attributes:
world: reference to the World the animal lives in.
x: location in Canvas coordinates
y: location in Canvas coordinates
"""
def __init__(self, world=None, x=0, y=0):
self.alive = True
self.x = x
self.y = y
self.world = world or World.current_world
if self.world:
self.world.register(self)
def set_delay(self, delay):
"""Sets delay for this animal's world.
delay is made available as an animal attribute for backward
compatibility; ideally it should be considered an attribute
of the world, not an animal.
Args:
delay: float delay in seconds
"""
self.world.delay = delay
delay = property(lambda self: self.world.delay, set_delay)
def step(self):
"""Takes one step.
Subclasses should override this method.
"""
pass
def draw(self):
"""Draws the animal.
Subclasses should override this method.
"""
pass
def undraw(self):
"""Undraws the animal."""
if self.world.exists and hasattr(self, 'tag'):
self.world.canvas.delete(self.tag)
def die(self):
"""Removes the animal from the world and undraws it."""
self.alive = False
self.redraw()
def redraw(self):
"""Undraws and then redraws the animal."""
if self.world.exists:
self.undraw()
self.draw()
def polar(self, x, y, r, theta):
"""Converts polar coordinates to cartesian.
Args:
x, y: location of the origin
r: radius
theta: angle in degrees
Returns:
tuple of x, y coordinates
"""
rad = theta * math.pi/180
s = math.sin(rad)
c = math.cos(rad)
return [ x + r * c, y + r * s ]
def wait_for_user():
"""Invokes wait_for_user on the most recent World."""
World.current_world.wait_for_user()
if __name__ == '__main__':
# make a generic world
world = World()
# create a canvas and put a text item on it
ca = world.ca()
ca.text([0,0], 'hello')
# wait for the user
wait_for_user()