Introduce game-specific music overrides using the soundtrack service. Adds arcadedark and hacker music packs, and wires them to Balatro, Colorado Defense, Tempest, Mastermind, Hexsweeper, Dot Link, and 2048. Games now lazy-load their soundtrack assets on entry instead of sharing a single generic music cache. |
||
|---|---|---|
| .vscode | ||
| assets | ||
| data | ||
| src | ||
| tools | ||
| .gitignore | ||
| README.md | ||
| android-chrome-192x192.png | ||
| android-chrome-512x512.png | ||
| apple-touch-icon.png | ||
| favicon-16x16.png | ||
| favicon-32x32.png | ||
| favicon.ico | ||
| index.html | ||
| package.json | ||
| start_web.sh | ||
| styles.css | ||
README.md
Fertig Classic Games
A single-page collection of 77 classic games — board games, card games, casino games, word games, and logic puzzles — all playable in the browser against local AI opponents. Built on Phaser 3.90.
This is a fully static site. There is no server, no database, no accounts,
no build step, and no npm install required to run it. The frontend uses native
browser ES modules and loads Phaser from a CDN. Everything that used to be a
backend round-trip — the single local player profile, casino chip balance, match
history, puzzle progress, and word-game dictionaries/AI — now runs entirely in
the browser against localStorage and the ported engines in src/words/.
Table of contents
- Quick start
- Deployment / hosting
- The game catalog
- Project layout
- Architecture
- Adding a new game
- Developer tooling (
tools/) - Data files (
data/)
Quick start
No dependencies to install. You just need a static file server, because ES
modules can't be loaded over file://.
git clone <this-repo>
cd fertig-classic-games
# Any static server works. A couple of one-liners:
npm run serve # python3 -m http.server 8000 → http://localhost:8000
./start_web.sh # python3 -m http.server 3000 → http://localhost:3000
npx serve . # if you prefer Node
Then open the printed URL in a modern browser. That's it — pick a game and play.
Requirements: a current browser with ES-module + importmap support (Chrome/Edge/Firefox/Safari, ~2023+). Python 3 (or any static server) for local hosting. Node.js 20+ is only needed for the optional dev tooling in
tools/.
Deployment / hosting
The repository root is the site. Serve it as static files from any web host, CDN, or object store (GitHub Pages, Netlify, Cloudflare Pages, S3, nginx, Apache, …). No configuration, environment variables, or runtime are needed.
Because every asset path is relative and index.html sets <base href="./">,
the site works unchanged from a domain root or any subdirectory
(e.g. example.com/games/). To drop it into an existing site:
cp -r index.html styles.css *.png favicon.ico assets data src \
/var/www/parent-site/games/
The only files the deployed site needs are index.html, styles.css, the
favicons/PWA icons in the root, and the assets/, data/, and src/
directories. The tools/ directory is dev-only and never shipped.
Player state (profile, chips, history, puzzle progress) lives in each visitor's
own browser localStorage under the fcg: prefix — nothing is stored or synced
server-side, so hosting is entirely stateless.
The game catalog
82 games are registered in src/data/gamesRegistry.js and grouped into six
menu categories:
| Category | Count | Examples |
|---|---|---|
| Tabletop | 21 | Backgammon, Chess, Checkers, Go, Othello, Settlers of Catan, Ticket to Ride, Risk, Monopoly, Blokus, Labyrinth, Mahjong, Stratego, Battleship, Mastermind, Connect 4, Forbidden Island, Azul, Chinese Checkers, Mexican Train, Parchisi |
| Cards | 18 | Cribbage, Gin Rummy, Rummikub, Canasta, Hearts, Uno, Phase 10, Skip-Bo, Go Fish, Old Maid, Nerts, Dominion, Splendor, Freecell, Solitaire Tour, Spire Climb, Zahtzee, Farkle |
| Casino | 9 | Blackjack, Texas Hold 'Em, Baccarat, Pai Gow Poker, Video Poker, Craps, Roulette, Bingo, Slot Machines |
| Word | 15 | Wordle Race, Scrabble, Boggle, Ghost, Word Ladder, Word Search, Hangman, Spelling Bee, Sudoku, Mini Crossword, Tectonic, Bookwork, Kiitos, Tri-Ominoes, Jumble |
| Logic & Puzzle | 14 | 2048, Rush Hour, Hexsweeper, Jell-o Monsters, Shift, Mahjong Match, Jewel Quest, Zuma, Bejeweled Blitz, Mini Motorways, Dot Link, Katamino, Genius Square, Block Fighter |
| Arcade, Console & PC | 2 | Colorado Defense, Star Control |
Each game is a self-contained Phaser.Scene (plus its own logic/AI/data helper
modules) under src/games/<slug>/. Games are single-player against 0–7
configurable AI opponents depending on the title; several ship a video tutorial.
Project layout
fertig-classic-games/
├── index.html Entry point: importmap for Phaser + <base href="./">
├── styles.css Page chrome (canvas container + DOM overlay layer)
├── package.json "serve" script; no runtime deps
├── start_web.sh Convenience local server (python http.server)
├── favicon*/apple-touch/… Favicons and PWA icons
│
├── src/ The entire application (native ES modules)
│ ├── main.js Phaser.Game config; registers every scene; DOM-overlay sync
│ ├── config.js Canvas dimensions (1920×1080) and the UI color palette
│ ├── data/
│ │ └── gamesRegistry.js The catalog: registerGame() for all 77 games
│ ├── scenes/ Shell scenes (menu, profile, opponent select, room)
│ ├── services/ api.js → localApi.js → localStore.js (the local backend)
│ ├── ui/ Reusable widgets: Button, Modal, TextInput, Portrait,
│ │ music/speech/tutorial/fullscreen helpers
│ ├── words/ Client-side word-game engines + wordRouter dispatcher
│ └── games/<slug>/ One folder per game (Scene + Logic + AI + Data + tutorial.md)
│
├── data/ Static JSON game data + word lists (fetched at runtime)
│ ├── *.json Per-game level banks, artwork maps, opponents, music, etc.
│ ├── crosswords/ Mini-crossword puzzle bank
│ └── wordlists/ enable1.txt (dictionary) + common.txt
│
├── assets/ Runtime media
│ ├── images/ Spritesheets, backgrounds, board art (81 files)
│ ├── fonts/ Bitmap/web fonts
│ ├── fx/ Sound effects (71 files)
│ ├── music/ Background music tracks (21 files)
│ ├── speech/ AI opponent voice clips (393 files)
│ ├── videos/ In-game portrait/animation clips (90 files)
│ └── tutorial-videos/ How-to-play videos (17 files)
│
└── tools/ Offline dev scripts (run with node; NOT deployed)
├── gen*.js Puzzle/level-bank generators
└── verify*.js Headless self-play verification harnesses
Architecture
The frontend is deliberately buildless: index.html declares an
<script type="importmap"> that maps the bare specifier phaser to a CDN ES
module, and everything else uses relative import statements. The Phaser canvas
is a fixed 1920×1080 design surface scaled to any viewport with
Phaser.Scale.FIT + CENTER_BOTH.
Scene flow
Scenes live in src/scenes/ and run in this order:
Boot → Preload → Landing → (Profile | GameMenu) → OpponentSelect → GameRoom → <ConcreteGame>
- BootScene / PreloadScene — bootstrap and load shared assets.
- LandingScene — title screen with menu music and fullscreen toggle.
- ProfileScene — edit the single local profile (name, avatar, bio).
- GameMenuScene — browse the catalog by category.
- OpponentSelectScene — choose opponent count/skill, playfield, card back, and any per-game options (expansion, difficulty, word length, …).
- GameRoomScene — a thin router. Its
slugDispatchmap turns the selected game'ssluginto a scene key and starts the concrete game scene, forwarding the chosen setup as scene-init data.
Every concrete game scene is registered in the scene array in src/main.js.
main.js also pins Phaser's DOM-overlay container to the real canvas box on
every resize (syncDomContainer), so DOM elements like text inputs and video
portraits line up exactly with their in-canvas targets under Scale.FIT.
The local "backend"
There is no HTTP backend. The chokepoint src/services/api.js exposes the same
api.get/post/patch/delete/upload surface the old server client had, but instead
of fetch it calls src/services/localApi.js, an in-browser shim that mirrors
the old REST responses ({ status, data }). This kept ~135 call sites across the
games working unchanged when the server was removed.
localApi.dispatch(method, path, …) handles the former endpoints against
localStorage:
| Former route | Now backed by |
|---|---|
/auth/*, /profile* |
A single implicit local profile in localStore.js |
/profile/chips* |
Casino chip balance (default 2000; low-balance reset) |
/games |
gamesRegistry.listGames() |
/history* |
Match results array (bounded to last 100) |
/puzzles/:slug/{progress,complete,reset} |
Ordered per-game level progress |
/words/* |
Delegated to src/words/wordRouter.js |
src/services/localStore.js is the typed localStorage wrapper. All keys are
namespaced under fcg: (fcg:profile, fcg:history, fcg:puzzleProgress) so
player data is easy to find, export, or clear.
Word games
Word games need a dictionary and move-search logic that used to live on the
server. Those engines were ported verbatim into src/words/ (Scrabble, Ghost,
Word Ladder, Word Search, Sudoku, Tectonic, Boggle, Spelling Bee, Mini Crossword,
Kiitos, …). wordRouter.js fetches the dictionaries from data/wordlists/ once
in the browser and routes /words/* calls to the right engine, returning the
same response shapes the old Express routes did.
Assets
Media is loaded from assets/ (spritesheets, backgrounds, SFX, music, AI voice
clips, tutorial and portrait videos). Per-game data — level banks, artwork maps,
opponent rosters, music playlists — is JSON in data/, fetched at runtime. Many
games render primarily with vector/procedural graphics and layer optional
drop-in spritesheets on top, so art can be added without refactoring scenes.
Adding a new game
-
Register it in
src/data/gamesRegistry.js:registerGame({ slug: 'cribbage', name: 'Cribbage', category: 'cards', // tabletop | cards | casino | word | logic | arcade-console-pc cardGame: true, // uses the shared card-back picker minPlayers: 2, maxPlayers: 2, minOpponents: 1, maxOpponents: 1, defaultOpponents: null, // optional preselected opponent count hasTutorial: true, // shows the how-to-play video button iconFrame: 64, // frame index into the game-icons spritesheet });The menu, opponent-select screen, and history all pick it up from here.
-
Implement the scene at
src/games/<slug>/<Name>Game.js. It's aPhaser.Scenewhoseinit(data)receives the chosen setup (opponents,playfield,cardBack,difficulty, expansion/word-length options, …) and whosecreate()renders the board and runs the game + AI locally. Follow the established per-game module split (e.g.<Name>Logic.js,<Name>AI.js,<Name>Data.js) so the scene stays presentation-focused. Add atutorial.mdif applicable. -
Wire it up:
- import and add the scene class to the
scenearray insrc/main.js; - add the
slug → SceneKeyentry toslugDispatchinsrc/scenes/GameRoomScene.js.
- import and add the scene class to the
-
Persist progress (optional): for level-based puzzles, call
/puzzles/<slug>/{progress,complete,reset}; to record win/loss/draw, POST to/history/single-player. Both are served by the local API shim.
Developer tooling (tools/)
Offline Node scripts, not part of the deployed site. Run them with plain node
from the repo root (Node.js 20+):
-
Generators (
genRushHour.js,genZuma.js,genKatamino.js,genMiniCrossword.js,genJumble.js,buildCommonWords.js, …) produce the level banks and word lists that live indata/. -
Verifiers (
verifyCribbage.js,verifyRisk.js,verifyRummikub.js, …) are headless self-play/assertion harnesses that exercise each game's engine and AI to catch regressions. They exit non-zero on failure:node tools/verifyCribbage.js node tools/verifyRisk.js --games=300
Data files (data/)
Runtime JSON fetched by the games:
- Per-game banks & artwork maps — e.g.
rushhour.json,zuma.json,katamino.json,dotlink.json,jumble.json,spireclimb-artwork.json,slots-artwork.json,shift-artwork.json. - Shared UI options —
playfields.json,colored-playfields.json,card-backs.json,opponents.json,music.json. - Word data —
wordlists/enable1.txt(main dictionary),wordlists/common.txt(curated common words),crosswords/minicrossword.json.
All are static assets; editing them (or regenerating them via tools/) changes
game content without touching code.