Skip to main content

Stage 5: Track Score and Progress

Course progressStage 5 of 10
~65 min
One game, one Trinket

Keep building in the workspace on the right.

This stage is part of the same Crewmate Task Dash project you started in Setup. Type each new code block into the Trinket rail and keep building on the last stage.

Build

task collection, score, and a HUD

Learn

how variables update when the game state changes

Ship

visible progress toward winning

Before you start

Make sure you finished Stage 4: Add Tasks to Collect. Your Trinket project should already have four task stations drawn around the ship from a list of (x, y) tuples. Open the same file you used last stage and keep building from there.

The big idea

A HUD turns movement into feedback. When the player completes a task, the screen should prove it immediately.

New words
HUD
on-screen information like score or task count
global
a keyword that lets a function update a variable outside itself
remaining
the items that are still left after a check
score
a number that rewards player progress
string
text wrapped in quotes, like `"Caught!"` or `"Score:"`.
f-string
a string that drops variables right into the text using `f"... {name} ..."`.

Python concept

Trace it

State

What it means: State is the information a program remembers while it runs.

Tiny Python example:

score = 0
score = score + 50

In this game: `score`, `tasks_done`, and `tasks` are game state. They change as the player completes tasks.

Why it matters: A game without state cannot remember progress. State is what lets the HUD, win condition, and score respond to the player.

Think first

Check: State

Is `score = score + 50` creating a brand-new score or updating remembered state?

Check your thinking

It updates remembered state. The old score is used to calculate the new score.

Finished game target
Tasks 2/4Score 80
Crewmate playerTask stationShadow chaser

The player moves through the ship, collects tasks, and avoids the chaser. All playable shapes are drawn with Python Turtle code.

Build it

Think first

Why global?

Why does `check_tasks()` need `global tasks_done, score, tasks`?

Check your thinking

Because the function changes variables that were created outside the function.

Your turn

Type, run, test

Read the code aloud before you run it. The goal is to understand what changed in the game.
Need a hint?

Add the new code to the same Trinket project. Keep previous stage code unless the stage says to replace a function.

Your turn

Step 1 - Create state variables

`tasks_done` and `score` are game state. They remember what has happened so far, between every key press and every frame of the game loop. Without state, every collection would feel like the first one because the game would have forgotten the rest.

tasks_done = 0
score = 0

You should see — nothing visible changes from these two lines alone. Like the boundary variables in Stage 1, they are storage — they only matter once `update_hud()` reads them and the collision code writes to them.

Need a hint?

Type and run one step at a time. If this step breaks, fix it before adding the next one.

Your turn

Step 2 - Write the HUD from variables

The HUD should not guess. It reads the current variable values and displays them directly, every time it is asked to refresh. `clear()` wipes the old text first so the new numbers do not pile on top of the old ones.

You should see — a line of text appears near the top of the screen: 'Tasks: 0/4 Score: 0'. The HUD always matches the variables, so if the numbers ever look wrong, the variables are the place to investigate, not the HUD code.

Your turn

Step 3 - Keep only remaining tasks

`remaining` starts empty. The loop walks through `tasks` and copies the uncollected ones into `remaining`. The collected ones are simply not copied — at the end of the loop, `tasks = remaining` swaps in the shorter list. Building a new list is safer than deleting from a list you are still looping through.

You should see — walk the player onto a task. The task dot disappears, the HUD jumps to 'Tasks: 1/4', and walking back over the same spot does nothing — that task is no longer in `tasks`, so the loop cannot find it again.

Active coding checkpoint

Your turn

Fix the HUD update

This code changes the score, but the player will not see the new number yet. Add the missing line.

score = score + 50
tasks_done = tasks_done + 1
____________________
Need a hint?

Try this before opening the solution. Type the starter code, then fill in or fix the missing part yourself.

Stuck? Compare carefully
Answer check
Debug compare only

main.py

Changing state and showing state are separate jobs. `update_hud()` redraws the visible score after the variables change.

score = score + 50
tasks_done = tasks_done + 1
update_hud()
Python code task
Full stage code

main.py

Use this as the stage target after you understand the smaller steps. Add it to your current Trinket file.

tasks_done = 0
score = 0

hud = turtle.Turtle()
hud.hideturtle()
hud.penup()
hud.color("white")

def update_hud():
hud.clear()
hud.setposition(0, TOP - 45)
hud.write(f"Tasks: {tasks_done}/{len(tasks) + tasks_done} Score: {score}", align="center", font=("Arial", 16, "bold"))

def check_tasks():
global tasks_done, score, tasks
remaining = []
for task in tasks:
if player.distance(task) < 28:
tasks_done += 1
score += 50
else:
remaining.append(task)
tasks = remaining
update_hud()

update_hud()
Trace it

Trace the idea

  1. `remaining` starts empty each check.
  2. Collected tasks do not get copied into `remaining`.
  3. The HUD clears and rewrites after changes.

Understand it

Trace it

Why this code works

  • The score is not drawn directly into the game logic. First the value changes, then `update_hud()` redraws what the player sees.
  • `player.distance(task) < 28` turns positions into a yes/no collection question.
  • Picture the alternative: deleting tasks straight out of `tasks` while the `for` loop is still walking it. Python loses track of positions in the shrinking list and either skips items or repeats them. Building a fresh `remaining` list sidesteps the whole problem — the loop reads the old list while we write the new one, and they never collide.

If it breaks

Trace it

Troubleshooting wisdom

If the score never goes up, `check_tasks()` is never running. Make sure something calls it — either after every movement or inside `game_loop` once that loop exists. Without the call, the collision math sits in the function and never executes.

If a task lets you collect it twice, the swap line is missing. After the loop builds `remaining`, the line `tasks = remaining` has to run so the old list gets replaced. Without that line, the collected task is still in `tasks` and your next pass over the same spot collects it again.

If Python prints `UnboundLocalError` or refuses to assign to a variable, the `global` line is missing one of the names. List every variable the function writes to: `global tasks_done, score, tasks`. Use the traceback primer from Stage 3 — the last line of the error names the exact variable Python is confused about.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first
Change the collection radius from `28` to `80`. Predict how easy task collection becomes.
Compare
Try scores of 10, 50, and 100 per task. Which makes the HUD feel most rewarding?
Connect
Why does the HUD make the project feel more like a game and less like a drawing?

Test your stage

  • A HUD appears near the top.
  • Touching a task increases score.
  • Collected tasks stop counting again.