Prompt
Implement the core timing logic for a reaction-speed game.
Write two functions:
startGame()— returns initial game state { phase: 'wait', score: 0, delay: number } with random 1-5s delayhandleTap(state, now)— processes a tap at timestampnow, returns updated state
Rules:
- Phase 'wait': ignore taps
- Phase 'active': record reaction time (now - targetShown), increment score, transition to 'wait' with new random delay
- Game ends after 10 seconds total (handle in UI)
- Phase 'result': game over, show final score
Solution
Two impurities make timing games untestable — the clock and the RNG — and this design quarantines both. Math.random() lives only in the state factories; the current time enters handleTap as the now parameter instead of being read via Date.now() inside. Injecting time is the whole trick: tests pass literal timestamps and assert exact reaction times, and the logic stays a synchronous pure transition.
The state is a phase machine — 'wait' → 'active' → 'wait' … → 'result' — and the first line is its guard: any tap outside 'active' returns the state unchanged, same reference. That's simultaneously the anti-cheat rule (mashing during 'wait' scores nothing) and a React optimization: returning the identical object lets a useState/useReducer update bail out of re-rendering entirely.
Division of labor is the other shipping concern: the pure layer decides what a tap means; the component owns the when — it schedules setTimeout(delay) to flip 'wait' into 'active' (stamping targetShown), runs the 10-second game clock, and clears timers on unmount. Timers, effects, and cleanup are UI-side machinery; none of it leaks into the logic.
Red flag: calling Date.now() inside handleTap. It still works — until you try to test it, replay it, or debug a reported score, and every run gives different numbers.
Say it: "It's a phase machine with time injected as a parameter — taps outside 'active' return the same reference, so cheating is impossible and React skips the render."