c10

Below are exercises from chapter 10 of How to Think Like a Computer Scientist: Learning with Python 3. The text in italics comes from the textbook, and the code (except where it is obviously part of the textbook's prompt) is my solutions.

10.6.1 -- Arbitrary addition to a stoplight simulator
...

Add some new key bindings to the first sample program:

  • Pressing keys R, G or B should change tess’ color to Red, Green or Blue.
  • Pressing keys + or - should increase or decrease the width of tess’ pen. Ensure that the pen size stays between 1 and 20 (inclusive).
  • Handle some other keys to change some attributes of tess, or attributes of the window, or to give her new behaviour that can be controlled from the keyboard.

We can do this like so. Note that decreasing or increasing the pen size has no visible effect on what's going on on the screen visually. I tested it with some print statements earlier, but pulled them out later. It's basically a useless feature, other than as an exercise. Just for giggles, I added functions to shrink and grow tess, and bound them to the s and d keys.

"""Tess becomes a traffic light."""
import turtle

def draw_housing():
    """Draw a nice housing to hold the traffic lights."""
    tess.pensize(3)
    tess.color("black", "darkgrey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()

def get_tess_situated():
    """Turn tess into an appropriately placed green light."""
    tess.penup()
    # Position tess onto the place where the green light should be
    tess.forward(40)
    tess.left(90)
    tess.forward(50)
    # Turn tess into a big green circle
    tess.shape("circle")
    tess.shapesize(3)
    tess.fillcolor("green")

# A traffic light is a kind of state machine with three states,
# Green, Orange, Red.  We number these states  0, 1, 2
# When the machine changes state, we change tess' position and
# her fillcolor.

def advance_state_machine():
    """Move a traffic light along."""
    global state_num
    if state_num == 0:       # Transition from state 0 to state 1
        tess.forward(70)
        tess.fillcolor("orange")
        state_num = 1
    elif state_num == 1:     # Transition from state 1 to state 2
        tess.forward(70)
        tess.fillcolor("red")
        state_num = 2
    else:                    # Transition from state 2 to state 0
        tess.back(140)
        tess.fillcolor("green")
        state_num = 0

def tess_blue():
    """Turn tess blue."""
    tess.fillcolor("blue")

def tess_green():
    """Turn tess green."""
    tess.fillcolor("green")

def tess_red():
    """Turn tess red."""
    tess.fillcolor("red")

def tess_minus():
    """Shrink tess's pen."""
    if tess.pensize() >= 2:
        tess.pensize(tess.pensize() - 1)

def tess_plus():
    """Make tess's pen larger."""
    if tess.pensize() <= 19:
        tess.pensize(tess.pensize() + 1)

def tess_shrink():
    """Make tess smaller."""
    temp = tess.shapesize()
    tess.shapesize(temp[0] * .9, temp[1] * .9, temp[2] * .9)

def tess_grow():
    """Make tess larger."""
    temp = tess.shapesize()
    tess.shapesize(temp[0] / .9, temp[1] / .9, temp[2] / .9)

# Set up a screen and a turtle to work with.
turtle.setup(400, 500)
wn = turtle.Screen()
wn.title("Tess becomes a traffic light!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()

draw_housing()

get_tess_situated()

# This variable holds the current state of the machine
state_num = 0

# Bind the event handler to various possible inputs.

# Traffic light hops through its states as space bar is pressed.
wn.onkey(advance_state_machine, "space")
# Tess's pensize shrinks when minus key is pressed.
wn.onkey(tess_minus, "minus")
# Tess's pensize grows when plus key is pressed.
wn.onkey(tess_plus, "plus")
# Tess turns red when r is pressed.
wn.onkey(tess_red, "r")
# Tess turns green when g is pressed.
wn.onkey(tess_green, "g")
# Tess turns blue when b is pressed.
wn.onkey(tess_blue, "b")
# Tess gets smaller when s is pressed.
wn.onkey(tess_shrink, "s")
# Tess gets larger when d is pressed.
wn.onkey(tess_grow, "d")

wn.listen()          # Listen for events
wn.mainloop()        # Keep window open till user closes it.

10.6.2 -- Make traffic light work automatically
...

Change the traffic light program so that changes occur automatically, driven by a timer.

A solution might look something like this:

import turtle

# A traffic light is a kind of state machine with three states,
# Green, Orange, Red.  We number these states  0, 1, 2
# When the machine changes state, we change tess's position and
# her fillcolor.
def advance_state_machine():
    """Move the stoplight (tess) to the next position.

    A traffic light is a kind of state machine with three
    states: Green, Orange, Red. We number these states
    0, 1, 2. When the machine changes states, we change tess's
    position and her fillcolor. Once this function is called,
    it executes once per second.
    """
    global state_num
    if state_num == 0:       # Transition from state 0 to state 1
        tess.forward(70)
        tess.fillcolor("orange")
        state_num = 1
    elif state_num == 1:     # Transition from state 1 to state 2
        tess.forward(70)
        tess.fillcolor("red")
        state_num = 2
    elif state_num == 2:       # Transition from state 2 to state 3
        tess.back(140)
        tess.fillcolor("green")
        state_num = 0
    else:
        print("There's been a terrible error in the state machine.")

    wn.ontimer(advance_state_machine, 1000)

def draw_housing():
    """Draw a nice housing to hold a traffic light."""
    tess.pensize(3)
    tess.color("black", "darkgrey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()

# Set up a turtle and a screen to work with.
turtle.setup(400, 500)
wn = turtle.Screen()
wn.title("Tess becomes a traffic light!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()

draw_housing()

tess.penup()
# Position tess onto the place where the green light should be.
tess.forward(40)
tess.left(90)
tess.forward(50)

# Turn tess into a big green circle.
tess.shape("circle")
tess.shapesize(3)
tess.fillcolor("green")

# This variable holds the current state of the machine.
# 0 represents a green light, 1 an orange light, 2 red.
state_num = 0

# Bind the event handler to a timer.
wn.ontimer(advance_state_machine, 1000)

wn.listen()                      # Listen for events
wn.mainloop()

10.6.3 -- Two philosophies of traffic lights
...

In an earlier chapter we saw two turtle methods, hideturtle and showturtle, that can hide or show a turtle. This suggests that we could take a different approach to the traffic lights program. Add to your program above as follows: draw a second housing for another set of traffic lights. Create three separate turtles to represent each of the green, orange and red lights, and position them appropriately within your new housing. As your state changes occur, just make one of the three turtles visible at any time. Once you’ve made the changes, sit back and ponder some deep thoughts: you’ve now got two different ways to use turtles to simulate the traffic lights, and both seem to work. Is one approach somehow preferable to the other? Which one more closely resembles reality — i.e. the traffic lights in your town?

I will leave the pondering for more philosophical minds, and who knows whether my town's traffic lights match yours? But as far as code goes, this will work:

import turtle

def draw_housing():
    """Draw a nice housing to hold the traffic lights."""
    tess.pensize(3)
    tess.color("black", "darkgrey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()

def advance_state_machine():
    """Move stoplight on to the next state.

    States 0, 1, 2 correspond to green, yellow, red
    in a standard traffic light.
    """
    global state_num
    if state_num == 0:       # Transition from state 0 to state 1
        green_light.hideturtle()
        tess.forward(70)
        yellow_light.showturtle()
        tess.fillcolor("orange")
        state_num = 1
    elif state_num == 1:     # Transition from state 1 to state 2
        yellow_light.hideturtle()
        tess.forward(70)
        red_light.showturtle()
        tess.fillcolor("red")
        state_num = 2
    elif state_num == 2:       # Transition from state 2 to state 3
        red_light.hideturtle()
        tess.back(140)
        tess.fillcolor("green")
        state_num = 0
        green_light.showturtle()
    else:
        print("Uh oh. Problem in the state machine.")

    wn.ontimer(advance_state_machine, 1000)

# Set up turtles and screen to work with.
# Tess will be the turtle who moves background
# and plays all three roles (red, green, yellow light)
# in the stoplight on the right. Three other turtles, with
# obvious names, will serve the left stoplight.

# Set up tess.
turtle.setup(400, 500)
wn = turtle.Screen()
wn.title("Tess becomes a traffic light!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()

# Set up tess's three counterparts.
green_light = turtle.Turtle()
yellow_light = turtle.Turtle()
red_light = turtle.Turtle()

# Make them the appropriate colors and such
green_light.shape("circle")
yellow_light.shape("circle")
red_light.shape("circle")

green_light.shapesize(3)
yellow_light.shapesize(3)
red_light.shapesize(3)

green_light.fillcolor("green")
yellow_light.fillcolor("orange")
red_light.fillcolor("red")

# The three lights shouldn't draw anything, so
# let's preemptively stop them.
green_light.penup()
yellow_light.penup()
red_light.penup()

# Move them all back toward the left housing.
green_light.forward(-110)
yellow_light.forward(-110)
red_light.forward(-110)

# Turn them all to face upward.
green_light.left(90)
yellow_light.left(90)
red_light.left(90)

# Walk them to their places
green_light.forward(50)
yellow_light.forward(120)
red_light.forward(190)

# Draw the housings and move tess into position.
draw_housing()

tess.penup()
tess.forward(-150)
tess.pendown()

draw_housing()
tess.penup()
tess.forward(150)

tess.penup()
# Position tess onto the place where the green light should be
tess.forward(40)
tess.left(90)
tess.forward(50)
# Turn tess into a big green circle
tess.shape("circle")
tess.shapesize(3)
tess.fillcolor("green")

# A traffic light is a kind of state machine with three states,
# Green, Orange, Red.  We number these states  0, 1, 2
# When the machine changes state, we change tess' position and
# her fillcolor.
# This variable holds the current state of the machine.
state_num = 0

# Bind the event handler to a timer.
wn.ontimer(advance_state_machine(), 1000)

wn.listen()                      # Listen for events
wn.mainloop()

10.6.4 -- Make the dim lights show
...

Now that you’ve got a traffic light program with different turtles for each light, perhaps the visibility / invisibility trick wasn’t such a great idea. If we watch traffic lights, they turn on and off — but when they’re off they are still there, perhaps just a darker color. Modify the program now so that the lights don’t disappear: they are either on, or off. But when they’re off, they’re still visible.

The way this wording is written seems to presuppose that we're only working with the three-turtle traffic light. So let's alter the code to only have that one traffic light. And then the basic mechanism here is to replace "showturtle" and "hideturtle" with color changes. One could probably fiddle indefinitely with the colors, but this is enough to show the basic idea:

import turtle

def draw_housing():
    """Draw a nice housing to hold the traffic lights."""
    tess.pensize(3)
    tess.color("black", "Dim Grey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()

def advance_state_machine():
    """Move stoplight on to the next state.

    States 0, 1, 2 correspond to green, yellow, red
    in a standard traffic light.
    """
    global state_num
    if state_num == 0:       # Transition from 'green' to 'yellow'
        green_light.color("Dark Olive Green")
        yellow_light.color("Orange")
        state_num = 1
    elif state_num == 1:     # Transition from 'yellow' to 'red'
        yellow_light.color("Sienna")
        red_light.color("red")
        state_num = 2
    elif state_num == 2:       # Transition from 'red' to 'green'
        red_light.color("Brown")
        state_num = 0
        green_light.color("Lime Green")
    else:
        print("Uh oh. Problem in the state machine.")

    wn.ontimer(advance_state_machine, 1000)

# Set up turtles and screen to work with.
# Tess will draw the housing, and
# turtles with obvious names will serve as lights.

# Set up tess.
turtle.setup(400, 500)
wn = turtle.Screen()
wn.title("Tess becomes a traffic light!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()

# Draw the housing and move tess into position.
tess.penup()
tess.forward(-40)
tess.pendown()

draw_housing()
tess.penup()
tess.hideturtle()

# Set up tess's three counterparts.
green_light = turtle.Turtle()
yellow_light = turtle.Turtle()
red_light = turtle.Turtle()

# Make them the appropriate colors and such
green_light.shape("circle")
yellow_light.shape("circle")
red_light.shape("circle")

green_light.shapesize(3)
yellow_light.shapesize(3)
red_light.shapesize(3)

green_light.fillcolor("Dark Olive Green")
yellow_light.fillcolor("Sienna")
red_light.fillcolor("Brown")

# The three lights shouldn't draw anything, so
# let's preemptively stop them.
green_light.penup()
yellow_light.penup()
red_light.penup()

# Turn them all to face upward.
green_light.left(90)
yellow_light.left(90)
red_light.left(90)

# Walk them to their places
green_light.forward(50)
yellow_light.forward(120)
red_light.forward(190)

# A traffic light is a kind of state machine with three states,
# Green, Orange, Red.  We number these states  0, 1, 2
# When the machine changes state, we change tess' position and
# her fillcolor.
# This variable holds the current state of the machine.
state_num = 0

# Bind the event handler to a timer.
wn.ontimer(advance_state_machine(), 1000)

wn.listen()                      # Listen for events
wn.mainloop()

10.6.5 -- Please an eccentric billionaire
...

Your traffic light controller program has been patented, and you’re about to become seriously rich. But your new client needs a change. They want four states in their state machine: Green, then Green and Orange together, then Orange only, and then Red. Additionally, they want different times spent in each state. The machine should spend 3 seconds in the Green state, followed by one second in the Green+Orange state, then one second in the Orange state, and then 2 seconds in the Red state. Change the logic in the state machine.

Presumably there's some very unusual municipality experimenting with something here. Very well. There's basically two changes we need to make. We add in a new state, which illuminates green plus orange. Then we use a variable, time_to_change, which will determine how long each state holds. We'll use that variable at the end of the advance_state_machine function to time when the function calls itself.

"""Exercise 10.6.5 from a textbook.

_How to Think Like a Computer Scientist: Learning with Python 3_
"""
import turtle

def draw_housing():
    """Draw a nice housing to hold the traffic lights."""
    tess.pensize(3)
    tess.color("black", "Dim Grey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()

def advance_state_machine():
    """Move stoplight on to the next state.

    States 0, 1, 2, 3 correspond to green, green+orange, orange, red
    in our modified traffic light system.
    """
    global state_num
    # Transition from 'green' to 'green plus orange'.
    if state_num == 0:
        orange_light.color("Orange")
        state_num = 1
        time_to_change = 1000
    # Transition from 'green plus orange' to 'orange'.
    elif state_num == 1:
        green_light.color("Dark Olive Green")
        state_num = 2
        time_to_change = 1000
    elif state_num == 2:     # Transition from 'orange' to 'red'
        orange_light.color("Sienna")
        red_light.color("red")
        state_num = 3
        time_to_change = 2000
    elif state_num == 3:       # Transition from 'red' to 'green'
        red_light.color("Brown")
        state_num = 0
        green_light.color("Lime Green")
        time_to_change = 3000
    else:
        print("Uh oh. Problem in the state machine.")

    wn.ontimer(advance_state_machine, time_to_change)

# Set up turtle and screen to work with.
# Tess will draw the housing, and
# turtles with obvious names will serve as lights.

# Set up tess.
turtle.setup(400, 500)
wn = turtle.Screen()
wn.title("Tess becomes a traffic light!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()

# Draw the housing and move tess into position.
tess.penup()
tess.forward(-40)
tess.pendown()

draw_housing()
tess.penup()
tess.hideturtle()

# Set up tess's three counterparts.
green_light = turtle.Turtle()
orange_light = turtle.Turtle()
red_light = turtle.Turtle()

# Make them the appropriate colors and such
green_light.shape("circle")
orange_light.shape("circle")
red_light.shape("circle")

green_light.shapesize(3)
orange_light.shapesize(3)
red_light.shapesize(3)

green_light.fillcolor("Dark Olive Green")
orange_light.fillcolor("Sienna")
red_light.fillcolor("Brown")

# The three lights shouldn't draw anything, so
# let's preemptively stop them.
green_light.penup()
orange_light.penup()
red_light.penup()

# Turn them all to face upward.
green_light.left(90)
orange_light.left(90)
red_light.left(90)

# Walk them to their places
green_light.forward(50)
orange_light.forward(120)
red_light.forward(190)

# A traffic light is a kind of state machine with three states,
# Green, Orange, Red.  We number these states  0, 1, 2
# When the machine changes state, we change tess' position and
# her fillcolor.
# This variable holds the current state of the machine.
state_num = 0

# Bind the event handler to a timer.
wn.ontimer(advance_state_machine(), 1000)

wn.listen()                      # Listen for events
wn.mainloop()

This page is released under the GNU Free Documentation License, any version 1.3 or later produced by the Free Software Foundation. I am grateful to the authors of the original textbook for releasing it that way: Peter Wentworth, Jeffrey Elkner, Allen B. Downey, and Chris Meyers.