CodeRigor

LESSON

Render shapes without changing the game

Translate a World into drawing commands, round at the display boundary and keep model tests independent from Pygame.

The model can now move, but a player needs to see it. Add a one-way rendering boundary: World → render plan → Pygame surface. Drawing reads the state; it does not move the ball, score a catch or consume a life.

A render plan is a plain list of dictionaries describing a fill, rectangle, circle and text. It may look more explicit than drawing directly from game objects. For this small course it gives us an important seam: hosted checks can inspect shapes and labels without installing a graphics package or opening a display.

State becomes drawing commands
start / 3
Rendering observes the world. The desktop adapter interprets commands and presents pixels; no arrow changes the World.
State becomes drawing commands
Rendering observes the world. The desktop adapter interprets commands and presents pixels; no arrow changes the World.

A rectangle uses a top-left position and size, while our paddle stores a center. Subtract half the width to obtain its left edge. The circle takes the ball center and radius. Keep model coordinates as floats and use round only for the plan’s integer drawing coordinates.

These runnable calculations are the same coordinate conversion used in the supplied base plan.

Python
center, width, y, height = 240.6, 90, 288, 12
rect = [round(center - width / 2), y, width, height]
ball = [round(100.4), round(20.6)]
print("paddle rectangle", rect)
print("ball center", ball)

Every frame starts with a fill command so old ball pixels do not trail across the screen. The adapter then draws the paddle and ball, renders text through a font, and calls display.flip once. The actual adapter is included in the download; it uses Pygame’s documented draw.rect and draw.circle APIs.

Our hosted exercise completes the mode overlay. A paused game says Paused; a finished round says Game over - R to restart; a stopped replay says Stopped. Running mode needs only the base plan. Return a newly created plan each call so one caller’s edits cannot contaminate a later render.

Font size and layout are tuned for the supplied 480 × 320 desktop configuration. The model also supports custom board sizes for tests; building a fully responsive desktop HUD is an extension, not a claim of this lab.

An observable pause
start / 3
Pause freezes updates, while drawing continues to show the frozen state and a label. The overlay is feedback, not a state transition.
An observable pause
Pause freezes updates, while drawing continues to show the frozen state and a label. The overlay is feedback, not a state transition.
Python
labels = {"paused": "Paused", "over": "Game over - R to restart", "stopped": "Stopped"}
for mode in ("running", "paused", "over", "stopped"):
    plan = ["background", "paddle", "ball", "score"]
    if mode != "running":
        plan.append(labels[mode])
    print(mode, plan[-1])

Complete the overlay block and verify every mode. Run rendering twice against the same state and check that the input stays equal. Then try the desktop lab: its background, blue paddle and amber ball are generated shapes, so no external image assets are needed.

Primary references: Pygame drawing and display updates. The next lesson adds the rule that resolves a descending 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.