Skip to main content

Stage 7: Add Lives and Game Over

Course progressStage 7 of 10
~40 min
One game, one Trinket

Keep building in the workspace on the right.

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

Build

lives, a penalty for missed aliens, and a game-over screen

Learn

how stakes turn a toy into a game

Ship

a game that can actually be lost

The big idea

Until this stage, you could not lose. Missing a laser was free. Letting an alien drift past the cannon was free. The game was, technically, a toy — a thing you played with rather than a thing you played. Today we add stakes.

The new tool is a piece of game state called a Boolean. A Boolean is a value that is either True or False — nothing else. We use one to ask a yes/no question on every frame: "is the game still running?" When the answer becomes False, the loop exits.

Stages 3–6: while True:
run forever (nothing can stop us)

Stage 7: game_running = True
while game_running:
run only while game_running is True

When lives reach 0:
game_running = False
→ loop exits → GAME OVER shows

This is the first time the game has two modes: playing and ended. The loop only runs in playing mode. The screen we show after the loop exits is its own thing — a tiny second program that prints the result.

We also tighten the alien cleanup. In Stage 5 we removed aliens when they passed BOTTOM — anywhere off the bottom of the screen counted. Today we move the line up to FLOOR_LEVEL — the cannon's row. An alien that reaches FLOOR_LEVEL has reached the player. That's the moment a life comes off.

Before you start

Your game should score points when lasers hit aliens.

Build it

Step 1 — Add the lives counter and the on/off switch

lives is just a number that ticks down. game_running is the Boolean that controls the loop. We also create a second writer Turtle so lives can live in the top-right corner, opposite the score.

Add these near score:

lives = 3
game_running = True

Then add a second writer before the game loop:

status_writer = turtle.Turtle()
status_writer.hideturtle()
status_writer.penup()
status_writer.color("white")
status_writer.setposition(RIGHT - 140, TOP - 40)

RIGHT - 140 slides the writer in from the right edge by a fixed amount — enough room for the word "Lives:" plus a number.

Step 2 — Draw the lives count

Same pattern as draw_score() from Stage 6 — clear, then write. Reusing the pattern means a future reader sees one shape for all on-screen text.

Write this function:

Think first

Boolean switch

What value should `game_running` have while the game is still playable: `True` or `False`?

Check your thinking

`True`. The loop keeps running while `game_running` is true.

def draw_status():
status_writer.clear()
status_writer.write("Lives: {}".format(lives), font=("Arial", 16, "bold"))

Call it before the game loop:

draw_status()

Step 3 — Take a life when an alien reaches the cannon

This is the change that makes misses hurt. Find the alien cleanup block inside your game loop. It currently checks if alien.ycor() < BOTTOM:. Change it to FLOOR_LEVEL, and add the life-loss logic.

Change the alien cleanup block into a life-loss block:

Your turn

Write the consequence

Say the consequence chain out loud before typing it: hide, remove, subtract, redraw, check for game over.

Need a hint?

The alien crosses `FLOOR_LEVEL`, so the code hides it, removes it, subtracts one life, redraws status, then maybe stops the loop.

Stuck? Compare carefully
Answer check
Debug compare only

main.py

Say the consequence chain out loud and write it yourself first, then reveal to check.

if alien.ycor() < FLOOR_LEVEL:
alien.hideturtle()
aliens.remove(alien)
lives -= 1
draw_status()

if lives <= 0:
game_running = False

Reading top-to-bottom: the alien crossed the player's line, so we hide it, drop it from the list, subtract a life, redraw the lives text — and if lives have hit zero, we flip the switch that ends the game.

Note the threshold: < FLOOR_LEVEL, not < BOTTOM. We want the player to lose lives at the cannon's row, not when aliens leave the screen entirely.

After the alien movement loop, add this quick exit before hit detection:

if not game_running:
break

That stops the current frame immediately after the last life is gone. Without it, the rest of the loop could still run once before while game_running: gets checked again.

Step 4 — Respawn only while the game is still running

Stage 6 added a respawn check so the game does not run out of aliens. Now that the game can end, that check needs one extra guard.

Find this Stage 6 respawn code near the bottom of your game loop:

if len(aliens) == 0:
spawn_alien_fleet()

Replace it with:

if game_running and len(aliens) == 0:
spawn_alien_fleet()

Put this after hit detection and before time.sleep(0.02). The game_running part matters: if the last alien costs the last life, the game should end cleanly instead of spawning a new fleet behind the GAME OVER screen.

Step 5 — Let the loop know how to stop

Right now your game loop says while True:. That's "run forever." Change it to:

while game_running:

That's "run while game_running is True." The moment Step 3's code sets game_running = False, this line evaluates to False and the loop ends.

After the loop, add the game-over screen. We reuse the score_writer from Stage 6 — it already exists, so we just move it and have it draw one more thing.

score_writer.setposition(0, 0)
score_writer.write("GAME OVER", align="center", font=("Arial", 28, "bold"))
screen.update()
turtle.done()

Run the game. Try to lose on purpose — stand still and let aliens come down. Lives should tick down, and when they hit zero you should see GAME OVER in the middle of the screen.

