3.4 KiB
Ultimate Tic-Tac-Toe
A two-player, hot-seat tic-tac-toe game for the browser where each player brings their own flavor.
Every player picks any avatar from a combined roster — sparkle or shred — and that choice sets their personal color, move sound, and particle effects. One player can rock 🦄 unicorns and rainbows while the other brings 💀 skulls and metal. A separate stage selector sets the global vibe.
Camps & stages
Camps (per player, set by the chosen avatar):
- ✨ Sparkle — ⭐🦄🌸🎀🦋🌈🎨🍭 · pink glow, bright sine chimes, sparkle particles.
- 💀 Shred — 💀☠️🎸🤘🔥⚡🏴☠️🐉 · red glow, sawtooth power-chords, fire/lightning particles, board shake.
Stages (global, pick on the setup screen):
- ✨ Sparkle — pastel gradient, glassmorphism cards, twinkling stars.
- 💀 Shred — black & red, rising embers, lightning flashes, metal lettering.
- ⚔️ Mixed — neutral dark arena where both camps' colors pop; both backdrops play. Great when the two players don't agree.
Features
- Free-for-all avatars — each player's grid shows all 16; a guard stops both choosing the same one.
- Per-player identity carries through the whole game: side panels, scoreboard, and each player's pieces on the board glow in their camp color, with camp-specific move sounds and particle bursts.
- Three stages driven entirely by CSS custom properties (
[data-stage]); the winner screen adopts the winner's camp. - Web Audio SFX (no asset files); mute toggle, top-right.
- Persistent names, avatars, stage, scores, and mute via
localStorage. - Accessible — keyboard-navigable board (arrow keys + Enter/Space), ARIA roles and live status, visible focus rings,
prefers-reduced-motion. - Responsive & scroll-safe — no horizontal overflow or clipped content from 320 px to desktop (the old nested-scroll / clipped-top bug is fixed: the layout now centers with
margin:autoinstead of fragile flex centering +max-height).
Run it
Static site, no build step.
Local web server (so localStorage + audio behave like production)
python3 -m http.server 8000
# → http://localhost:8000
Docker / OrbStack (nginx, production-style headers)
docker compose up -d --build
# → http://localhost:8090
docker compose down
Test
End-to-end tests drive real Chromium against the running app via playwright-core and print a plain-text PASS/FAIL report (avatar roster, mixed-camp play, stage switching, win/draw flows, persistence, responsive overflow at five widths, keyboard a11y, console cleanliness). Screenshots land in screenshots/.
# with a server (or the container) running on :8080
PW_CORE="$(node -e "console.log(require.resolve('playwright-core'))")" \
node test/e2e.mjs http://localhost:8080
# optional: PW_CHROMIUM=/path/to/Chromium to pin a browser build
Project structure
index.html markup; stage selector + three screens (setup / game / winner)
style.css stages ([data-stage]) + per-player camps (.camp-sparkle/.camp-shred)
script.js engine: avatar roster, camps, stages, audio, FX, persistence, a11y
Dockerfile nginx:alpine static image
nginx.conf gzip, cache + security headers
docker-compose.yml one-command run on :8080 via OrbStack
test/e2e.mjs Playwright end-to-end suite