fertig-classic-games/README.md

293 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
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, Bookworm, 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 07
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 | 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.
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. `katamino.json`, `dotlink.json`,
`jumble.json`, `spireclimb-artwork.json`, `slots-artwork.json`,
`shift-artwork.json`. Newer games keep their level banks under
`assets/gamedata/<game>/` and fetch them on entry instead (Rush Hour, Zuma,
Peggle, Goo Tower).
- **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.