Kevin 8df4859696 Render the board as SVG, laid out from the graph data
Adds public/boardRender.js: a layout algorithm that derives every space's
position purely from shared/board.js (column = longest-path distance from
Start, lane = branch offset that fans out at a choice space and re-centers
wherever branches rejoin), so it keeps working unmodified as the board data
grows — no hand-placed coordinates to maintain.

Renders spaces as styled SVG nodes (color/shape by type), connects them with
curved path lines, and animates player tokens between positions. Pending
choices glow and are clickable directly on the board, in addition to the
existing text buttons. Game view widens the card on desktop for the board
and scrolls horizontally on narrow viewports.

Also repicks the player color palette (server/index.js) to avoid the board's
own semantic colors (green/gold/red), after a token nearly disappeared into
the same-colored Start space during visual testing.

Verified with a headless-browser run (Playwright, off-screen — not a desktop
screenshot): two players through lobby -> live join update -> board -> a
fork choice, no console errors, reducer tests still green.
2026-07-21 18:12:01 -07:00
2026-07-21 16:17:12 -07:00
2026-07-21 20:48:28 +00:00
2026-07-21 14:16:59 -07:00
2026-07-21 16:17:12 -07:00

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

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 (Phase 1 subset)

The full hand-drawn board (assets/game_board.png) has ~150 spaces across two thematic passes (Career, Education, Gap Year, Relationship/Family, Investment, High Risk). Phase 1 encodes a small subset with the same shape — a Career-vs-Education fork, a Relationship-vs-Investment fork, a High-Risk-vs-Safe fork, converging to Finish — enough to prove the reducer, persistence, and rooms all work end to end. More spaces can be inserted into any branch later without touching the reducer or database schema.

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:

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)
S
Description
Mairins Version of The game of life
Readme 22 MiB
Languages
JavaScript 92%
HTML 7.7%
Dockerfile 0.3%