The oldest hand game, rebuilt around a single timing decision instead of pure chance. The chant runs on its usual clock — rock, paper, scissors, shoot — but the opponent’s hand is already decided the instant “Shoot!” is called; it just takes a beat to fully resolve on screen. Lock your throw the moment it lands and you’re guessing blind, the same odds as always. Wait, and the glimpse of their true shape fades in, clearer the longer you hold — right up until the window closes and hesitating costs the round outright, no throw at all.
Timed to World Rock Paper Scissors Day, August 27.
How it plays
R / P / S or the on-screen buttons, timed against the chant. There’s no aim or power to manage — the only choice is how long you’re willing to wait for a clearer read before the window runs out.
Reuse
The mechanic lives in src/shoot.js as a framework-free ES module:
createMatch(opts)— returns a state object; callupdate(dt)each frame andlockThrow(move)on input ('rock'|'paper'|'scissors').opts.randoverrides the RNG.state.phasedrives the loop:'calling' → 'window' → 'result' → 'calling'.state.callis the current chant word ('rock'|'paper'|'scissors'|'shoot');state.glimpse(0..1) is how much ofstate.cpuMove’s true shape has faded in, useful for drawing the opponent hand mid-window.state.lastOutcomeis{ result: 'win'|'loss'|'tie', reason: 'read'|'flinch', playerMove, cpuMove, glimpse };state.scoretracks rounds/wins/losses/ties/flinches plus a streak.- Rendering, input, and any hand art are entirely the caller’s job — the demo draws two procedural canvas hands (palm plus finger strokes whose spread encodes the move) rather than a sprite sheet.
Port notes for DragonRuby: update() and resolveRound() have no DOM
dependencies; the hand rendering belongs to the caller, same split as
match-point.
Gotchas
lockThrow()is a no-op outsidephase === 'window'— same contract asmatch-point’sswing(), so mashing the buttons during the chant does nothing until “Shoot!” actually lands.- Locking at
glimpse === 0isn’t cheating-proof-fair in the sense of being literally blind past the RNG — the demo does fade the CPU hand in from the very first window frame, so a fast display refresh shows a sliver of true shape a hair earlier than a slow one. The mechanic is the tradeoff (wait longer, see more, risk more), not a hard information embargo. - The demo in
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.
