Skip to main content

Stage 5: Move the Alien Fleet

Course progressStage 5 of 10
~35 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

downward enemy movement and cleanup at the floor

Learn

how two systems can run in parallel inside one game loop

Ship

a multi-row alien fleet that drifts toward the player and disappears at the bottom

The big idea

Your game already has one system moving every frame: lasers climbing up. Today we add a second system that runs alongside it: the alien fleet drifting down. Same game loop, two passes — one for lasers, one for aliens. The loop is the conductor; each system is a player in the orchestra.

Stage 4 made the fleet by adding separate alien Turtles to one flat aliens list. That means we do not need a special "fleet loop." If every alien in the list moves down by the same amount each frame, the rows stay lined up.

The structure of the alien movement code is going to look almost exactly like the laser movement code, but mirrored:

Stage 3 — Lasers (up): Stage 5 — Aliens (down):

for laser in lasers[:]: for alien in aliens[:]:
laser.sety(... + LASER_SPEED) alien.sety(... - ALIEN_SPEED)
if laser.ycor() > TOP: if alien.ycor() < BOTTOM:
hide + remove hide + remove

Read those two blocks side by side. Almost every line is the same shape. The differences — lasers vs aliens, + vs -, TOP vs BOTTOM — are the only places the two systems disagree on direction. Once you see this mirror, you understand how every moving thing in every video game gets updated.

The frame delay from Stage 3 still matters today. time.sleep(0.02) keeps the loop from running too fast in Trinket and sets the rough framerate — how many frames the game draws per second.

Before you start

Your game should show a multi-row alien fleet near the top of the screen.

Build it

Step 1 — Pick the alien speed

Speed is the dial that decides how much threat the fleet carries. A fast fleet punishes hesitation; a slow fleet gives the player room to think. We start slow and tune later.

Define this near your alien constants:

ALIEN_SPEED = 2

Lasers used LASER_SPEED = 18. The alien fleet moves much slower than lasers because the player needs time to react. Speed is story: fast lasers feel like a weapon, slow aliens feel like a creeping threat.

Step 2 — Walk the alien list every frame

This is the mirror of Stage 3's laser loop, but going down instead of up. We subtract ALIEN_SPEED from each alien's Y position each frame, and we clean up any alien that crosses the bottom edge. Because the fleet moves every alien by the same amount, the rows stay aligned.

Find the while True: loop from Stage 3. Inside that loop, you already have the laser movement block. Add the alien movement block after the laser block and before time.sleep(0.02).

Do not delete the laser loop. Do not put the alien loop inside the laser loop. The two loops should sit one after the other:

while True:
# Stage 3 laser loop is already here.

# Stage 5 alien loop goes here.

time.sleep(0.02)
screen.update()

Now add the alien loop in that spot:

Trace it

Mirror the laser loop

  1. Lasers move up with + LASER_SPEED and leave at TOP.
  2. Every alien in the fleet moves down with - ALIEN_SPEED and leaves at BOTTOM.
  3. The list-copy pattern stays the same.
Your turn

Write the alien loop from the mirror

Cover the answer and write the alien movement loop by transforming the Stage 3 laser loop.

Need a hint?

Start with `for alien in aliens[:]:`, then change the laser Y update from plus to minus.

Stuck? Compare carefully
Answer check
Debug compare only

main.py

Transform the Stage 3 laser loop yourself first, then reveal to check.

for alien in aliens[:]:
alien.sety(alien.ycor() - ALIEN_SPEED)

if alien.ycor() < BOTTOM:
alien.hideturtle()
aliens.remove(alien)

aliens[:] makes a copy of the list — the same trick from Stage 3, for the same reason. Walking a copy means we can safely remove aliens from the real list as we go.

Run the game. The alien fleet should now drift down the screen and disappear one alien at a time when aliens pass BOTTOM.

Step 3 — Check the frame delay

Stage 3 added the frame delay. Make sure it is still at the bottom of the loop so the whole game runs at a steady pace.

At the bottom of the while True: loop, just before screen.update(), you should already have:

time.sleep(0.02)

time.sleep(0.02) means "pause this program for 0.02 seconds." With a 20-millisecond pause per frame, the loop runs roughly 50 times a second — fast enough for smooth motion, slow enough for the player to keep up.

