Files
Kevin 6aa9769ce5 Expand the board to the full sketch (~107 spaces, three forks)
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).
2026-07-21 18:56:27 -07:00

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`)