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 500x500 turtle window
setup(400, 400)
reset()
fd(100) # move the turtle forward 100 pixels
lt(90) # turn the turtle 90 degrees to the left
fd(100) # move forward another 100 pixels
# complete a square
lt(90)
fd(100)
lt(90)
fd(100)
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)
setup(400, 400)
reset()
drawSquare(150)
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)
setup(500, 500)
reset()
# shift the turtle slightly to get a centered figure
up()
goto(-40, -40)
down()
drawPolygon(80, 3)
shape("turtle")
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()
setup(500, 500)
reset()
up()
goto(-40, -40)
down()
drawPolygonColor(80, 10, "purple")
Concentric Circles¶
def concentricCirclesWrong(radius, thickness):
# base case do nothing
if radius < thickness:
pass
else:
# tell the turtle to start drawing and draw a circle
circle(radius)
# recursive function call
concentricCirclesWrong(radius-thickness, thickness)
setup(1200, 1200)
reset()
speed(10)
up()
goto(0, -300)
down()
concentricCirclesWrong(300, 30)
hideturtle()
We need to move the turtle’s starting point to draw the concentric circles correctly.
def concentricCircles(radius, thickness):
# base case do nothing
if radius < thickness:
pass
else:
# tell the turtle to start drawing and draw a circle
down()
circle(radius)
# reposition the turtle for the next circle
up() # ensure the turtle doesn't draw while repositioning
lt(90)
fd(thickness)
rt(90)
# recursive function call
concentricCircles(radius-thickness, thickness)
setup(1200, 1200)
reset()
speed(10)
up()
goto(0, -300)
down()
concentricCircles(300, 30)
hideturtle()
Now let’s add some color!
def drawDisc(radius, color):
"""
Draw circle of a given radius
and fill it with a given 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, thickness, colorOuter, colorInner):
"""
Recursive function to draw concentric circles with alternating
color
"""
if radius < thickness:
pass
else:
drawDisc(radius, colorOuter)
lt(90)
fd(thickness)
rt(90)
concentricCirclesColor(radius-thickness, thickness, colorInner, colorOuter)
setup(1200, 1200)
reset()
speed(10)
up()
goto(0, -300)
down()
concentricCirclesColor(300, 30, "gold", "purple")
hideturtle()
Finally, let’s write a version that maintains the function’s invariance.
def drawDisc(radius, color):
"""
Draw circle of a given radius
and fill it with a given 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 concentricCirclesInvariant(radius, thickness, colorOuter, colorInner):
"""
Recursive function to draw concentric circles with alternating
color
"""
if radius < thickness:
pass
else:
drawDisc(radius, colorOuter)
lt(90)
fd(thickness)
rt(90)
concentricCirclesInvariant(radius-thickness, thickness, colorInner, colorOuter)
# move turtle back to starting position
lt(90)
bk(thickness)
rt(90)
setup(1200, 1200)
reset()
speed(10)
up()
goto(0, -300)
down()
concentricCirclesInvariant(300, 30, "gold", "purple")
hideturtle()
Nested Circles¶
def drawDisc(radius, 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 nestedCircles(radius, minRadius, colorOut, colorAlt):
if radius < minRadius:
pass # do nothing
else:
# contribute to the solution
drawDisc(radius, colorOut)
# save half of radius
halfRadius = radius/2
# position the turtle at the right place
lt(90); fd(halfRadius); rt(90); fd(halfRadius)
# draw right subcircle recursively
nestedCircles(halfRadius, minRadius, colorAlt, colorOut)
# position turtle for left subcircle
bk(radius)
# draw left subcircle recursively
nestedCircles(halfRadius, minRadius, colorAlt, colorOut)
# bring turtle back to start position
fd(halfRadius); lt(90); bk(halfRadius); rt(90)
setup(1200, 1200)
reset()
speed(5)
up()
goto(0, -300)
down()
nestedCircles(300, 30, "gold", "purple")
hideturtle()
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.