A single-file, self-contained browser boxing game — index.html — built entirely with HTML5 Canvas, vanilla JavaScript, and the Web Audio / Speech Synthesis APIs. No external assets, libraries, or network access required; it runs straight from the file system. It's also installable as a mobile PWA (see Mobile & PWA below) when served over http(s).
Two stick-figure boxers face off in a ring: Logan, controlled by the player, and Nunchucks, a nasty computer-controlled opponent. Whoever lands 30 punches on the other first wins.
| Action | Keyboard | On-screen button |
|---|---|---|
| Punch | Space / J |
PUNCH |
| Duck | hold K |
DUCK (hold) |
| Move left/right | ← / → |
— |
| Jump escape | ↑ / L |
JUMP |
| Quah Fury (special) | ↓ |
QUAH FURY |
On-screen buttons also work with mouse or touch, so the game is playable without a keyboard.
- Positioning & range — Both fighters occupy an x-position on a fixed-width ring floor. Punches only land if the fighters are within actual arm's reach of each other (
PUNCH_RANGE); throwing from too far away simply whiffs. Logan closes distance with the arrow keys; Nunchucks closes distance on his own. - Punching — A punch is a three-phase animation (wind-up → active/throw → recover), rendered with real forward-kinematic arm joints (shoulder → elbow → fist). The outcome (hit, miss, dodge) is resolved the instant the punch goes active.
- Ducking — Holding duck lowers the whole figure's stance. If the defender is sufficiently crouched when a punch resolves, it's avoided.
- Jumping (the signature move) — Either fighter can perform a jump escape that makes them briefly invulnerable to any incoming punch, regardless of range or duck state:
- If there's enough clear ring space on the far side of the opponent, the jumper leaps clean over them, lands on the other side, and both fighters automatically turn to face each other again (facing direction is recalculated every frame from relative position).
- If there isn't enough room to land safely beyond the opponent, the fighter instead jumps backward on their own side to create space.
- Jumping locks out punching/ducking/moving for the duration of the leap.
- Nunchucks' AI:
- Approaches Logan when out of range, attacks on a randomized timer, and occasionally feints (a fake wind-up with no real punch) to bait a duck.
- Difficulty ramps over the first ~60 seconds of a match: he attacks more often, dodges more often, and feints more often the longer the fight goes.
- After taking 2 hits, he becomes evasive: he alternates between rapid, randomized retreats (to create distance) and sudden fast advances (to surprise the player at range), rather than holding a steady position.
- After taking 3 hits in a row (a streak that resets on any avoided punch), he permanently unlocks his own jump escape and may use it — over or backward — exactly like the player.
- Quah Fury (Logan's special move) — a four-phase combo triggered with
↓/ the QUAH FURY button, roughly 1.2 seconds total, during which Logan is fully invulnerable to Nunchucks' punches:- Leap — Logan launches upward and hops toward Nunchucks to close distance.
- Flying kick — one leg extends straight toward the opponent; resolves as a real hit using the same range/duck/jump-escape rules as a punch.
- Finger-poke flurry — both arms snap out fully extended toward Nunchucks' face with a rapid jitter, poking 4 times in quick succession, each landing or missing independently.
- Backward somersault — Logan spins a full backward rotation while retreating away from Nunchucks, shrieking "Quahhhh, to youuuu!" as the flip begins.
- It's high risk/reward: up to 5 hits land if Nunchucks stands still and doesn't react, but he can duck, jump-escape, or retreat out of range mid-flurry to blunt it. While the move is active, Logan can't punch, duck, jump, or move manually — his position is fully driven by the leap and somersault.
Everything audible is generated live in the browser — there are no sound files:
- Grunts — a synthesized pitch-swept tone plus a filtered noise burst, played by whichever fighter throws a punch, using the Web Audio API oscillator/noise graph.
- Impact / whiff sounds — filtered noise bursts (a "thud" for a landed hit, a lighter "whoosh" for a miss).
- Background music — a looping 8-bit-style chiptune (square-wave lead melody over a triangle-wave bassline), scheduled with a lookahead step sequencer against the
AudioContextclock. It starts the moment the fight begins and stops the instant the match ends. - Game-over music — when a fighter reaches 30 hits, the chiptune cuts out and a separate, deliberately unsettling piece takes over: a sparse sawtooth melody built on the tritone ("devil's interval") between C and F#, a droning triangle-wave bass alternating the same two roots, and a continuous low sine drone with slow vibrato underneath. It stops the moment a new fight starts.
- Voice lines — spoken via the browser's built-in
SpeechSynthesisUtterance(no audio files, no network):- Intro, once per session on the very first "Start Fight": "Play the Fingers of Quah, if you dare!"
- Logan's punch yell: "Quahhh-so!"
- Logan's jump: "Jump escape!"
- Nunchucks' jump: "Lame dude!"
- Proximity announcer, whenever the fighters close back into striking distance after being apart: "Fingers of Quah!" (cooldown + distance hysteresis prevent it spamming).
- Random taunts, fired on an unpredictable timer (roughly every 7–16 seconds during a live match): "Weasle Face", "U a Stinker", "Kickin Booty", "Outta my way", "Gonna Kick Butt" — one is picked at random each time.
- Quah Fury shriek, spoken the instant Logan's backward somersault begins: "Quahhhh, to youuuu!"
- Fighters are drawn each frame from scratch using canvas primitives: a head circle, torso, two-segment legs, and two-segment arms computed via simple trigonometry from shoulder/hip pivot points and joint angles — no sprite images.
- Both fighters have a stylized face (soft skin-tone gradient, dark hair, simple almond-shaped eyes, subtle mouth line) topped with a conical straw hat (nón lá style, woven texture, tan gradient), with a chin cord colored to match each fighter (blue for Logan, red for Nunchucks) so they stay easy to tell apart alongside their torso/glove colors.
- Getting hit triggers a red flash and a brief knockback recoil; jumping triggers a vertical arc with a forward lean. Logan's Quah Fury special has its own dedicated poses: tucked/extended kicking legs for the flying kick, double outstretched jittering arms for the finger-poke flurry, and a full rotational spin (via canvas transform) for the backward somersault.
- The backdrop is a procedurally painted rural-China dusk landscape (no image files — canvas primitives only, matching the project's self-contained design): a plum-to-amber sky gradient, a glowing low sun, three layers of misty silhouette mountains, a distant pagoda and pine tree tucked to the sides of the fight, a few birds, and a horizon mist band.
- The ring itself (ropes, posts, mat) is drawn procedurally to match the current canvas size, styled to fit the backdrop: bamboo-toned ropes/posts and a paddy-terraced earthen ring floor instead of a boxing-gym mat.
- The game renders full-screen: the page is a top-to-bottom flex layout — the title/health bar docks to the top, the control buttons dock to the bottom, and the canvas fills exactly whatever space is left between them, so the HUD and buttons reserve their own room instead of floating over the ring. Gameplay logic still runs against a fixed 1080×576 logical arena (so all positions, ranges, and speeds stay untouched); a resize-aware canvas transform (backed by a
ResizeObserveron the canvas itself) stretches that logical space to fill whatever box it's been given. - Rendering is device-pixel-ratio aware (capped at 3x): the canvas backing store is sized to the screen's actual pixel density and redrawn through a scaling transform, so linework stays crisp on retina/high-DPI displays instead of being blurred by CSS upscaling.
The game is designed to be played in landscape on a phone:
- If the device is in portrait, a "Rotate your device" prompt takes over the screen (game and controls hidden) until it's turned sideways. On browsers that support it (mostly Android/Chrome, especially when installed), the game also makes a best-effort
screen.orientation.lock('landscape')call. - At short mobile-landscape heights, the HUD text and control buttons shrink at two breakpoints so the ring keeps as much vertical room as possible; long fighter/name labels truncate with an ellipsis instead of overflowing the screen.
- Touch: on-screen buttons work with touch out of the box (also mouse/keyboard on desktop); pinch-zoom, double-tap-zoom, text selection, and iOS's touch callout menu are all disabled so it feels like a native app rather than a web page.
It's also an installable Progressive Web App:
manifest.json— name/icons/theme colors,display: "standalone",orientation: "landscape".sw.js— a cache-first service worker that caches the app shell (HTML, manifest, icons) so the game keeps working offline once it's been loaded once.icons/— generated app icons (192px, 512px, a maskable 512px, and an Apple touch icon).- Requires
http(s)hosting to actually install/work offline — service workers cannot register overfile://, so openingindex.htmldirectly from disk still plays the game fine but skips PWA install/offline behavior. The live build is hosted athttps://carlso2.github.io/battle-game/via GitHub Pages.
- Base game: two canvas-rendered fighters, punch/duck controls, timing-based hit resolution, synthesized grunt/impact sounds, 20-hit win condition.
- Added horizontal movement for Logan and true range-based hit detection (punches can now be dodged by footwork, not just ducking); Nunchucks began closing distance on his own.
- Added Nunchucks' post-2-hits evasive retreat/advance behavior, and Logan's jump escape (with a synthesized whoosh + spoken "Jump escape!").
- Enlarged the arena 20% and reworked jump escape to actually traverse the ring: jump clean over the opponent when there's room, or jump backward when there isn't, with facing direction recalculated live.
- Gave Nunchucks the same jump-escape ability once he's hit 3 times in a row, with his own taunt line, plus faster/further randomized retreats and advances, the "Fingers of Quah!" proximity announcer, and looping chiptune background music.
- Renamed the game to Fingers of Quah, added a one-time spoken intro line, stopped the music on game over, and raised the win condition to 30 punches.
- Added a distinct scary/eerie music piece (tritone melody + drone) that takes over specifically when the match ends, replacing the chiptune gameplay loop.
- Reworked the layout to fill the whole browser viewport (translucent floating HUD/controls instead of a centered fixed-width box), added device-pixel-ratio-aware rendering for crisp visuals at any screen size, and redesigned both fighters with stylized faces and conical straw hats (color-corded to match each fighter).
- Added Logan's Quah Fury special move (
↓): an invulnerable leap-kick, finger-poke flurry, and backward somersault combo with its own custom poses and a shrieked voice line, replacing down-arrow's old duck binding (duck moved toK). - Replaced the flat gym-style backdrop with a procedurally painted rural-China dusk landscape (misty layered mountains, distant pagoda and pine, low sun, birds, mist), and restyled the ring itself to match (bamboo ropes/posts, paddy-terraced earthen floor).
- Reworked the layout for mobile landscape play (docked HUD/controls that reserve their own space instead of overlaying the ring, shrink-to-fit breakpoints, a rotate-to-landscape prompt for portrait phones, disabled pinch/double-tap zoom) and turned the game into an installable Progressive Web App (manifest, offline-caching service worker, app icons), deployed live via GitHub Pages.
index.html— the game itself (markup, styles, and script) in one file.manifest.json— PWA metadata (name, icons, theme colors, standalone/landscape display).sw.js— service worker that caches the app shell for offline play.icons/— generated app icons used by the manifest andapple-touch-icon.