CodeRigor

LESSON

Move in pixels per second

Scale motion by elapsed seconds, cap stalled frames and clamp the paddle to its legal center bounds.

“Move three pixels each frame” makes a faster computer change the rules. Use speed in pixels per second and pass elapsed seconds into the update. At 150 pixels/second, a .02-second step moves three pixels, while a .01-second step moves one and a half.

The model does not read a clock. The desktop loop calls Clock.tick(60) once per frame and divides its millisecond result by 1000. Requesting a 60-frame cap does not guarantee every frame takes exactly one sixtieth of a second. Tests pass explicit elapsed values.

Keep the units visible
start / 3
The desktop clock returns milliseconds. Convert to seconds once, then multiply speed by bounded elapsed seconds inside motion.
Keep the units visible
The desktop clock returns milliseconds. Convert to seconds once, then multiply speed by bounded elapsed seconds inside motion.

The lab caps simulated elapsed time at .05 seconds. A breakpoint or dragged window can produce a much longer interval; applying all of it in one movement would make the ball leap. This policy discards the extra time. The game slows during a stall rather than catching up. More advanced games may use an accumulator and fixed substeps; that is a different policy with additional limits to design.

Python
speed, cap = 150, .05
for elapsed in [.01, .02, 2.0]:
    simulated = min(elapsed, cap)
    print("seconds", elapsed, "simulated", simulated, "pixels", speed * simulated)

The paddle coordinate is its center. Keep it between half its width and board width minus half its width. Clamp after computing the candidate position. The ball moves down; its x stays fixed until the scoring transition spawns the next ball.

Positions stay floating-point in the model. Round only when making a render plan. Rounding each update can lose small movements and make speed depend on frame count.

The supplied validator rejects negative, infinite and NaN elapsed values, Boolean axes and axes outside −1/0/+1. The update changes positions only: counters, turn and mode belong to later transitions. A paused world must remain equal to its input.

Clamp the center, not the outer edge
start / 3
A 90-pixel paddle on a 480-pixel board keeps its center in [45, 435]. The model retains fractions between frames.
Clamp the center, not the outer edge
A 90-pixel paddle on a 480-pixel board keeps its center in [45, 435]. The model retains fractions between frames.
Python
x, speed, elapsed = 434.0, 260.0, .02
half, width = 45, 480
candidate = x + speed * elapsed
clamped = max(half, min(width - half, candidate))
print("candidate", candidate)
print("center", clamped)
print("right edge", clamped + half)

Test .02 seconds, a stall, both board edges, zero elapsed and a paused world. A motion-only partition test is useful before clamping or scoring occurs. Do not generalize it into a claim that all discrete collision outcomes are frame-rate independent.

Primary reference: Pygame Clock.tick. Next we turn state into visible shapes without letting drawing advance the simulation.

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.