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.