Skip to main content

Stage 6: Detect Hits and Score Points

Course progressStage 6 of 10
~45 min
One game, one Trinket

Keep building in your saved Python Arcade project.

This stage is part of the same game you started in Setup. Do the work in your own remixed Trinket — it’s the workspace on the right of this page.

Build

collision detection, a score readout, and hit cleanup

Learn

how two game systems finally notice each other

Ship

a game where shooting an alien actually matters

The big idea

Until now, lasers and aliens have been strangers. They live in separate lists, get updated by separate loops, and never know the other exists. Today the two systems finally talk to each other — and the moment they do, the game gets its first real purpose. Pressing space stops being a button you mash; it becomes a button you aim.

The tool we use is collision detection. A collision is what we call it when two objects in the game overlap. Computers don't actually check pixel overlap — that would be slow and weird. Instead we check distance. If a laser and an alien are within, say, 25 pixels of each other, we treat it as a hit. Turtle has a built-in helper for this: laser.distance(alien) returns the gap between two Turtles.

To check every laser against every alien, we need a nested loop — a loop inside a loop. Picture it like this:

for each laser in lasers:
for each alien in aliens:
How far apart are they?


Less than 25 pixels? ──→ Hit! Remove both. Score +10.

The outer loop walks the laser list once. For each laser, the inner loop walks the whole alien list. So if you have 3 lasers and 5 aliens on screen, we do 3 × 5 = 15 distance checks per frame. That's fine here. Nested loops can get expensive when the lists get huge — keep that in the back of your mind for whenever you build something bigger after camp.

We also introduce the first piece of game state that isn't a list — the score. And we meet a new kind of Turtle: a writer Turtle, whose only job is drawing text on the screen.

Before you start

Your game should have moving lasers and moving aliens.

Build it

Step 1 — Add the score and a writer to draw it

Until now, every Turtle in the game has been a thing the player sees as an object — a cannon, a laser, an alien. A writer Turtle is different: it's hidden, it doesn't move, and its only job is to call .write(...) to put text on the screen.

Add the score variable near your other lists:

score = 0

Then create the writer before the game loop:

score_writer = turtle.Turtle()
score_writer.hideturtle()
score_writer.penup()
score_writer.color("white")
score_writer.setposition(LEFT + 20, TOP - 40)

hideturtle() makes the Turtle itself invisible — we only see the text it writes. setposition(LEFT + 20, TOP - 40) puts it in the top-left corner, using the named edges from Stage 1.

Step 2 — Write the score every time it changes

We wrap the writing in a function so we can call it once now and again every time the score changes. The clear() call wipes the old text before drawing the new — otherwise "Score: 0" and "Score: 10" would stack on top of each other.

Add this function:

def draw_score():
score_writer.clear()
score_writer.write(f"Score: {score}", font=("Arial", 16, "bold"))

The f"Score: {score}" is an f-string — it builds a piece of text and slots the value of score into the spot where {score} is. So when score is 10, the text becomes "Score: 10".

Call it once before the game loop so the player sees Score: 0 when the game starts:

draw_score()

Step 3 — Check every laser against every alien

This is the nested loop. Inside the game loop, after both movement loops, add:

for laser in lasers[:]:
for alien in aliens[:]:
if laser.distance(alien) < 25:
laser.hideturtle()
alien.hideturtle()
lasers.remove(laser)
aliens.remove(alien)
score += 10
draw_score()
break

Both lasers[:] and aliens[:] are copies — same trick from Stage 3, for the same reason. When a hit happens, four things happen together: hide the laser, hide the alien, remove them from their lists, bump the score.

The break at the end is important. Once a laser hits one alien, we stop checking that laser against any other alien — it's already gone. Without break, one laser could "double-kill" two aliens on the same frame just because they happened to be close together. Tiny detail, big difference.

Run the game and try to shoot an alien. Score should go up.

Understand it

Three things in this stage are worth slowing down on.

Distance, not overlap. "Are they touching?" sounds simple, but it isn't — Turtle shapes are different sizes, drawing positions are at object centers, and pixel-perfect overlap math is fiddly. Distance under a threshold is a 90% solution that feels right to the player. The 25 is a tuning number, not a law: smaller feels stingy (the laser has to thread the needle), larger feels generous (the laser is basically a missile). Pick what feels fair.

Nested loops are how systems talk. Whenever you have list A and list B and you need to ask "for each thing in A, does anything in B affect it?" — nested loops are the answer. Game collision is the most familiar example. Search engines, recommendation systems, every "find the closest pair" problem in your future uses the same shape.

Score lives outside the lists. It's a single number, not a list of things, so it doesn't need a Turtle of its own. It's just state — data the game holds onto. Stage 7 adds another piece of state (lives), Stage 8 adds another (game time), and pretty soon the game has a small handful of state variables doing all the bookkeeping. This is normal.

Try this

Learning beat

Try this

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

Predict first

Change the hit threshold from 25 to 100. Predict before you run: will hits be easier, or so easy that aiming stops mattering? Try it. Was it as broken as you expected? Try 5 next. What's the smallest number that still feels like hitting?

Compare

Comment out the break line at the end of the nested loop. Now spawn a few aliens close together (you may need to raise the spawn chance temporarily) and fire a single laser at them. What happens? Is that better (super-shotgun feeling) or broken (one shot wipes a cluster unfairly)?

Connect

Stage 7 needs to know when an alien reaches the cannon — that triggers losing a life. Look at the alien movement loop you wrote in Stage 5. What's the existing line that's almost what Stage 7 needs?

Test your stage

  • Firing at an alien removes the alien.
  • The laser also disappears after a hit.
  • Score goes up by 10 per hit.
  • Missing a shot leaves both the laser and the alien intact.
  • The score text stays in the top-left corner and doesn't smear.
  • Design check. Play for thirty seconds. Does hitting feel rewarding? Does missing feel like it cost you something?

If it breaks

  • Python complains that score is referenced before assignment. That happens when score += 10 lives inside a function. The code in this stage changes score directly in the main loop (module scope), so no global keyword is needed. If you moved the hit logic into a function, add global score as the first line of that function.
  • Hits almost never count. The 25 threshold is too tight for your laser/alien sizes. Try 35 or 40.
  • One laser wipes out a whole cluster on impact. The break line is missing or in the wrong place. It needs to be the last line inside the if laser.distance(alien) < 25: block.
  • Score text stacks on top of itself. score_writer.clear() is missing from draw_score(). The writer redraws fresh every time.
  • The game crashes after a hit. Probably a list mix-up — you may be removing from the wrong list (lasers.remove(alien) or vice versa). Re-read the four .remove() and .hideturtle() lines and make sure each laser action targets a laser, each alien action targets an alien.