6aa9769ce5
shared/board.js now transcribes every distinct space label from the hand-drawn sketch (assets/game_board.png) rather than Phase 1's small subset. The sketch's arrows get genuinely ambiguous in a few places (it reads as a mockup, not an engineered spec) and reuses several space names across zones (Start A Business, Family Reunion, Market Crash, ...) — that repetition is kept as intentional flavor rather than deduplicated, and the ambiguous bits are resolved into a clean DAG with the same shape Phase 1 proved out: a fork, a chain() per branch, a convergence — repeated three times (Career/Education/Gap Year, then Relationship/Investment, then High Risk/Safe), into a long shared retirement tail. Branches are built with a small chain() helper that auto-wires each entry's `next` to the following one, since hand-wiring ~107 ids was too error-prone. The reducer, SQLite schema, and rooms/WS layer needed zero changes — the whole point of the pure-reducer/graph-data design from Phase 1. Two things did need generalizing: - public/boardRender.js's lane offset was hardcoded to a 2-way fork; the new "Which Path?" fork is 3-way (Career/Education/Gap Year), so the offset formula is now symmetric for any number of branches. - shared/game.test.js hardcoded Phase 1's specific space ids. Rewritten to be board-structure-agnostic: it always resolves the first offered choice and otherwise rolls the max die value, which reliably makes progress regardless of board shape (walkForward always stops early at the next choice/finish), plus a graph-well-formedness check. Verified: unit tests green; a full two-player game played headlessly end-to-end through all three forks to Finish in 38 turns with zero console/page errors; the rendered board visually confirmed at full scale (5-row snake layout, 3-way fork fans out correctly, all labels legible).
89 lines
3.6 KiB
Markdown
89 lines
3.6 KiB
Markdown
# Life Journey — Phase 1
|
|
|
|
An async multiplayer, Game-of-Life-style board game. Phase 0 proved the
|
|
deployment path (Docker → Nginx Proxy Manager → HTTPS → WebSocket). Phase 1
|
|
adds the actual game: a pure shared reducer, SQLite persistence, and
|
|
rooms with shareable invite links, so a few people can play together across
|
|
devices and tab closes.
|
|
|
|
The server is the sole authority over game state. It generates the only
|
|
source of randomness (the dice roll) and applies it through the exact same
|
|
reducer (`shared/game.js`) the browser imports — client and server can never
|
|
disagree about the rules.
|
|
|
|
## What's here
|
|
|
|
```
|
|
lifegame/
|
|
├── docker-compose.yml
|
|
├── Dockerfile
|
|
├── package.json / package-lock.json
|
|
├── shared/
|
|
│ ├── board.js # the Phase 1 board graph + movement helper
|
|
│ ├── game.js # pure reducer: reduce(state, action) -> newState
|
|
│ └── game.test.js # node --test coverage of the whole rules engine
|
|
├── server/
|
|
│ ├── index.js # REST + WebSocket, static hosting
|
|
│ ├── rooms.js # in-memory room registry, applies/broadcasts actions
|
|
│ ├── db.js # SQLite schema + data access (games/players/tokens)
|
|
│ └── ids.js # id/token/join-code generation
|
|
└── public/
|
|
├── index.html # lobby / waiting room / game UI
|
|
├── client.js # REST wrappers + NetworkTransport (WebSocket)
|
|
└── boardRender.js # SVG board, laid out from board.js graph data
|
|
```
|
|
|
|
## Running locally
|
|
|
|
```bash
|
|
npm install
|
|
npm test # reducer unit tests — no server needed
|
|
npm run dev # starts on :3000, creates data/lifegame.db on first game
|
|
```
|
|
|
|
Open two browser tabs at `http://localhost:3000`. Create a game in one tab,
|
|
copy the invite link, open it in the other tab, join, and start the game once
|
|
both players are in the lobby.
|
|
|
|
## The board
|
|
|
|
`shared/board.js` (~107 spaces) is transcribed from the hand-drawn sketch at
|
|
`assets/game_board.png`, organized into three fork points with the same
|
|
shape the sketch uses: **Career / Education / Gap Year** at the start,
|
|
**Relationship / Investment** after a shared "quarter-life crisis" chain,
|
|
then **High Risk / Safe** before a long shared retirement tail to Finish.
|
|
The sketch reuses several space names across its zones (Start A Business,
|
|
Family Reunion, Market Crash, ...) — that's kept as intentional recurring
|
|
flavor rather than deduplicated. More spaces can be inserted into any
|
|
branch's `chain([...])` array in `board.js` without touching the reducer,
|
|
the SVG renderer, or the database schema — none of them know or care how
|
|
many spaces exist.
|
|
|
|
## Deploying on the homelab
|
|
|
|
Same as Phase 0 — see `homelab-config.md` for the full infrastructure
|
|
reference. Set `PUBLIC_URL` in `.env` (or the compose environment) to your
|
|
public domain so invite links generated by the server are shareable rather
|
|
than pointing at an internal address:
|
|
|
|
```bash
|
|
cd ~/homelab/lifegame
|
|
docker compose up -d --build
|
|
docker compose logs -f # expect: "Life Journey (Phase 1) listening on :3000"
|
|
```
|
|
|
|
## Data & backups
|
|
|
|
The `lifegame-data` volume (mounted at `/app/data`) holds `lifegame.db` —
|
|
every game, player, and token. Back it up like your other self-hosted data;
|
|
losing it loses every in-progress and finished game.
|
|
|
|
## What's next
|
|
|
|
- Expand `shared/board.js` toward the full sketched board
|
|
- Richer board rendering (the current UI is a functional list view, not the
|
|
illustrated board)
|
|
- Tighter reconnection/presence handling (who's online right now, not just
|
|
who's joined)
|
|
- Admin page (`ADMIN_PASSWORD`, already stubbed in `.env.example`)
|