Run the game again. Lasers should still feel snappy, the alien fleet should drift down at a readable pace, and the cannon should respond cleanly to the keys.

File order checkpoint

By the end of Stage 5, the bottom of your game loop should run in this order:

  1. Move and clean up lasers
  2. Move and clean up every alien in the fleet
  3. Pause with time.sleep(0.02)
  4. Redraw with screen.update()

Understand it

The most important idea in this stage is parallel systems sharing one loop. Right now the game updates lasers, then aliens, then redraws — every frame, in that order. The fleet does not change that structure because it is made of ordinary aliens in the same list. As we add scoring, lives, timers, and difficulty over the next five stages, every new system slots into this same loop.

time.sleep(0.02) deserves a second look. It might feel weird that the way to speed up motion is sometimes to slow down the loop. But think about it this way: a faster loop runs the alien movement more often, so even with ALIEN_SPEED = 2 each frame, a too-fast loop adds up to too much movement per second. The frame delay isn't there to make the game slower — it's there to make the game consistent. Without it, the same code runs at different speeds on different computers, which is a nightmare to design around.

We cleanup aliens at the floor for the same reason we cleanup lasers at the top: every Turtle costs the computer a little work, and any object that's off-screen and forgotten about is wasted work that eventually slows the game down. A row can break apart later when some aliens get shot and others survive. That's fine — once aliens are in the list, each one is responsible for itself. The pattern is let no orphan Turtle live.

Try this

Learning beat

Try this

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

Predict first

Set ALIEN_SPEED = 20 and run. Decide before you click run: do you think the game becomes harder, or just impossible? Where is the line between difficulty and unfairness?

Compare

Comment out the time.sleep(0.02) line and run. Then put it back and try time.sleep(0.1). Now try time.sleep(0.005). Which felt smooth? Which felt broken? Frame timing is invisible when it works — and obvious when it doesn't.

Connect

Stage 6 detects when a laser hits an alien. To check that, you need to compare every laser to every alien and see how close they are. Look at your two loops. What's the obvious next move?

Test your stage

Stuck? Compare carefully
Answer check
Debug compare only

required Stage 5 code

Where it goes: Compare this with the bottom of your game loop. The Stage 3 laser loop stays first, the alien loop goes after lasers, then `time.sleep(...)` and `screen.update()` stay last.

Use this only after writing the alien loop from the laser-loop mirror.

ALIEN_SPEED = 2

while True:
# Stage 3 laser loop stays here.

for alien in aliens[:]:
alien.sety(alien.ycor() - ALIEN_SPEED)

if alien.ycor() < BOTTOM:
alien.hideturtle()
aliens.remove(alien)

time.sleep(0.02)
screen.update()
  • Aliens move steadily downward.
  • Aliens disappear when they pass the bottom of the screen.
  • Aliens that start in the same row stay lined up while the fleet moves.
  • Lasers still move upward.
  • The cannon still responds to arrow keys.
  • The game runs at a steady speed — no sudden bursts or freezes.
  • Design check. Play for a full minute. Are the aliens slow enough that you feel in control, or fast enough that a missed laser actually costs you?
  • From memory. Without looking, write the line that moves one alien down each frame. Compare — did you use a minus (alien.ycor() - ALIEN_SPEED), not a plus?

If it breaks

  • Aliens move upward instead of down. Look at the line alien.sety(alien.ycor() - ALIEN_SPEED). The - is what makes aliens fall. If you wrote +, they climb. Coordinates: down is negative in Turtle.
  • The game gets slower and slower over time. That's almost always a cleanup problem. Re-read the if alien.ycor() < BOTTOM: block. Aliens that pass the bottom must be hidden and removed from the list. If only one of those happens, the game is hauling around dead aliens forever.
  • Movement is jumpy or choppy. Two suspects. First, check that screen.tracer(0) (from Stage 3) is still in the program. Second, check that screen.update() is inside the loop, not outside. The screen must redraw once per frame.
  • The game freezes when I press a key. time.sleep(0.02) is probably running while the screen is trying to listen. Make sure screen.listen() is before the while True: loop, not inside it.