CodeRigor

LESSON

Ship a playable catch-the-ball loop

Compose input, commands, bounded motion, scoring and rendering into a downloadable game with headless replay tests.

You now have the pieces of a complete game. The final model function, step, wires them together in one explicit order. The desktop adapter wraps that pure update with input, a clock and a renderer.

The finished lab is a playable catch-the-ball game. Use arrows or A/D to move, P to pause or resume, R to restart and Esc or the window close button to quit. The blue paddle catches the amber ball. Three misses end the round. Everything needed for shapes, fonts, tests and replay fixtures is included or declared as a pinned dependency.

One complete frame
start / 3
The adapter supplies semantic input and elapsed seconds. The pure model resolves commands, motion and scoring before the adapter draws and presents.
One complete frame
The adapter supplies semantic input and elapsed seconds. The pure model resolves commands, motion and scoring before the adapter draws and presents.

First apply commands. Return immediately if this is a command frame or the resulting state is not running. This also makes resume and restart frames motionless even though their result can be running. On an ordinary frame, advance positions and resolve the fall using both the pre-motion and moved values.

The pure function never imports Pygame. Its tests cover command priority, a catch-plane boundary, a complete missed round and replay equality. A fixed fixture is not proof of every possible game sequence; it is reproducible evidence for the stated contracts.

Python
def decide_frame(command, mode):
    if command or mode != "running":
        return ["apply commands", "return"]
    return ["apply commands", "advance motion", "resolve fall"]
for command, mode in [(False,"running"),(True,"running"),(False,"paused")]:
    print(decide_frame(command, mode))

Download and unzip the lab, create a virtual environment, install requirements.txt, run the headless tests, then launch play.py. The archive contains model.py, starter.py, the desktop adapter, model and adapter tests, replay fixtures, diagrams and a README. To practice locally, choose the unfinished model through --model starter; to inspect the working artifact, use the default model.

The adapter drains event.get once per frame, samples held directions, gets elapsed milliseconds from Clock.tick(60), calls step, interprets render_plan and flips the display. A finally block releases Pygame resources. The target of 60 frames is a cap, not an exact timing promise.

The gameplay clip below is an actual scripted replay through the shipped model and renderer. Its controller supplies semantic controls rather than real keyboard presses; the desktop game accepts both paths. All visuals are original generated shapes.

Two adapters, one game model
start / 3
A human keyboard and a supplied replay both drive the same pure model. Headless tests do not need Pygame; the real desktop adapter does.
Two adapters, one game model
A human keyboard and a supplied replay both drive the same pure model. Headless tests do not need Pygame; the real desktop adapter does.
Python
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Round:
    score: int = 0
    misses: int = 0
state = Round()
for event in ["catch", "catch", "miss"]:
    state = replace(state, score=state.score + (event == "catch"), misses=state.misses + (event == "miss"))
print(state)
print("resolved", state.score + state.misses)

Actual gameplay replay: an amber ball falls toward a blue paddle; the score advances, pause is shown, and play resumes.

Watch the short MP4 replay. Both use the shipped renderer with scripted controls. The lab README documents how to regenerate frames.

Before calling your version finished, run the model tests without Pygame, run the adapter smoke test after installing the desktop dependency, play a round, deliberately miss, pause, restart and close the window. Extend it by adding a second ball only after deciding how multiple outcomes affect the turn invariant. See the included review checklist for concrete completion evidence.

Download the complete lab. The archive includes runnable code, tests, fixtures and a README.

The shipped model and Pygame renderer, driven by the included replay fixture: the paddle follows the ball, score reaches four, P pauses and resumes, R restarts, then quit stops the game. No audio. Playback starts when you choose Play.

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.