Merge branch 'docs/devpost-story'
All checks were successful
CI / test (push) Successful in 22s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lukas Müllner 2026-11-13 18:34:33 +01:00
commit 8ee43eeb3f

View file

@ -45,9 +45,100 @@ Alternativen:
- (141) One map. Nine lands. Every day. Build your realm piece by piece, chase three stars and climb today's
leaderboard – right inside a Reddit post.
### About the project (Markdown, LaTeX erlaubt)
> Abschnitt „Inspiration“ bei Bedarf persönlicher machen (eigene Worte des Besitzers). Ob die KI-Unterstützung
> erwähnt wird, entscheidet der Besitzer (die Regeln erlauben technische Hilfe, solange Ideen und Rechte beim
> Teilnehmer liegen).
```markdown
## Inspiration
Ninelands started as a strategy board game on a 9×9 board split into nine lands: you place a castle, farmers,
knights and bishops, every piece claims land, and the goal is to rule the nine lands. It runs on the web, desktop,
Android and itch.io – and its most popular mode has always been the **map of the day**, where everyone plays the same
map and compares results.
Reddit felt like the natural home for that mode. A daily map is a shared puzzle, and a shared puzzle wants a place to
talk about it: _Where did you put your castle? How did you get 9/9 lands?_ So we built **Ninelands for Reddit** – a
focused, bite-sized daily edition that lives inside a post, with the comment thread as the place to compare realms.
## What it does
- **One 9×9 map per day, the same for everyone.** A new post appears automatically every day.
- **Easy to start, hard to master.** Tap to preview what a piece would claim and how much gold it brings; tap again to
place it. The line below the board explains why a cell fits or not – no wall of rules.
- **Three stars per map:** reach the gold target, rule the lands target, and both in one game. Targets come from
computer games on that very map, so every day is fair.
- **Leaderboard and streaks.** Only your first game counts. More stars first, then more gold, then the earlier
submission.
- **Your realm.** Unlock board themes with your streak and new banners and peoples (orcs, pirates) with your stars.
- **Reddit-native.** Post your result to the comments with one tap, earn a 🔥 streak flair, and the best player of the
day wears 👑 _Ruler of the day_ the next day.
## How we built it
- **Devvit Web** with plain TypeScript, bundled by esbuild; no UI framework. An inline start screen in the feed, the
full board in expanded mode.
- **The original engine, untouched.** The Ninelands engine is pure TypeScript (seeded RNG, `legalMoves`,
`applyMove`, replay). A sync script copies it – plus the SVG board renderer – from a tagged release, so the Reddit
edition plays exactly like the original and fixes flow in one direction only.
- **A server that trusts nothing.** The client sends only its moves. The server replays them with the same engine and
computes gold, lands and stars itself, so results cannot be faked.
- **Redis leaderboards.** Each day is a sorted set. To rank by stars, then gold, then submission time in a single
score, we use
$$\text{score} = 100\,000 \cdot \text{stars} + \text{gold} + \frac{1}{\text{seq} + 1}$$
where $\text{seq}$ is the running number of the submission. The fraction lies in $(0, \tfrac12]$, so it only breaks
ties – and it makes every score unique, which turns a player's rank into a simple
$\text{rank} = \text{count} - \text{zRank}$.
- **First attempt only**, enforced atomically with `hSetNX` – two parallel submissions can never both count.
- **Daily posts** via the Devvit scheduler, **Devvit Journeys** for analytics (journey starts when the castle is
placed, a progress event per star, the end carries the gold as score).
- **Privacy by design:** all user data expires 30 days after a player's last game.
## Challenges we ran into
- **Making the daily map unspoilable.** The website has a map of the day too – including a replay of the best game.
The Reddit edition therefore uses its own seed, so its daily map can never be looked up elsewhere.
- **Knowing when to stop.** In the original, a game ends when no move is left. On a phone that can drag on, especially
under the law _One farm per land_, where only costly knights remain. We added an _End game_ button, and the game now
detects when no remaining move earns gold and suggests ending.
- **Fitting a strategy board into a post.** The board, the goals and nine piece costs have to be readable on a phone in
portrait mode. Container queries keep the board as large as possible without pushing the hint line away.
- **Platform details:** a template workflow our CI runner didn't understand, Redis commands that are not enabled for
every app, `localStorage` that is cleared on every app update (so all choices live in Redis), and an analytics router
that expects Express while our server has no framework – we rebuilt its five endpoints by hand.
## What we learned
- A daily game lives from **one shared moment**: same map, same day, one first attempt. Every feature we kept serves
that moment; menus and settings did not make the cut.
- Contextual hints beat tutorials. Showing _why_ a move doesn't fit teaches the rules faster than any rules screen.
- Porting is easiest when the game logic is pure: the engine moved to a new platform without a single change.
## What's next
- Seasonal events beyond the winter festival, and more unlockable banners and boards.
- A weekly challenge with a twist (fog of war, bandit camps) – both already exist in the original engine.
- An optional duel against the computer after the daily map.
```
### Built with (Tags, max. 25)
typescript, javascript, html5, css3, svg, node.js, devvit, devvit-web, reddit, reddit-api, redis, devvit-scheduler,
devvit-journeys, esbuild, biome, playwright, ffmpeg, pixel-art
### „Try it out“-Links
- App: `https://developers.reddit.com/apps/ninelands-game`
- Spielen: `https://www.reddit.com/r/Ninelands/` (sobald angelegt; bis dahin nicht eintragen)
- Original-Spiel: `https://ninelands.stackteam.at`
- Code: Repo ist privat – nur eintragen, wenn es öffentlich wird (optional)
### Noch zu schreiben
- **Ausführliche Beschreibung** (Funktionen und Ablauf) – Grundlage: `README.md`.
- **Portierung angeben** (Kategorie „Best Ported Daily Game“): Ninelands gehört StackTeam; erklären, was seit dem
11. Nov. neu entstanden ist (Pflicht für bestehende Projekte): neue Codebasis auf Devvit Web, neuer Tagesmodus mit
eigenem Seed, serverseitig nachgespielte Bestenliste in Redis, Streak, Realm-Freischaltungen, Flair, Ergebnis als