Beginner / Input & touch
Make a Racer Change One Lane per Key Press

Holding an arrow key should not send the car repeatedly across all three lanes. Lane Racer treats a fresh directional press as one command, while the collector treats a held direction as continuous movement. That distinction belongs in the input model.
What you'll build
We will trace the racer wrapper's press guard and the engine's lane clamp. The result accepts one change per held direction, supports keyboard aliases, and keeps the lane index between zero and two.
Prerequisites
Know keydown, keyup, and the held-action set from demo.js. The racer engine exposes a move(direction) method. Its draw function uses fixed lane centers, so input changes an index rather than an arbitrary x coordinate.
Separate an event from a new press
Browsers can send repeated keydown events while a key remains held. The wrapper first asks whether that logical direction is already in its held set. For the racer, it calls move only when the direction was previously absent, then records it as held.
if (mode === 'racer' && !held.has(direction)) {
game.move(direction === 'left' ? -1 : 1);
}
held.add(direction);
On release, the wrapper removes the action. The next accepted press can then change lanes again. This is a simple edge detector: it distinguishes entering a held state from receiving another event while already in that state.
Checking only a repeat flag can also be useful, but an action set lets multiple input paths share a clear state model. The tradeoff is that aliases such as A and Left currently share one action entry. If exact simultaneous physical-key behavior becomes important, track physical keys before deriving actions.
Clamp the lane in the engine
The engine updates its lane with a clamp between zero and two. A left command at lane zero therefore leaves the car in lane zero rather than producing a negative array index. The same rule protects keyboard, button, and swipe callers.
Keeping that guard in the engine means the interface does not need to duplicate the road bounds. Even if a caller accidentally sends a large negative direction, the resulting lane remains within the configured three-lane range.
The clamp does not itself enforce “one lane per press.” That comes from the wrapper sending minus one or plus one once per accepted press. Distinguish input semantics from model bounds; one mechanism should not be credited with both responsibilities.
Draw from the selected lane
The renderer maps the index to centers at 280, 480, and 680. The car snaps between those positions in the current implementation. A later visual lane-change animation could interpolate the displayed car position while leaving the logical lane decision separate.
If you add such animation, decide when collisions use the new lane. Immediately changing collision membership while drawing the car between lanes is one possible policy, but it should be intentional. Smooth visuals do not automatically define continuous collision behavior.
Preserve document controls
The wrapper handles gameplay keys only within the arena and ignores select controls. It prevents page scrolling for accepted directional commands while the game runs. Outside the arena, normal document keyboard behavior remains available.
Pause clears held actions. Without that reset, resuming while the wrapper still believes Right is held could reject the player's next fresh-looking press. Focus and visibility changes therefore affect both timing and discrete input readiness.
Test a press sequence
Start in the center lane. Press Left once, repeat the same keydown without releasing, and verify the car remains in the left lane. Release, press Right, and verify it returns to center. Then test repeated left commands at the boundary.
Also test different input methods in one round: a keyboard change, a movement-button press, and a swipe. All should call the same bounded engine method. If one path edits a pixel position directly, it can disagree with the model's lane index and break collision checks.
Next steps
Read Lane Racer's source and locate the small move method before exploring its larger update function. For a local extension, animate lane changes without altering the accepted-command count, then write down exactly which lane owns the car during that transition.