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.