290 lines
13 KiB
Markdown
290 lines
13 KiB
Markdown
# 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](https://phaser.io/).
|
||
|
||
**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](#quick-start)
|
||
- [Deployment / hosting](#deployment--hosting)
|
||
- [The game catalog](#the-game-catalog)
|
||
- [Project layout](#project-layout)
|
||
- [Architecture](#architecture)
|
||
- [Scene flow](#scene-flow)
|
||
- [The local "backend"](#the-local-backend)
|
||
- [Word games](#word-games)
|
||
- [Assets](#assets)
|
||
- [Adding a new game](#adding-a-new-game)
|
||
- [Developer tooling (`tools/`)](#developer-tooling-tools)
|
||
- [Data files (`data/`)](#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://`.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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
|
||
|
||
77 games are registered in `src/data/gamesRegistry.js` and grouped into five
|
||
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 |
|
||
|
||
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 `slugDispatch` map turns the selected
|
||
game's `slug` into 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
|
||
|
||
1. **Register it** in `src/data/gamesRegistry.js`:
|
||
|
||
```js
|
||
registerGame({
|
||
slug: 'cribbage',
|
||
name: 'Cribbage',
|
||
category: 'cards', // tabletop | cards | casino | word | logic
|
||
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.
|
||
|
||
2. **Implement the scene** at `src/games/<slug>/<Name>Game.js`. It's a
|
||
`Phaser.Scene` whose `init(data)` receives the chosen setup (`opponents`,
|
||
`playfield`, `cardBack`, `difficulty`, expansion/word-length options, …) and
|
||
whose `create()` 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 a `tutorial.md`
|
||
if applicable.
|
||
|
||
3. **Wire it up:**
|
||
- import and add the scene class to the `scene` array in `src/main.js`;
|
||
- add the `slug → SceneKey` entry to `slugDispatch` in
|
||
`src/scenes/GameRoomScene.js`.
|
||
|
||
4. **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 in `data/`.
|
||
- **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:
|
||
|
||
```bash
|
||
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.
|