/
TurtleWorld.py
299 lines (232 loc) · 8.61 KB
/
TurtleWorld.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
290
291
292
293
294
295
296
297
298
299
"""This module is part of Swampy, a suite of programs available from
allendowney.com/swampy.
Copyright 2010 Allen B. Downey
Distributed under the GNU General Public License at gnu.org/licenses/gpl.html.
"""
from tkinter import TOP, BOTTOM, LEFT, RIGHT, END, LAST, NONE, SUNKEN
from .Gui import Callable
from .World import World, Animal, wait_for_user
class TurtleWorld(World):
"""An environment for Turtles and TurtleControls."""
def __init__(self, interactive=False):
World.__init__(self)
self.title("TurtleWorld")
# the interpreter executes user-provided code
gs = globals()
gs["world"] = self
self.make_interpreter(gs)
# make the GUI
self.setup()
if interactive:
self.setup_interactive()
def setup(self):
"""Create the GUI."""
# canvas width and height
self.ca_width = 400
self.ca_height = 400
self.row()
self.canvas = self.ca(width=self.ca_width, height=self.ca_height, bg="white")
def setup_interactive(self):
"""Creates the right frame with the buttons for interactive mode."""
# right frame
self.fr()
self.gr(2, [1, 1], [1, 1], expand=0)
self.bu(text="Print canvas", command=self.canvas.dump)
self.bu(text="Quit", command=self.quit)
self.bu(text="Make Turtle", command=self.make_turtle)
self.bu(text="Clear", command=self.clear)
self.endgr()
# run this code
self.bu(side=BOTTOM, text="Run code", command=self.run_text, expand=0)
self.fr(side=BOTTOM)
self.te_code = self.te(height=10, width=25, side=BOTTOM)
self.te_code.insert(END, "world.clear()\n")
self.te_code.insert(END, "bob = Turtle()\n")
self.endfr()
# run file
self.row([0, 1], pady=30, side=BOTTOM, expand=0)
self.bu(side=LEFT, text="Run file", command=self.run_file)
self.en_file = self.en(side=LEFT, text="turtle_code.py", width=5)
self.endrow()
# leave the right frame open so that Turtles can add TurtleControls
# self.endfr()
def setup_run(self):
"""Adds a row of buttons for run, step, stop and clear."""
self.gr(2, [1, 1], [1, 1], expand=0)
self.bu(text="Run", command=self.run)
self.bu(text="Stop", command=self.stop)
self.bu(text="Step", command=self.step)
self.bu(text="Quit", command=self.quit)
self.endgr()
def make_turtle(self):
"""Creates a new turtle and corresponding controller."""
turtle = Turtle(self)
control = TurtleControl(turtle)
turtle.control = control
return control
def clear(self):
"""Undraws and remove all the animals, clears the canvas.
Also removes any control panels.
"""
for animal in self.animals:
animal.undraw()
if hasattr(animal, "control"):
animal.control.frame.destroy()
self.animals = []
self.canvas.delete("all")
class Turtle(Animal):
"""Represents a Turtle in a TurtleWorld.
Attributes:
x: position (inherited from Animal)
y: position (inherited from Animal)
r: radius of shell
heading: what direction the turtle is facing, in degrees. 0 is east.
pen: boolean, whether the pen is down
color: string turtle color
"""
def __init__(self, world=None):
Animal.__init__(self, world)
self.r = 5
self.heading = 0
self.pen = True
self.color = "red"
self.pen_color = "blue"
self.draw()
def get_x(self):
"""Returns the current x coordinate."""
return self.x
def get_y(self):
"""Returns the current y coordinate."""
return self.y
def get_heading(self):
"""Returns the current heading in degrees. 0 is east."""
return self.heading
def step(self):
"""Takes a step.
Default step behavior is forward one pixel.
"""
self.fd()
def draw(self):
"""Draws the turtle."""
if not self.world:
return
self.tag = "Turtle%d" % id(self)
lw = self.r / 2
# draw the line that makes the head and tail
self._draw_line(2.5, 0, tags=self.tag, width=lw, arrow=LAST)
# draw the diagonal that makes two feet
self._draw_line(1.8, 40, tags=self.tag, width=lw)
# draw the diagonal that makes the other two feet
self._draw_line(1.8, -40, tags=self.tag, width=lw)
# draw the shell
self.world.canvas.circle([self.x, self.y], self.r, self.color, tags=self.tag)
self.world.sleep()
def _draw_line(self, scale, dtheta, **options):
"""Draws the lines that make the feet, head and tail.
Args:
scale: length of the line relative to self.r
dtheta: angle of the line relative to self.heading
"""
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 fd(self, dist=1):
"""Moves the turtle foward by the given distance."""
x, y = self.x, self.y
p1 = [x, y]
p2 = self.polar(x, y, dist, self.heading)
self.x, self.y = p2
# if the pen is down, draw a line
if self.pen and self.world.exists:
self.world.canvas.line([p1, p2], fill=self.pen_color)
self.redraw()
def bk(self, dist=1):
"""Moves the turtle backward by the given distance."""
self.fd(-dist)
def rt(self, angle=90):
"""Turns right by the given angle."""
self.heading = self.heading - angle
self.redraw()
def lt(self, angle=90):
"""Turns left by the given angle."""
self.heading = self.heading + angle
self.redraw()
def pd(self):
"""Puts the pen down (active)."""
self.pen = True
def pu(self):
"""Puts the pen up (inactive)."""
self.pen = False
def set_color(self, color):
"""Changes the color of the turtle.
Note that changing the color attribute doesn't change the
turtle on the canvas until redraw is invoked. One way
to address that would be to make color a property.
"""
self.color = color
self.redraw()
def set_pen_color(self, color):
"""Changes the pen color of the turtle."""
self.pen_color = color
"""Add the turtle methods to the module namespace
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
set_pen_color = Turtle.set_pen_color
class TurtleControl(object):
"""Represents the control panel for a turtle.
Some turtles have a turtle control panel in the GUI, but not all;
it depends on how they were created.
"""
def __init__(self, turtle):
self.turtle = turtle
self.setup()
def setup(self):
w = self.turtle.world
self.frame = w.fr(bd=2, relief=SUNKEN, padx=1, pady=1, expand=0)
w.la(text="Turtle Control")
# forward and back (and the entry that says how far)
w.fr(side=TOP)
w.bu(side=LEFT, text="bk", command=Callable(self.move_turtle, -1))
self.en_dist = w.en(side=LEFT, fill=NONE, expand=0, width=5, text="10")
w.bu(side=LEFT, text="fd", command=self.move_turtle)
w.endfr()
# other buttons
w.fr(side=TOP)
w.bu(side=LEFT, text="lt", command=self.turtle.lt)
w.bu(side=LEFT, text="rt", command=self.turtle.rt)
w.bu(side=LEFT, text="pu", command=self.turtle.pu)
w.bu(side=LEFT, text="pd", command=self.turtle.pd)
w.endfr()
# color menubutton
colors = "red", "orange", "yellow", "green", "blue", "violet"
w.row([0, 1])
w.la("Color:")
self.mb = w.mb(text=colors[0])
for color in colors:
w.mi(self.mb, text=color, command=Callable(self.set_color, color))
w.endrow()
w.endfr()
def set_color(self, color):
"""Changes the color of the turtle and the text on the button."""
self.mb.config(text=color)
self.turtle.set_color(color)
def move_turtle(self, sign=1):
"""Reads the entry and moves the turtle.
Args:
sign: +1 for fd or -1 for back.
"""
dist = int(self.en_dist.get())
self.turtle.fd(sign * dist)
if __name__ == "__main__":
tw = TurtleWorld(interactive=True)
tw.wait_for_user()