project · July 24, 2026 · 3 min read

Royal Lands

An online multiplayer kingdom-building tile-placement game for 2–4 players, built to learn Go and PixiJS — server-authoritative rules engine, event-sourced SQLite persistence, WebSockets, deployed on Railway.

gotypescriptpixijssqlite

Royal Lands

Royal Lands is an online multiplayer kingdom-building tile-placement game for 2–4 players, playable in the browser at royal-lands.up.railway.app. Players take turns drafting dominoes and placing them to grow a 5x5 kingdom, scoring by matching terrains and crowns — with a Middle Kingdom bonus for keeping your castle centered.

I built it to learn two things at once: Go on the server and PixiJS on the client. Deliberately no frameworks in the way — the backend is Go’s stdlib net/http plus a WebSocket library (the entire Go dependency list is two modules), and the frontend is PixiJS v8 with TypeScript and Vite, no React. The point was to learn the actual technologies, not a framework’s wrapper around them.

How the server works

The server is authoritative: clients send intents, the server validates, applies, and broadcasts state. It’s split into three strict layers:

  • A pure rules engine — placement validation, flood-fill scoring, drafting — with no I/O or goroutines, covered by table-driven tests.
  • A room layer where one goroutine owns each room’s game state, receiving actions over a channel and broadcasting back. No mutexes on game state; the goroutine is the lock. “Share memory by communicating” finally clicked for me here.
  • A thin WebSocket transport with one reader and one writer goroutine per connection.

Games that survive redeploys

Persistence is event sourcing on SQLite: every accepted action appends an event to a per-room journal, and on startup the server replays non-finished rooms through the same rules engine used live — decks are regenerated from a logged seed, so replay is deterministic. Live games survive server restarts and Railway redeploys. A TTL sweeper expires idle lobbies and abandoned games, and a summary table powers history and leaderboards without replaying logs.

The client

The PixiJS client is a deliberately dumb renderer: one applyState per scene reconciles the screen with the latest server snapshot. On top of that sit the parts that made it feel like a real game — lobby and room scenes, a pan-and-zoom kingdom view with a shrinking placement window and locked-boundary walls, clickable minimaps to peek at opponents’ kingdoms, touch controls (tap to preview, tap again to confirm), and a PWA production build with the client embedded in the Go binary — the whole thing deploys as a single container.

Krushi Raj Tula

Krushi Raj Tula · a developer, geek, and enthusiast who loves solving hard problems and fixing things with technology. Reach out on Twitter.

© 2026 Krushi Raj Tula · Designed & built by me, like everything else here · Source on GitHub