CodeRigor

LESSON

Resolve one catch or miss per falling ball

Detect a catch-plane crossing, award one outcome, reset the ball and keep score + misses equal to the turn count.

Movement supplies positions. A game rule decides what those positions mean. In this catch game, a ball either crosses the paddle’s catch plane within its horizontal catch interval or eventually falls completely below the board.

We keep before and moved worlds so the rule can detect a transition. “The ball is below the paddle” is a state and can remain true for many frames. “It crossed from above this frame” can resolve one outcome and spawn the next ball.

A crossing is an event
start / 3
Compare the old and new y against the catch plane. A ball already below the plane cannot earn a late catch.
A crossing is an event
Compare the old and new y against the catch plane. A ball already below the plane cannot earn a late catch.

The catch plane is paddle_y − radius. For the default board that is 288 − 9 = 279. We accept a crossing when old_y < 279 <= new_y. Horizontal acceptance uses abs(ball_x − moved_paddle_x) <= half_width + radius.

This is a deliberately simplified game rule. It uses the paddle’s end-of-step position and an expanded horizontal interval. It is not exact circle-versus-rectangle geometry, continuous moving-body physics or a claim that every frame partition gives identical outcomes. Making the policy explicit is more useful than calling it a perfect collision solver.

Python
plane, paddle_x, half, radius = 279, 240, 45, 9
for old_y, new_y, ball_x in [(278,280,240),(278,280,294),(280,281,240)]:
    crossing = old_y < plane <= new_y
    caught = crossing and abs(ball_x - paddle_x) <= half + radius
    print(crossing, caught)

A miss waits until the ball’s top is strictly below the bottom: moved_y − radius > height. At equality the ball is not yet a miss. A catch has priority if a directly supplied large movement satisfies both tests; ordinary bounded motion is much smaller.

On an outcome, increment turn exactly once and increment score for a catch or misses otherwise. Spawn the next ball from the new turn index. Keep the moved paddle. When misses reaches the configured lives, set mode to over; otherwise continue running.

Returning moved when neither event occurs preserves position without manufacturing points. The supplied validation rejects a motion pair that secretly changed counters, mode or ball x. Tests exercise exact boundaries and a just-outside horizontal position, because happy-path center catches alone do not establish the rule.

Exactly one outcome per ball
start / 3
A catch or miss increments the turn and one counter, then respawns. No outcome simply preserves the moved world.
Exactly one outcome per ball
A catch or miss increments the turn and one counter, then respawns. No outcome simply preserves the moved world.
Python
height, radius = 320, 9
for center_y in [328, 329, 330]:
    top = center_y - radius
    print("center", center_y, "top", top, "miss", top > height)

Implement the transition, then test: exact plane equality, exact horizontal touch, just outside the interval, bottom equality, the final life and a ball already below the catch plane. Check both input worlds remain unchanged.

Your model can now score a full round. Next we decide how pause, restart and quit interact without letting a command frame accidentally move the ball.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Optional long-form companion. Our catch game and model exercises are original and self-contained.