87 lines
5.2 KiB
Markdown
87 lines
5.2 KiB
Markdown
# The Desk
|
|
|
|
A single-player browser game. You rule the fictional Republic of Valdoria for five in-game years from a wooden desk: a yes/no proposal lands in your tray every week, you set the budget and taxes every quarter, and the country answers. Debt, unrest and repression can end your term early. English and French.
|
|
|
|
The rules are in [web/data/rules/rules.en.md](web/data/rules/rules.en.md) and [rules.fr.md](web/data/rules/rules.fr.md). They are plain Markdown, and the game shows the same files under **Menu → Rules**.
|
|
|
|
## Run it
|
|
|
|
You need Docker with Compose.
|
|
|
|
```bash
|
|
docker compose up --build -d # build and start
|
|
# open http://localhost:8080
|
|
docker compose down # stop
|
|
```
|
|
|
|
The container listens on port 8080 (change the left side of `ports` in `docker-compose.yml` to use another host port). It restarts unless stopped and has a health check (`/healthz`, probed by the binary itself because the image has no shell).
|
|
|
|
Without Docker (Go 1.23+):
|
|
|
|
```bash
|
|
go run ./server # PORT=3000 go run ./server to pick a port
|
|
```
|
|
|
|
There is nothing to configure: no database, and no environment variables except the optional `PORT` and `MUSIC_DIR`. Saves live in the player's browser (`localStorage`), with three manual slots and export/import as text.
|
|
|
|
## Music
|
|
|
|
The cassette player on the desk plays the `.mp3` files of [music/](music/) in random order (keys: previous, play, pause, stop, next). Drop files in that folder and reload the page: no rebuild, because Docker mounts the folder read-only and the server lists it live. File names become the titles (`03_Evening_Report.mp3` shows as "Evening Report"). `placeholder.mp3` is an empty file that is ignored. Without Docker the server reads `./music`, or the folder in `MUSIC_DIR`. Each sound effect and the music have their own slider under **Menu → Settings → Sound mixer**.
|
|
|
|
## Stack
|
|
|
|
- **Server:** Go, standard library only. It serves the files under `web/` (embedded in the binary), the music folder and `/healthz`. No game logic runs on the server.
|
|
- **Game:** vanilla JavaScript ES modules and CSS. No framework, no bundler, no npm, no runtime dependency. Charts are hand-drawn SVG, sound effects are synthesised with the Web Audio API (no audio files), fonts are bundled (OFL).
|
|
- **Image:** multi-stage build into `gcr.io/distroless/static` (a few MB).
|
|
|
|
## Folders
|
|
|
|
```
|
|
server/ Go entry point (main.go: static files, music folder, health check) and the tests (*_test.go)
|
|
music/ your .mp3 files for the cassette player (mounted into the container)
|
|
web/
|
|
embed.go go:embed of everything below
|
|
index.html the page
|
|
css/ desk, papers, ledger, decision, menu, endgame, anim (motion), mobile (phone layout)
|
|
fonts/ img/ bundled fonts and small images
|
|
js/
|
|
main.js boot, lifecycle, the real-time loop (frames + a worker heartbeat for background play)
|
|
timer-worker.js heartbeat that keeps the game running in a hidden tab
|
|
engine/ the game rules: pure functions, no DOM, no network (state, economy, sectors,
|
|
indicators, cards, gameover, score, world, projection, validate...)
|
|
ui/ DOM panels: dashboard, decision (the tray), calendar (dial clock), ledger, newspaper,
|
|
telegram, endgame, menu, shelf (phone), walkman, audio, music, markdown
|
|
save/ save codec (CG1:<crc>:<base64>) with migrations, and storage with fallbacks
|
|
i18n/ interface strings, en.json and fr.json
|
|
data/
|
|
balance.json every tunable number
|
|
scenarios/ the country at the start (default.json, index.json)
|
|
cards/ the proposal pool, one file per category (900+ cards, EN and FR)
|
|
characters.json newspaper.json endgame.json rules/
|
|
dev/ headless simulation page (sim.html)
|
|
tools/ Node scripts: tests, balance, card and scenario helpers (below)
|
|
docs/ PLAN.md (plan and decisions log), BALANCE.md, CARD-WRITING.md, SCENARIO-WRITING.md
|
|
```
|
|
|
|
The engine never touches the DOM and the UI never changes the state directly: it calls engine actions. That keeps the simulation testable headless.
|
|
|
|
## Test and tune
|
|
|
|
```bash
|
|
go test ./server # validates every card, scenario, balance section, rules file and string
|
|
node tools/engine-check.mjs # engine rules, saves, tray, clock, game overs
|
|
node tools/balance-check.mjs 24 --check # plays full terms with bots and checks the difficulty bands
|
|
node tools/card-stats.mjs # overview of the card pool
|
|
node tools/add-cards.mjs <category> <batch.json> # merge a batch of new cards
|
|
node tools/scenario-check.mjs [id] # start-state report for a scenario
|
|
node tools/bal-edit.mjs path=value # edit balance.json keeping its layout
|
|
```
|
|
|
|
Guides for contributors (and agents): [docs/BALANCE.md](docs/BALANCE.md) for difficulty, [docs/CARD-WRITING.md](docs/CARD-WRITING.md) for new proposals, [docs/SCENARIO-WRITING.md](docs/SCENARIO-WRITING.md) for new countries.
|
|
|
|
Handy URL parameters for development (they skip the menu and never touch the real autosave): `?dev` (exposes `window.__game`), `?seed=N`, `?lang=fr`, `?pronouns=she`, `?skip=DAYS`, `?alloc=K`, `?card=ID`, `?msPerDay=N`, `?menu`.
|
|
|
|
## Licence
|
|
|
|
Public domain, see [LICENSE](LICENSE). The fictional countries, people and events are invented.
|