Introduction to Turtle and Graphical Recursion
Contents
Introduction to Turtle and Graphical Recursion¶
Turtle is a graphics module first introduced in the 1960s by South African-born computer scientist Seymour Papert. It uses a programmable cursor, fondly referred to as the “turtle” to draw on a Cartesian plane (x and y axis.) Helpful documentation on the built-in Python module for turtle can be found here. Below, we cover the basics that are essential for the course.
Note: you may need to re-run some cells a couple times for the Jupyter notebook to properly clear the Turtle window.
from turtle import *
# set up a 400x400 turtle window
setup(400, 400)
reset()
# move the turtle forward 100 pixels
fd(100)
# turn the turtle 90 degrees to the left
lt(90)
# move forward another 100 pixels
fd(100)
# complete a square
lt(90)
fd(100)
lt(90)
fd(100)
done()
# we can use this for the following examples to set up the basic environment for our turtle
def initializeTurtle():
setup(500, 500)
reset()
# shift the turtle slightly to get a centered figure
up()
goto(-40, -40)
down()
shape("turtle")
Drawing basic shapes with turtle¶
Rather than manually writing the steps to draw a square each time, we can write a function that takes as input the length of the square and draws it for us.
def drawSquare(length):
# a loop that runs 4 times
# and draws each side of the square
for i in range(4):
fd(length)
lt(90)
done()
setup(400, 400)
reset()
drawSquare(150)
---------------------------------------------------------------------------
Terminator Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 setup(400, 400)
2 reset()
3 drawSquare(150)
File <string>:5, in setup(width, height, startx, starty)
Terminator:
Here’s a more general function to draw arbtitrary polygons instead of squares.
def drawPolygon(length, numSides):
for i in range(numSides):
fd(length)
lt(360/numSides)
done()
initializeTurtle()
drawPolygon(80, 8)
Adding some color!¶
Here’s how we can add some color to our shapes in turtle!
def drawPolygonColor(length, numSides, color):
# set the color we want to fill the shape with
# color is a string
fillcolor(color)
begin_fill()
for i in range(numSides):
fd(length)
lt(360/numSides)
end_fill()
done()
initializeTurtle()
drawPolygonColor(80, 8, "blue")
---------------------------------------------------------------------------
Terminator Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 initializeTurtle()
2 drawPolygonColor(80, 8, "blue")
Input In [3], in initializeTurtle()
2 def initializeTurtle():
----> 3 setup(500, 500)
4 reset()
5 # shift the turtle slightly to get a centered figure
File <string>:5, in setup(width, height, startx, starty)
Terminator:
Concentric Circles¶
def initializeTurtleForCircles():
setup(1200, 1200)
reset()
speed(10)
up()
goto(0, -300)
down()
shape("turtle")
def concentricCirclesWrong(radius, gap):
# base case, don't draw anything, return 0
if radius < gap:
return 0
else:
# tell the turtle draw a circle
circle(radius)
# recursive function call; draw smaller circles
num = concentricCirclesWrong(radius-gap, gap)
# we drew one circle in this step, plus however many we
# drew recursively, so return 1 + num
return 1 + num
initializeTurtleForCircles()
print("Num Circles:", concentricCirclesWrong(300, 30))
#hideturtle()
done()
Num Circles: 10
We need to move the turtle’s starting point to draw the concentric circles correctly.
def concentricCircles(radius, gap):
# base case, don't draw anything, return 0
if radius < gap:
return 0
else:
# pen down, draw circle
down()
circle(radius)
# pen up, ensure the turtle doesn't draw while repositioning
up()
# reposition the turtle for the next circle
lt(90)
fd(gap)
rt(90)
# recursive function call; draw smaller circles
num = concentricCircles(radius-gap, gap)
# we drew one circle in this step, plus however many we
# drew recursively, so return 1 + num
return 1 + num
initializeTurtleForCircles()
print("Num Circles:", concentricCircles(300, 30))
hideturtle()
done()
---------------------------------------------------------------------------
Terminator Traceback (most recent call last)
Input In [14], in <cell line: 1>()
----> 1 initializeTurtleForCircles()
2 print("Num Circles:", concentricCircles(300, 30))
3 hideturtle()
Input In [10], in initializeTurtleForCircles()
1 def initializeTurtleForCircles():
----> 2 setup(1200, 1200)
3 reset()
4 speed(10)
File <string>:5, in setup(width, height, startx, starty)
Terminator:
Now let’s add some color!
def drawDisc(radius, color):
"""
Draw circle of a given radius and fill it with color
"""
# put the pen down
down()
# set the color
fillcolor(color)
# draw the circle
begin_fill()
circle(radius)
end_fill()
# put the pen up
up()
def concentricCirclesColor(radius, gap, colorOuter, colorInner):
"""
Recursive function to draw concentric circles with
alternating colors
"""
# base case, don't draw anything, return 0
if radius < gap:
return 0
else:
drawDisc(radius, colorOuter)
lt(90)
fd(gap)
rt(90)
num = concentricCirclesColor(radius-gap, gap, colorInner, colorOuter)
return 1 + num
initializeTurtleForCircles()
print("Num circles:", concentricCirclesColor(300, 30, "gold", "purple"))
hideturtle()
done()
Num circles: 10
Finally, let’s write a version that maintains the function’s invariance.
def concentricCirclesInvariant(radius, gap, colorOuter, colorInner):
"""
Recursive function to draw concentric circles with alternating
color
"""
# base case, don't draw anything, return 0
if radius < gap:
return 0
else:
drawDisc(radius, colorOuter)
lt(90)
fd(gap)
rt(90)
num = concentricCirclesInvariant(radius-gap, gap, colorInner, colorOuter)
# move turtle back to starting position
lt(90)
bk(gap)
rt(90)
return 1 + num
initializeTurtleForCircles()
print("Num circles:", concentricCirclesInvariant(300, 30, "gold", "purple", 2))
hideturtle()
done()
---------------------------------------------------------------------------
Terminator Traceback (most recent call last)
Input In [19], in <cell line: 1>()
----> 1 initializeTurtleForCircles()
2 print("Num circles:", concentricCirclesInvariant(300, 30, "gold", "purple", 2))
3 hideturtle()
Input In [10], in initializeTurtleForCircles()
1 def initializeTurtleForCircles():
----> 2 setup(1200, 1200)
3 reset()
4 speed(10)
File <string>:5, in setup(width, height, startx, starty)
Terminator:
Nested Circles¶
def nestedCircles(radius, minRadius, colorOut, colorAlt):
if radius < minRadius:
return 0
else:
# contribute to the solution
drawDisc(radius, colorOut)
# save half of radius
halfRadius = radius/2
# position the turtle to draw right subcircle
lt(90); fd(halfRadius); rt(90); fd(halfRadius)
# draw right subcircle recursively
right = nestedCircles(halfRadius, minRadius, colorAlt, colorOut)
# position turtle for left subcircle
bk(radius)
# draw left subcircle recursively
left = nestedCircles(halfRadius, minRadius, colorAlt, colorOut)
# bring turtle back to start position
fd(halfRadius); lt(90); bk(halfRadius); rt(90)
# return total number of circles drawn
return 1 + right + left
initializeTurtleForCircles()
print("Num circles:", nestedCircles(300, 30, "gold", "purple"))
hideturtle()
done()
Num circles: 15
Try the above example again without the final set of instructions to return the turtle to its starting state. This is why invariance matters! We must always make sure the turtle returns to its starting position and state.