We'll learn about a new graphics library today, Turtle Graphics.
We will write programs that control the turtle's movements. The turtle leaves a trail behind.
from turtle import *
setup(400,400, 0, 0) # open the Turtle graphics window
fd(100) # move turtle forward 100 pixels
lt(90) # change direction by 90 degrees left
fd(100) # move forward 100 pixels
pensize(5) # change pen size
pencolor('red') # change pen color
lt(90)
fd(100)
Your Turn: Complete the drawing of the square in the cell below.
# solution
lt(90)
fd(100)
# use clear() to erase the canvas
clear()
Notice that the turtle's properties (pen size and color haven't changed). If in addition to clearing
the drawing we want to return the turtle in its original state, use reset()
.
reset()
Here is a new example (shown in the slides):
fd(100)
lt(60)
shape('turtle')
pencolor('red')
fd(150)
rt(15)
pencolor('blue')
bk(100)
pu()
bk(50)
pd()
pensize(5)
bk(250)
pensize(1)
home()
If what you saw was to fast, we can change the speed with which the turtle moves, with
the method speed()
. Let's add the line speed(1)
at the start of the block above, and rerun the example.
clear()
reset()
Loops can be used in conjunction with turtles to make interesting designs.
Let's build together the function polygon()
, which will produce different shapes as shown in slide 13-4.
def polygon(numSides, sideLength):
'''Draws a polygon with the specified number
of sides, each with the specified length.'''
# solution
angle = 360.0/numSides #angle to turn at each side
for i in range(numSides):
fd(sideLength)
lt(angle)
# Draw a triangle, allow user to click on canvas to close window
# 0,0 sets the position of the canvas at the top-left corner
clear()
reset()
polygon(3,100)
# Draw a square
clear()
reset()
polygon(4,100)
# Draw a pentagon
clear()
reset()
polygon(5,75)
# Draw an approximate circle
clear()
reset()
polygon(100, 3)
In addition to the simple shapes we saw above, with loops we can create more interesting shapes too,
such as geometric flowers. Examples are shown in the slides on page 13-5.
Use the helper function polygon()
that you defined previously.
def polyFlow(numPetals, petalSides, petalLen):
'''Draws "flowers" with numPetals arranged around
a center point. Each petal is a polygon with
petalSides sides of length petalLen.'''
# solution
angle = 360.0/numPetals #angle to turn after each petal
for i in range(numPetals):
polygon(petalSides, petalLen)
lt(angle)
# Example 1: square petals
clear()
reset()
polyFlow(7,4,80)
# Example 2: pentagon petals
clear()
reset()
polyFlow(10,5,75)
# Example 3: hexagon petals
clear()
reset()
polyFlow(11,6,60)
Let's try to write together the recursive function spiral()
, which creates the images shown in the slide 13-6.
def spiral(sideLen, angle, scaleFactor, minLength):
'''sideLen is the length of the current side;
angle is the amount the turtle turns left to draw the next side;
scaleFactor is the multiplicative factor by which to scale the next side
(it is between 0.0 and 1.0);
minLength is the smallest side length that the turtle will draw.
'''
# solution
if sideLen >= minLength:
fd(sideLen)
lt(angle)
spiral(sideLen*scaleFactor, angle, scaleFactor, minLength)
To test the function with large values, we need to move the turtle from (0,0) in the center
of the canvas to some other point. However, whenever the turtle moves, it leaves a trail behind.
This is why we used the methods pu()
and pd()
(pen up and pen down).
To keep everything clean, we'll wrap it all in the function.
def drawSpiral(sideLen, angle, scaleFactor, minLength):
clear()
reset()
padding = 100
width = sideLen + padding
height = sideLen + padding
pu()
goto(-height/2+padding/2, -width/2+padding/2)
pd()
spiral(sideLen, angle, scaleFactor, minLength)
drawSpiral(625, 90, 0.7, 100)
Let's see the examples shown in slide 13-6:
drawSpiral(200,90,0.9,10)
drawSpiral(200,72,0.97,10)
drawSpiral(200,121,0.95,15)
A function is invariant relative to an object’s state if the state of the object is the same
before and after the function is invoked.
Let's write the following function together. It's very similar to spiral()
, we just need to
undo the operations we did before the recursion.
def spiralBack(sideLen, angle, scaleFactor, minLength):
'''Draws a spiral. The state of the turtle (position,
direction) after drawing the spiral should be the
same as before drawing the spiral. '''
# solution
if sideLen >= minLength:
fd(sideLen)
lt(angle)
spiralBack(sideLen*scaleFactor, angle, scaleFactor, minLength)
pu()
rt(angle)
bk(sideLen)
pd() # the last four lines retrace position without drawing
clear()
reset()
spiralBack(200,95,0.93,10)
How would we modify spiralBack
so it alternates colors on each line segment?
Modify the function and then try it below.
clear()
reset()
spiralBack(200,95,0.93,10, 'red', 'blue')
zigzag
to become invariant¶def zigzag(num, length):
'''Draws the specified number of zigzags with the specified length.'''
if num>0:
lt(45)
fd(length)
rt(90)
fd(length*2)
lt(90)
fd(length)
rt(45)
zigzag(num-1, length)
# solution
pu()
lt(45)
bk(length)
rt(90)
bk(length*2)
lt(90)
bk(length)
rt(45)
pd() # re-trace steps to get to invariant
clear()
reset()
zigzag(5,20)
Define the koch
and snowflake
functions as indicated on the slides.
def koch(levels, size):
# solution
if levels == 0:
fd(size)
else:
koch(levels-1, int(size/3))
lt(60)
koch(levels-1, int(size/3))
rt(120)
koch(levels-1, int(size/3))
lt(60)
koch(levels-1, int(size/3))
#invoke
clear()
reset()
koch(2, 150)
def snowflake(levels, size):
# solution: just make a triange of koch curves!
for i in range(3):
koch(levels, size)
rt(120)
#invoke
clear()
reset()
snowflake(2, 150)
Now we will use the idea of invariant functions to create more sofisticated turtle designs,
again using recursion. Slide 13-40 shows some designs created with the trees()
recursive function.
def tree(levels, trunkLen, angle, shrinkFactor):
'''levels is the number of branches on any path from the root to a leaf
trunkLen is the length of the base trunk of the tree
angle is the angle from the trunk for each subtree
shrinkFactor is the shrinking factor for each subtree'''
# solution
if levels > 0:
fd(trunkLen)
rt(angle) # move into direction to draw right sub-tree
tree(levels-1, trunkLen*shrinkFactor, angle, shrinkFactor)
lt(angle*2) # move into direction to draw left sub-tree
tree(levels-1, trunkLen*shrinkFactor, angle, shrinkFactor)
rt(angle) # move into starting direction
pu()
bk(trunkLen) # move into starting position
pd()
clear()
reset()
lt(90) # Face north first to grow the tree upwards
tree(7, 75, 30, 0.8)
def treeRandom(trunkLen, minLength, thickness, minThickness,
minAngle, maxAngle, minShrink, maxShrink):
if (trunkLen < minLength) or (thickness < minThickness): # Base case
pass # Do nothing
else:
angle1 = random.uniform(minAngle, maxAngle)
angle2 = random.uniform(minAngle, maxAngle)
shrink1 = random.uniform(minShrink, maxShrink)
shrink2 = random.uniform(minShrink, maxShrink)
pensize(thickness)
fd(trunkLen)
rt(angle1)
treeRandom(trunkLen*shrink1, minLength, thickness*shrink1,
minThickness, minAngle, maxAngle, minShrink, maxShrink)
lt(angle1 + angle2)
treeRandom(trunkLen*shrink2, minLength, thickness*shrink2,
minThickness, minAngle, maxAngle, minShrink, maxShrink)
rt(angle2)
pensize(thickness)
bk(trunkLen)
clear()
reset()
lt(90) # Face north first to grow the tree upwards
treeRandom(120, 3, 20, 1, 15, 55, 0.5, 0.8)
exitonclick() # close canvas