← Python functions and events

Optional: Control panel

Turn the game into an app with a panel of buttons made from lists.

Every app is a panel of controls over some state. Build the panel from data, and adding a control is one more item in a list.

Your game has one button. Real apps have panels: rows of controls that change what the program does. Here the buttons come from lists, so a loop draws them and a loop finds which one was clicked.

PredictRead the code and say what it will do before you run anything. A wrong guess is fine: it shows you what to look for.

Predict

panel.py
from turtle import *
from ui import inside, draw_button

speed(0)
penup()
hideturtle()

xs = [-120, 0, 120, 120, 0, -120]
ys = [70, 110, 70, -20, -50, -20]
place = 0
tx = 0
ty = 0
score = 0
showing = False
time_left = 30
playing = False
delay = 1000

names = ["New game", "Pause", "Go"]
bxs = [-190, -60, 70]


def draw_scene():
    clear()
    goto(-190, 160)
    write(score, False, "left", ("Arial", 20, "normal"))
    goto(150, 160)
    write(time_left, False, "left", ("Arial", 20, "normal"))
    for i in range(len(names)):
        draw_button(bxs[i], -190, 120, 40, names[i])
    goto(tx, ty)


def hop():
    global place, tx, ty, showing
    if playing:
        tx = xs[place]
        ty = ys[place]
        place = (place + 1) % len(xs)
        goto(tx, ty)
        showturtle()
        showing = True
        ontimer(hide, delay - 300)
        ontimer(hop, delay)


def hide():
    global showing
    if showing:
        showing = False
        hideturtle()


def tick():
    global time_left, playing
    if playing:
        time_left = time_left - 1
        if time_left == 0:
            playing = False
            hideturtle()
        else:
            ontimer(tick, 1000)
        draw_scene()


def go():
    global playing
    if playing == False and time_left > 0:
        playing = True
        hop()
        ontimer(tick, 1000)


def press(name):
    global score, time_left, place, playing
    if name == "New game":
        playing = False
        score = 0
        time_left = 30
        place = 0
        draw_scene()
        ontimer(go, 1100)
    if name == "Pause":
        playing = False
        hideturtle()
    if name == "Go":
        go()


def clicked(x, y):
    global score, showing
    if playing and showing and inside(x, y, tx - 20, ty - 20, 40, 40):
        score = score + 1
        showing = False
        hideturtle()
        draw_scene()
    for i in range(len(names)):
        if inside(x, y, bxs[i], -190, 120, 40):
            press(names[i])


draw_scene()
onscreenclick(clicked)
listen()
mainloop()

names and bxs hold one button each at the same index. draw_scene loops over them to draw, and clicked loops over them to check.

You click at x = 0 on the row of buttons. Which button is pressed?

RunRun it and compare the result with your prediction.

Run

Press New game, play, pause, then go again.

panel.py
from turtle import *
from ui import inside, draw_button

speed(0)
penup()
hideturtle()

xs = [-120, 0, 120, 120, 0, -120]
ys = [70, 110, 70, -20, -50, -20]
place = 0
tx = 0
ty = 0
score = 0
showing = False
time_left = 30
playing = False
delay = 1000

names = ["New game", "Pause", "Go"]
bxs = [-190, -60, 70]


def draw_scene():
    clear()
    goto(-190, 160)
    write(score, False, "left", ("Arial", 20, "normal"))
    goto(150, 160)
    write(time_left, False, "left", ("Arial", 20, "normal"))
    for i in range(len(names)):
        draw_button(bxs[i], -190, 120, 40, names[i])
    goto(tx, ty)


def hop():
    global place, tx, ty, showing
    if playing:
        tx = xs[place]
        ty = ys[place]
        place = (place + 1) % len(xs)
        goto(tx, ty)
        showturtle()
        showing = True
        ontimer(hide, delay - 300)
        ontimer(hop, delay)


def hide():
    global showing
    if showing:
        showing = False
        hideturtle()


def tick():
    global time_left, playing
    if playing:
        time_left = time_left - 1
        if time_left == 0:
            playing = False
            hideturtle()
        else:
            ontimer(tick, 1000)
        draw_scene()


def go():
    global playing
    if playing == False and time_left > 0:
        playing = True
        hop()
        ontimer(tick, 1000)


def press(name):
    global score, time_left, place, playing
    if name == "New game":
        playing = False
        score = 0
        time_left = 30
        place = 0
        draw_scene()
        ontimer(go, 1100)
    if name == "Pause":
        playing = False
        hideturtle()
    if name == "Go":
        go()


def clicked(x, y):
    global score, showing
    if playing and showing and inside(x, y, tx - 20, ty - 20, 40, 40):
        score = score + 1
        showing = False
        hideturtle()
        draw_scene()
    for i in range(len(names)):
        if inside(x, y, bxs[i], -190, 120, 40):
            press(names[i])


draw_scene()
onscreenclick(clicked)
listen()
mainloop()
Try it yourselfYour edits are saved in this browser.

InvestigateTrace how each line of code connects to what you see.

Investigate

panel.py
from turtle import *
from ui import inside, draw_button

speed(0)
penup()
hideturtle()