File order checkpoint

By the end of Stage 7, your game has three zones:

  1. Setup before the loop: state variables, writer Turtles, helper functions, key bindings, and first draw calls
  2. Play inside while game_running:: movement, life loss, immediate exit if the game ended, hit detection, guarded respawn, pause, and update
  3. Ending after the loop: GAME OVER text, final update, and turtle.done()

Understand it

The Boolean switch is small but huge. Until now, your game has been a thing the computer runs until you close the window. Today the game runs until it decides to stop on its own. That's a real grown-up loop pattern. Most apps you use every day — the camera, the music player, the browser tab — work this way: they have a state variable, the main loop checks it every cycle, and it can end gracefully when needed.

We deliberately wrote if lives <= 0: and not if lives == 0:. Using <= is defensive — if some future change to the game subtracted two lives at once (say, a powerful alien), == 0 would skip right past zero into -1 and never trigger game-over. <= 0 says "if we ever get to zero or below, end it." Code that survives changes you haven't made yet is good code.

Game-over is the first place we let one writer Turtle have two jobs: drawing the score during the game and announcing the result after. We could have made a third writer just for "GAME OVER," but reusing what we already have is cheap and clean. The Turtle moves, prints, and that's it — its work is done.

Try this

Learning beat

Try this

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

Predict first

Set lives = 1. Predict before you run: how does the whole feel of the game change with one-hit-and-done? Now play it. Is it more intense, or just frustrating? Is there a fair version of one-life mode?

Compare

Change if lives <= 0: to if lives == 0: and run. Now imagine a future stretch where a single alien costs two lives. Walk through what happens with that change. Why does <= survive that future change and == not?

Connect

Stage 8 adds a win state — survive long enough and you win. The loop needs to end for winning the same way it currently ends for losing. Look at your while game_running: line. What does Stage 8 need to add?

Test your stage

Stuck? Compare carefully
Answer check
Debug compare only

required Stage 7 code

Where it goes: Compare this with your Stage 7 changes: lives/state variables before writers, `draw_status()` before the loop, `while game_running:` replacing `while True:`, life loss inside the alien movement loop, the immediate exit before hit detection, guarded respawn near the bottom of the loop, and the GAME OVER code after the loop.

Use this only after you identify which line flips the game from running to over.

lives = 3
game_running = True

status_writer = turtle.Turtle()
status_writer.hideturtle()
status_writer.penup()
status_writer.color("white")
status_writer.setposition(RIGHT - 140, TOP - 40)

def draw_status():
status_writer.clear()
status_writer.write("Lives: {}".format(lives), font=("Arial", 16, "bold"))

draw_status()

while game_running:
# Laser movement stays here.

for alien in aliens[:]:
alien.sety(alien.ycor() - ALIEN_SPEED)
if alien.ycor() < FLOOR_LEVEL:
alien.hideturtle()
aliens.remove(alien)
lives -= 1
draw_status()

if lives <= 0:
game_running = False

if not game_running:
break

# Hit detection stays here.

if game_running and len(aliens) == 0:
spawn_alien_fleet()

time.sleep(0.02)
screen.update()

score_writer.setposition(0, 0)
score_writer.write("GAME OVER", align="center", font=("Arial", 28, "bold"))
screen.update()
turtle.done()
  • Aliens that reach FLOOR_LEVEL are removed and take a life.
  • The lives text in the top-right corner updates every time.
  • When lives reach zero, the game stops.
  • GAME OVER appears centered on the screen.
  • Aliens that the player shoots down don't cost a life (only aliens that get through the cannon line should).
  • A new fleet appears when the old one is cleared, but not after the game is over.
  • Hit detection does not keep running after the last life is gone.
  • Design check. Play three full rounds. Did losing feel earned, or did the game cheat you? If it cheated, what would you change — alien speed, fleet size, threshold, life count?
  • From memory. Without looking, write the two lines that end the game when lives run out. Compare — did you use if lives <= 0: then game_running = False?

If it breaks

  • Python says lives is referenced before assignment. The life-changing code probably ended up inside a function. Either move it back into the main loop (where this stage puts it) or add global lives and global game_running as the first lines of the function.
  • The game never ends — even at zero lives. Two suspects. First, check that the loop now says while game_running: and not while True:. Second, make sure the if lives <= 0: block is inside the alien cleanup block, where it can actually run.
  • Score changes after the final life is gone. Add the if not game_running: break check after the alien movement loop and before hit detection.
  • A new fleet appears after GAME OVER. The respawn check is missing game_running and. It should say if game_running and len(aliens) == 0:.
  • GAME OVER appears behind the score text. The score_writer is being moved to (0, 0) but the old text from the score is still sitting in its old position. Add score_writer.clear() before the new position, or call draw_score() once more before the game-over screen.
  • Lives count never appears. The draw_status() call before the game loop is missing, or the writer position is off the screen. Run with print(lives) after each change to confirm the value is moving.
  • The game ends the moment it starts. Check that lives = 3 (not 0) and game_running = True (not False). Defaults matter.