Stage 2: Move the Cannon
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.
keyboard controls for the cannon
how a game reacts to the player without checking constantly
a cannon that moves left and right and cannot leave the screen
The big idea
Right now your cannon is a statue. It sits at FLOOR_LEVEL and does nothing. Today we teach the game to react when the player presses a key.
The way most games do this is called event-driven programming. The game does not stop and ask "did anyone press a key yet?" a thousand times per second. Instead, the screen listens in the background, and when something happens — a keypress, a click, a touch — it calls a function you wrote ahead of time. An event is a thing that happened (like a left-arrow press). An event handler is the function the screen calls in response.
Player presses ← → screen.onkey → move_left() → cannon moves
(the listener) (your handler)
We also need to stop the cannon from sliding off the edge of the screen. Last stage we named LEFT and RIGHT. Today they earn their keep: we use them as invisible walls.
A function is a named block of code we can call by writing its name. move_left and move_right will be the two functions the screen calls when the player presses an arrow.
Your Stage 1 cannon should appear at the bottom of the screen.
Build it
Step 1 — Decide how fast the cannon moves
Speed and edges are tuning knobs. Pulling them into named constants at the top means we can change the feel of the game in one place instead of hunting through code.
Add these near your other constants:
Name the knobs
Which name controls how far the cannon moves: `CANNON_STEP` or `CANNON_MARGIN`?
Check your thinking
`CANNON_STEP` controls the movement amount. `CANNON_MARGIN` controls how far from the wall the cannon stops.
CANNON_STEP = 20
CANNON_MARGIN = 40
CANNON_STEP is how many pixels the cannon shifts on each press. CANNON_MARGIN is how close to LEFT and RIGHT we let the cannon get before the invisible wall stops it.
Step 2 — Write the two movement functions
The screen will call one of these whenever the player presses an arrow. Each function checks the wall before moving — that way the cannon never lives outside the playfield, even for a single frame.
Add these functions above draw_cannon():
Write one direction first
Write move_left() first. Then build move_right() by changing the sign and the wall comparison.
Need a hint?
Left means subtract from X. Right means add to X. The two functions should be almost the same shape.
Stuck? Compare carefully
main.py
Write move_left() yourself first, then reveal to check it and adapt move_right().
def move_left():
new_x = cannon.xcor() - CANNON_STEP
if new_x > LEFT + CANNON_MARGIN:
cannon.setx(new_x)
draw_cannon()
def move_right():
new_x = cannon.xcor() + CANNON_STEP
if new_x < RIGHT - CANNON_MARGIN:
cannon.setx(new_x)
draw_cannon()
Read the structure: figure out where the cannon would go → check the wall → only then commit the move. We always recompute the cannon's drawing with draw_cannon() so we don't leave a smear of old stamps behind.
Step 3 — Tell the screen which keys to listen for
The functions exist now, but nothing calls them yet. We connect them to the keyboard with screen.onkey(...). We also add a quick way to quit the game.
Wire the controls before draw_cannon():
Trace one keypress
- The player presses the left arrow.
screen.onkey(move_left, "Left")calls your function.move_left()checks the wall and redraws the cannon.
screen.onkey(move_left, "Left")
screen.onkey(move_right, "Right")
screen.onkey(turtle.bye, "q")
screen.listen()
screen.listen() is the line that actually starts the listening. Without it, you can press all the keys you want — nothing happens.
Run the game and use the left and right arrow keys. The cannon should slide back and forth and stop politely at each invisible wall.
File order checkpoint
By the end of Stage 2, your main.py should be ordered like this:
- Constants, including
CANNON_STEPandCANNON_MARGIN - Cannon setup
move_left()andmove_right()draw_cannon()- Key bindings and
screen.listen() draw_cannon()call, thenturtle.done()
Understand it
This stage swaps one mental model for another. In Stage 1 we wrote code that ran top-to-bottom once and then sat at turtle.done(). Now the game has a heartbeat made of events — the screen is awake in the background, and your move_left / move_right functions are the things it calls when the player presses an arrow.
Notice we kept the cannon as one Turtle (the same one from Stage 1) and just changed its X position. We did not create a new cannon for every keypress. That is the same "reuse one Turtle for decoration" idea from Stage 1, applied to a moving object — every Turtle has a cost, so we keep things small.
The wall check (if new_x > LEFT + CANNON_MARGIN) is a design choice, not a Python rule. We could have let the cannon slide off the screen. But a player who can lose their own cannon by holding a key feels punished by the controls, not the game. Walls keep the focus on the aliens.
Try this
Try this
Three short experiments. Predict before you run, then test your guess.
Change CANNON_STEP to 100. Before you run, write down what you expect to happen. Now run it. Did the cannon do what you predicted? What broke that you didn't expect?
Temporarily remove the if new_x > LEFT + CANNON_MARGIN: line in move_left (and the matching line in move_right). Run the game and hold an arrow. What happens at the edge? Put the if back when you're done — and notice how invisible good wall logic is when it works.
Stage 3 adds a fire button — the player presses space to shoot a laser. You will use screen.onkey(...) again. Look at the three lines you wrote in Step 3. Which one tells you the pattern you'll reuse?
Test your stage
Stuck? Compare carefully
required Stage 2 code
Where it goes: Compare this with the Stage 2 additions in your file: constants near the top, movement functions before `draw_cannon()`, key bindings before `draw_cannon()` is called.
Use this only after you try to write one movement function and adapt the other.
CANNON_STEP = 20
CANNON_MARGIN = 40
def move_left():
new_x = cannon.xcor() - CANNON_STEP
if new_x > LEFT + CANNON_MARGIN:
cannon.setx(new_x)
draw_cannon()
def move_right():
new_x = cannon.xcor() + CANNON_STEP
if new_x < RIGHT - CANNON_MARGIN:
cannon.setx(new_x)
draw_cannon()
screen.onkey(move_left, "Left")
screen.onkey(move_right, "Right")
screen.onkey(turtle.bye, "q")
screen.listen()
- Left arrow moves the cannon left.
- Right arrow moves the cannon right.
- The cannon redraws cleanly (no smear of old stamps).
- The cannon stops short of the screen edges.
- Pressing
qcloses the game. - Design check. Hold an arrow for a few seconds. Does the speed feel like the player is moving, or like they're chasing the cannon?
- From memory. Without scrolling up, write the two lines that make the screen respond to a key press at all. Compare — did you include a
screen.onkey(...)binding andscreen.listen()?
If it breaks
- Nothing happens when I press keys. The
screen.listen()line is missing or is in the wrong place. It needs to run after you callscreen.onkey(...)for each key, and beforeturtle.done(). - The cannon smears across the screen. The
draw_cannon()function probably forgot itscannon.clear()first line. Without it, every move stamps a new cannon on top of the old ones. - Left arrow moves it right (or vice versa). Check the
+and-signs inmove_leftandmove_right.move_leftshould subtract from X;move_rightshould add. Coordinates: left is negative, right is positive. - The cannon slides right off the edge. Re-read the
ifline in both functions.move_leftchecksnew_x > LEFT + CANNON_MARGIN(we want X to stay bigger than the left wall).move_rightchecksnew_x < RIGHT - CANNON_MARGIN(we want X to stay smaller than the right wall).