xs = [-120, 0, 120, 120, 0, -120]
ys = [70, 110, 70, -20, -50, -20]
place = 0
tx = 0
ty = 0
score = 0
showing = False
time_left = 30
playing = False
delay = 1000

names = ["New game", "Pause", "Go"]
bxs = [-190, -60, 70]


def draw_scene():
    clear()
    goto(-190, 160)
    write(score, False, "left", ("Arial", 20, "normal"))
    goto(150, 160)
    write(time_left, False, "left", ("Arial", 20, "normal"))
    for i in range(len(names)):
        draw_button(bxs[i], -190, 120, 40, names[i])
    goto(tx, ty)


def hop():
    global place, tx, ty, showing
    if playing:
        tx = xs[place]
        ty = ys[place]
        place = (place + 1) % len(xs)
        goto(tx, ty)
        showturtle()
        showing = True
        ontimer(hide, delay - 300)
        ontimer(hop, delay)


def hide():
    global showing
    if showing:
        showing = False
        hideturtle()


def tick():
    global time_left, playing
    if playing:
        time_left = time_left - 1
        if time_left == 0:
            playing = False
            hideturtle()
        else:
            ontimer(tick, 1000)
        draw_scene()


def go():
    global playing
    if playing == False and time_left > 0:
        playing = True
        hop()
        ontimer(tick, 1000)


def press(name):
    global score, time_left, place, playing
    if name == "New game":
        playing = False
        score = 0
        time_left = 30
        place = 0
        draw_scene()
        ontimer(go, 1100)
    if name == "Pause":
        playing = False
        hideturtle()
    if name == "Go":
        go()


def clicked(x, y):
    global score, showing
    if playing and showing and inside(x, y, tx - 20, ty - 20, 40, 40):
        score = score + 1
        showing = False
        hideturtle()
        draw_scene()
    for i in range(len(names)):
        if inside(x, y, bxs[i], -190, 120, 40):
            press(names[i])


draw_scene()
onscreenclick(clicked)
listen()
mainloop()

Which button does the loop find?

What if the lists disagree?

A click at x = 100, y = -170. Follow the loop in clicked.

ibxs[i]inside?
0-190no
1
2

You add a fourth name to names but forget to add a position to bxs. What happens?

ModifyChange the code on purpose and check the result.

Difficulty row

Add a second row: Easy, Medium and Hard. Each one sets how fast the turtle hops. Three more lists, two more loops.

Try it yourselfYour edits are saved in this browser.
Need a hint?

Three new lists: levels = ["Easy", "Medium", "Hard"], lxs = [-190, -60, 70], delays = [1300, 1000, 700].

Another hint

Copy the drawing loop in draw_scene, but use levels, lxs and y = -140.

Another hint

Copy the checking loop in clicked: when a level is clicked, delay = delays[i] (and add delay to the global line).

A second row of buttons from lists, which sets the difficulty.

    Run your program. This ticks when your code does all of the above.

    MakeBuild something new using what you have learned. Expect a few tries; that is normal.

    Your own controls

    Add a row of controls you design. Where this leads: in Gradio, you list the controls (gr.Button, gr.Radio) and Gradio draws them and checks the clicks for you. On a web page, HTML draws the buttons and JavaScript listens for clicks. You have just built what both of them do.

    Try it yourselfYour edits are saved in this browser.
    Need a hint?

    A theme row: names like "Day" and "Night" with a list of colours, and bgcolor(colours[i]) when one is clicked.

    Another hint

    A path chooser: two pairs of lists, and a button that swaps which pair xs and ys use.

    For teachers

    An optional lesson in the user interface direction. Students turn the game into an app with rows of buttons built from lists, so adding a control means adding to a list. About 45 minutes.

    Open the teacher guide: answers, solutions, slides and a worksheet.

    Students will be able to

    Before class

    The big idea: buttons are data. Three lists (the names, the positions, the values) and two loops (one draws the buttons, one checks which was clicked) replace a pile of copied if statements. "New game" waits 1.1 seconds before starting: that lets any timer still running from the last game stop first, so two games never run at once.

    Running the lesson

    1. Predict: the Pause button covers x from -60 to 60, so a click at x = 0 presses Pause.
    2. Investigate: follow the loop for a click at x = 100. Only the third button matches.
    3. Ask: what goes wrong if names has four items but bxs has three? (The loop asks for a position that does not exist.)
    4. Modify: the difficulty row is the same pattern again: three lists, a drawing loop and a checking loop.
    5. Make: students design a row of their own. A colour theme with bgcolor is a good first choice.

    The hard part (on purpose)

    Controls stored as lists that must stay the same length. Ask them to count each list's items when an IndexError appears.

    When students get stuck

    Stretch and support

    Stretch: Add a row that chooses between two different paths (two pairs of lists) for the turtle.

    Support: Give the difficulty row's three lists already written; students add only the two loops.

    Where it leads

    Gradio lets you list controls (gr.Button, gr.Radio) and draws them and checks clicks for you. On a web page, HTML draws the buttons and JavaScript listens for clicks. Students have built what both do.

    What each step is for

    Now you can
    Next lesson12. Optional: Game levelsTurn the game into a real game with screens, levels and a turtle that moves.