From The File: Turtle-Graphics - Tex
From The File: Turtle-Graphics - Tex
From The File: Turtle-Graphics - Tex
Turtle Graphics
13.1 Introduction
Graphical User Interfaces (GUIs) provide a rich environment in which information can be ex-
changed between a user and the computer. GUIs are not limited to simply displaying text and
reading text from the keyboard. GUIs enable users to control the behavior of a program by per-
forming actions such as using the mouse to drag or click on graphical objects. GUIs can make
using programs much more intuitive and easier to learn since they provide users with immediate
visual feedback that shows the effects of their actions.
There are many Python packages that can be used to create graphics and GUIs. Two graphics
modules, called turtle and tkinter, come as a part of Pythons standard library. tkinter is primarily
designed for creating GUIs. In fact, IDLE is built using tkinter. However, we will focus on the
turtle module that is primarily used as a simple graphics package but can also be used to create
simple GUIs.
The turtle module is an implementation of turtle graphics and uses tkinter for the creation
of the underlying graphics. Turtle graphics dates back to the 1960s and was part of the Logo
programming language.1 This chapter provides an introduction to using the graphics capabilities
of the turtle module and demonstrates the creation of simple images and simple GUIs for games
and applications. We will not delve into the details of building and designing GUIs, but many of
the skills developed here can be applied to more complicated GUI designs if you wish to pursue
that in the future. In addition to helping you gain practical programming skills, learning to use
turtle graphics is fun and it enables you to use Python to be visually creative!
311
312 CHAPTER 13. TURTLE GRAPHICS
pen it holds is down, the turtle leaves a trail when you tell it to move to a new location. When the
pen is up, the turtle moves to a new position but no trail is left. In addition to position, the turtle
also has a heading, i.e., a direction, of forward movement. The turtle module provides commands
that can set the turtles position and heading, control its forward and backward movement, specify
the type of pen it is holding, etc. By controlling the movement and orientation of the turtle as well
as the pen it is holding, you can create drawings from the trails the turtle leaves.
This imports the turtle module using the identifier t. By importing the module this way we
access the methods within the module using t.<object> instead of turtle.<object>. To
ensure that the module was properly imported, use the dir() function as shown in Listing 13.2.
Listing 13.2 Using dir() to view the turtle modules methods and attributes.
1 >>> dir(t)
2 [Canvas, Pen, RawPen, RawTurtle, Screen, ScrolledCanvas,
3 Shape, TK, TNavigator, TPen, Tbuffer, Terminator,
4 Turtle, TurtleGraphicsError, TurtleScreen, TurtleScreenBase,
5 Vec2D, _CFG, _LANGUAGE, _Root, _Screen, _TurtleImage,
6 __all__, __builtins__, __cached__, __doc__, __file__,
7 ... <<MANY LINES OF OUTPUT DELETED>>
8 window_width, write, write_docstringdict, xcor, ycor]
The list returned by the dir() function in Listing 13.2 has been truncatedin all, there
are 173 items in this list. To learn more about any of these attributes or methods, you can use
the help() function. For example, to learn about the forward() method, enter the statement
shown in line 1 of Listing 13.3.
4 forward(distance)
5 Move the turtle forward by the specified distance.
6
13.2. TURTLE BASICS 313
7 Aliases: forward | fd
8
9 Argument:
10 distance -- a number (integer or float)
11
From this we learn, as shown in lines 12 and 13, that this method moves the turtle forward by
the specified distance, in the direction the turtle is headed. We also learn that there is a shorter
name for this method: fd().
As shown in Fig. 13.1, a graphics window should appear in which you see a small arrow 100
units to the right of the center of the window.2 A thin black line is drawn from the center of the
window to the tail of the arrow. The arrow represents our turtle and the direction the arrow is
pointing indicates the current heading. The fd() method is a shorthand for the forward()
methodthe two methods are identical. fd() takes one integer argument that specifies the num-
ber of units you want to move the turtle forward in the direction of the current heading.3 If you
provide a negative argument, the turtle moves backwards the specified amount. Alternatively, to
move backward one can call either backward(), back(), or bk().
The default shape for our turtle is an arrow but if we wanted to have it look like a turtle we
could type the command shown in Listing 13.5.
This replaces the arrow with a small turtle. We can change the shape of our turtle to a number
of other built in shapes using the shape() method. We can also create custom shapes although
we wont cover that here.
2
When it first opens, this window may appear behind previously opened windows. So, you may have to search for
it.
3
By default the units correspond to pixels, i.e., individual picture-elements or dots on your screen, but one can reset
the coordinates so that the units can correspond to whatever is most convenient to generate the desired image.
314 CHAPTER 13. TURTLE GRAPHICS
Even though our turtles shape appears on the graphics window, the turtle is not truly part of our
drawing. The shape of the turtle is there to help you see the turtles current position and heading,
but you need to issue other commands, such as fd(), to create a drawing. If you have created a
masterpiece and you no longer want to see the turtle in your graphics window, you can enter the
command shown in Listing 13.6.
Listing 13.6 Command to hide the turtles shape from the screen.
>>> t.hideturtle()
This hides the image that currently represents the turtle. In fact, you can continue to create
lines even when the turtles shape is hidden, but you will not be able to see the turtles current
position nor its heading. If you want to see the turtle again, simply issue the command shown in
Listing 13.7.
The turtles heading can be controlled using one of three methods: left(), right(), and
setheading(); or the shorter aliases of lt(), rt(), and seth(), respectively. left() and
right() turn the turtle either to the left or right, respectively, by the number of degrees given as
the argument. These turns are relative to the turtles current heading. So, for example, left(45)
causes the turtle to turn 45 degrees to the left. On the other hand, setheading() and seth()
13.2. TURTLE BASICS 315
set the absolute heading of the turtle. A heading of 0 is horizontally to the right (i.e., east), 90 is
up (i.e., north), 135 is up and to the left (i.e., northwest), and so on.
Assuming you have previously entered the command of Listing 13.4, enter the commands in
Listing 13.8. After doing this you should see a square drawn in the graphics window as shown in
Fig. 13.2.
Listing 13.8 Commands to change the turtles heading and draw a square box.
1 >>> t.left(90)
2 >>> t.fd(100)
3 >>> t.left(90)
4 >>> t.fd(100)
5 >>> t.left(90)
6 >>> t.fd(100)
What if we want to change the location of the turtle without generating a line? We can ac-
complish this by calling the method penup() before we enter commands to move the turtle. To
re-enable drawing, we call the method pendown().
We can also move the turtle to a specific position within the graphics window by using the
setposition() method (or its aliases setpos() and goto()). setposition()s argu-
ments are the desired x and y values. The change of position does not affect the turtles heading.
If the pen is down, when you call setposition() (or its aliases), a straight line is drawn from
that starting point to the position specified by the arguments. To demonstrate the use of penup(),
pendown(), and setposition(), issue the commands shown in Listing 13.9. The resulting
image is shown in Fig. 13.3.
316 CHAPTER 13. TURTLE GRAPHICS
Figure 13.3: Result of moving the turtle without drawing a line and then, once at the new location,
drawing a line.
Listing 13.9 Using penup() and setposition() to move the turtle without making a line.
1 >>> t.penup()
2 >>> t.setposition(100, -100)
3 >>> t.pendown()
4 >>> t.fd(130)
If we want to erase everything that we previously drew, we can use either the clear() or
reset() methods. clear() clears the drawing from the graphics window but it leaves the
turtle in its current position with its current heading. reset() clears the drawing and also returns
the turtle to its starting position in the center of the screen. To illustrate the behavior of clear(),
enter the statement shown in Listing 13.10.
You should now see that the drawing that our turtle generated has been cleared from the screen
but the turtle is still in the state that you last specified. To demonstrate what reset() does, enter
the command shown in Listing 13.11.
>>> t.reset()
The turtle is moved back to the center of the screen with its original heading. Note that we do
not need to call clear() before we call reset(). reset() will also clear the drawingin
the above example they were done sequentially solely for demonstrating their behavior.
Building a square from straight lines is relatively straightforward, but what if we want to draw
circles? Turtle provides a method called circle() that can be used to tell the turtle to draw a
complete circle or only a part of a circle, i.e., an arc. The circle() method has one mandatory
argument which is the radius of the circle. Optional arguments specify the extent, which is the
degrees of arc that are drawn, and the steps, which are the number of straight-line segments used
to approximate the circle. If the radius is positive, the circle is drawn (starting from the current
position) by turning to the left (counterclockwise). If the radius is negative, the circle is drawn
(starting from the current position) by turning to the right (clockwise). To demonstrate this, enter
the commands shown in Listing 13.13. After issuing these commands, the graphics window should
appear as shown in Fig. 13.5
The image should be the same as 13.5, but it should render noticeably faster than it did previ-
ously. However, even though supplying the speed() method with an argument of 0 makes the
animation faster, it is still quite slow if we want to draw a more complex drawing. In order to make
our drawing appear almost immediately we can make use of the tracer() method. tracer()
takes two arguments. One controls how often screens should be updated and the other controls the
delay between these update. To obtain the fastest possible rendering, both these arguments should
be set to zero as shown in Listing 13.15.
By calling tracer() with bother arguments set to zero, we are essentially turning off all
animation and our drawings will be drawn immediately. However, if we turn the animation off
in this way we need to explicitly update the image with the update() method after we are done
issuing drawing commands.
If you want to reset tracer() to its original settings, its arguments should be 1 and 10, as
shown in Listing 13.16.
This change the turtles pen to blue. There are actually numerous ways of specifying a color
in Pythons implementation of turtle graphics. You can, as shown in Listing 13.17, specify a color
via a string.4 Alternatively, we can specify a color by providing numeric values the specify the
amount of red, green, and blue. To learn more about the use of colors, use the help() function
to read about t.color or t.pencolor.
We can also change the thickness of the turtles pen by using the pensize() method and
passing it an integer argument that specifies the thickness. This is demonstrated in Listing 13.18.
After issuing these commands (and those of Listing 13.17), you will see that a thick blue line has
been drawn in the graphics window as shown in Fig. 13.6
4
The string must be one of the Tk color specifications. A listing of these can be found at
www.tcl.tk/man/tcl8.4/TkCmd/colors.htm.
13.4. COLORS AND FILLED SHAPES 321
We can also change the background color of the graphics window. To demonstrate this, you
should enter the commands of Listing 13.19.
Lets take what we have learned so far and draw a more complex image. Enter the commands
shown Listing 13.20. The for-loop that starts in line 3 sets the heading to angles between 0 and
345 degrees, inclusive, in 15 degree increments. For each of these headings it draws a circle with
a radius of 100. (Note that a heading of 360 is the same as a heading of 0, so that why a circle is
not draw with a heading of 360 degrees.) The resulting image is shown in Fig. 13.7.
We can also use iteration to change the color and thickness of the turtles pen more dynamically.
As an example, try typing the commands shown in Listing 13.21. This should result in the image
shown in Fig. 13.8.
The resulting error message, shown in lines 2 through 10, is long and possibly confusing but if
you look at the last line it tells you that pumpkin is not a valid color string.
In the introduction to this chapter we mentioned that the turtle module uses tkinter for its
underlying graphics. Sometimes the errors you get will contain messages that pertain to tkinter
errors by using tk in the message. If you encounter one of these errors you typically do not need
to know anything about tkinter. It is likely the cause of the error is somewhere in your turtle code,
e.g., you are passing a method an incorrect value, so review your code carefully and make sure it
conforms to the requirements of the turtle module.
You dont need to completely connect (close) the shape you want to fill. To illustrate this, try
entering the code in Listing 13.24 and observe what you have drawn at each step. After issuing all
these commands you should see the shape shown in Fig. 13.10
Now issue the statements shown in Listing 13.25. You should now see the image shown in
Fig. 13.11. Note that the shape was filled as if there were an edge between the start-point and the
end-point, but it will not draw a line along this edge. Instead, the current color is merely painted
up to this edge.
Recall the Fibonacci fib() function from Listing 6.23. Now consider the function drawfib()
shown in Listing 13.26. This function takes two arguments. The second is identified as len ang
and, by inspection of the code, we see this parameter is used both as a length (when it is used to
control movement in lines 2 and 13) and as an angle (when it used to alter the heading in lines 8,
10, and 12) The value of len ang is never changed. The drawfib() function starts by drawing
a line (line 2). Then, if the first argument is greater than 1, it alters the heading and calls itself
twice, once with the value of the first argument reduced by 1 and another time with the argument
reduced by 2. The final statement of the function moves the pen back to the starting point. If the
first argument is 0 or 1, then the function function doesnt do anything before moving the pen back
to the starting point. We accomplish doing nothing by using the pass statements given in lines
4 and 6. Note that bodies of loops, functions, conditional statements, and classes cannot be truly
empty. However, we can make them effectively empty by using a pass statement.
Because we are not doing anything in the bodies of the first two clauses of the conditional
statement, we could have eliminated lines 3 through 6 and replaced line 7 with if n > 1.
However, we have elected to write things as given to help establish a connection with the way
numbers are generated in the Fibonacci sequence. The discussion of this code continues below the
listing.
Listing 13.26 Code to draw a tree recursively. The resulting tree is related to the Fibonacci se-
quence.
1 >>> def drawfib(n, len_ang):
2 ... t.forward(2 * len_ang)
3 ... if n == 0:
4 ... pass // Do nothing.
5 ... elif n == 1:
6 ... pass // Do nothing.
7 ... else:
8 ... t.left(len_ang)
9 ... drawfib(n - 1, len_ang)
10 ... t.right(2 * len_ang)
11 ... drawfib(n - 2, len_ang)
12 ... t.left(len_ang)
13 ... t.backward(2 * len_ang)
14 ...
15 >>> # Six different starting points for six different trees.
16 >>> start_points = [[-300, 250], [-150, 250],
17 ... [-300, 110], [-80, 110],
18 ... [-300, -150], [50, -150]]
19 >>>
20 >>> # For each starting point, draw a tree with n varying
21 ... # between 1 and 6 and len_ang set to 30.
22 >>> n = 0
23 >>> for start_point in start_points:
24 ... x, y = start_point
25 ... n = n + 1
13.5. VISUALIZING RECURSION 327
26 ... t.penup()
27 ... t.setpos(x, y)
28 ... t.pendown()
29 ... drawfib(n, 30)
It probably isnt at all obvious why we would bring up the Fibonacci sequence in connection
with the code in Listing 13.26. Before reading on, you should consider entering the function and
see what it produces for different arguments. Consider values of n between 1 and 10 and values of
len ang in the range of 20.
Now, to help illustrate the connection between drawfib() and the Fibonacci sequence, the
code in lines x through y of Listing 13.26 draw six different trees where n varies between 1 and 6
while the len ang is held fixed at 30. The resulting trees are shown in Fig. 13.12. The trunk
of each tree is the horizontal line that is the right-most component of each tree. If we had drawn
a tree with an n of 0, it would appear the same as with an n of 1 (i.e., the top left drawing in Fig.
13.12. If we count the number of tips or branches on the left side of the tree, for an n or 0 or
1, there is only 1 tip. For an n of 2 there are 2 (top right tree). When n is 3 there are 3 (middle left
tree). When n is 4 there are 5 tips (middle right). And, we see for an n of 5 or 6, there are 8 or 13
tips, respectively (the two bottom trees). Thus, the number of tips in our trees corresponds to the
numbers in the Fibonacci sequence! (The number of tips is the nth number in the sequence.)
Figure 13.12: Drawings of multiple Fibonacci trees using the drawfib() function of Listing
13.26. The first argument of this function (n) varies between 1 and 6. The upper left tree was
generated with an n of 1 while the bottom right tree had an n of 6.
A more complicated tree is drawn using the code shown in Listing 13.27. In this case n is
15 which results in 987 tips (although it would be almost impossible to count these from the tree
figure itself). The resulting tree is shown in Fig. 13.13.
328 CHAPTER 13. TURTLE GRAPHICS
Listing 13.27 Recursive tree using the fib() function with 987 branches.
1 >>> t.reset()
2 >>> t.tracer(0, 0)
3 >>> t.drawfib(15, 10)
4 >>> t.update()
5 >>> t.tracer(1, 10)
As you can see, the ks() function only draws one side of the snowflake. If we want to
complete the snowflake we can reuse this function and rotate the turtle appropriately to complete
the picture. The code to accomplish this is given in Listing 13.29. The resulting drawing is shown
in Fig. 13.15.
6 ... t.left(120)
7 ...
8 >>> t.update()
Figure 13.15: The Koch snowflake produced by the code of Listing 13.29.
Listing 13.30 Calling the print() function and obtaining a reference to it.
1 >>> print() # This prints a newline.
2 >>> print # This returns a reference to the print() function.
3 <built-in function print>
The statement in line 2 and the output in line 3 show that calling a function without paren-
theses behaves differently than when we use parentheses. In fact, without the parentheses, we are
not actually calling the function. When we write print without parentheses we obtain a refer-
ence to the function, i.e., an object that represents the print() function. Thus we can assign the
print() function to an valid identifier, as Listing 13.31 indicates.
The type() function, in line 2, shows that foo() is now also a function. Notice were
even told that foo() is a built-in function! Of course, there isnt a foo() function built-into
Python, but at this point we have made foo() indistinguishable from the actual built-in function
print(). If we try to use foo() in place of print(), we find, as lines 4 and 5 indicate, that
foo() now behaves exactly as print() does!
Given that we can assign functions to identifiers, it also makes sense that we can pass functions
as arguments to other functions. To demonstrate this, the fun run() function in Listing 13.32
takes two arguments. The first argument is a function while the second is an argument that first
argument is supposed to act upon.
turtle graphics has a method called onscreenclick() that takes a single parameter. This
parameter is assumed to be a reference to a function that takes two arguments. This function will
be called whenever there is a mouse click on the graphics window. The arguments passed to the
function will be the x and y coordinates of the point where the click occurred. To illustrate this,
assuming you have entered the code in Listing 13.33, type the code shown in Listing 13.34. In line
1 we register the printxy() function so that it will be called back whenever there is a click
on the graphics window. If you are using IDLE, you will also need to call mainloop() to enable
to processing of events. Once you call mainloop(), the interactive prompt will disappear and
it wont reappear until you close the graphics window (using the close button and the top of the
window).
Listing 13.34 Use of the onscreenclick() method to register the printxy() function as a
callback function.
>>> t.onscreenclick(printxy)
>>> t.mainloop() # If you are using IDLE.
Once you have done this, click on the graphics window in various places. You should see pairs
of numbers corresponding to the location of the point that was clicked! Note that these pairs of
numbers will be displayed in environment in which you have been issuing the Python commands
the numbers will not appear in the graphics window.
1 >>> # Set window to be 300 by 200 with the point (0, 0) as the
2 >>> # lower left corner and (300, 200) as the upper right corner.
3 >>> t.setup(300, 200)
4 >>> t.screensize(300, 200)
5 >>> t.setworldcoordinates(0, 0, 300, 200)
6 >>> # Draw two vertical lines to divide the window into thirds.
7 >>> t.penup()
8 >>> t.setpos(100, 0) # First line.
9 >>> t.pendown()
10 >>> t.setpos(100, 200)
11 >>> t.penup()
12 >>> t.setpos(200, 0) # Second line.
13 >>> t.pendown()
14 >>> t.setpos(200, 200)
After issuing these commands, your window should appear as shown in Fig. 13.16.
Now lets write a callback function that will draw a shape at the point where a click occurred,
but the shape that is drawn depends on where the click occurred. The function should draw a green
square if the click was in the left third of the window, red circles if the click was in the middle third
of the window, and blue squares if the click was in the right third of the window.
Listing 13.36 Callback function to draw various shapes at the point of a click.
1 >>> def shapedrawer(x, y):
2 ... t.penup()
334 CHAPTER 13. TURTLE GRAPHICS
The last two statements in Listing 13.36 register our callback function and then enter the
main loop to start processing events. Once you have entered this code, click in various places in
the graphics window. You should be able to produce images like the one shown in Fig. 13.17.