All work

Tuan Tanah

Real-time multiplayer Monopoly with an Indonesian theme — pinjol loans, role passives, structured negotiation, 2–8 players.

React.jsReact-RouterRedisPostgresTypeScriptNode.jswebsocketDockerSelf HostedSentry
Tuan Tanah interface
I built Tuan Tanah to work on a harder class of problem than a typical CRUD app: keeping many concurrent players in perfect agreement about a shared, fast-changing game state, in real time, without corrupting it under concurrent actions. Rather than reskin an existing Monopoly clone, I designed the game system itself — an Indonesian-flavored economy with two property tracks, a pinjol (informal payday loan) mechanic with real debt consequences, role-specific passive abilities, and structured deal negotiation between players, alongside the classic buy/rent/tax/jail loop.
Solo developer — game design (rules, economy, negotiation mechanics) and full implementation: the pure game engine, the Fastify/[Socket.io](http://Socket.io) server, and the React/Zustand client
Full game loop playable end to end; deployed via Docker Compose (Caddy + Postgres + Redis) on a VPS. Remaining work is balance tuning and finishing i18n coverage

The server is the single source of truth

Clients only ever emit requests; the server resolves them and broadcasts the authoritative GameState. That is what made keeping 2–8 concurrent players in perfect agreement a tractable problem rather than a constant source of desync bugs.

Rejected: Clients applying moves locally and reconciling afterwards

A pure, I/O-free game engine, separate from the socket server

All mutation logic lives in functions that touch no I/O, so the entire ruleset is testable with Vitest without ever opening a socket connection. Whether the server and every client agree on what just happened becomes a test rather than a bug report.

Rejected: Resolving moves inside the [Socket.io](http://Socket.io) handlers

Modelled the economy as data plus pure functions

Two property tracks, pinjol loans, negotiable deals and role passives are described as data rather than scattered conditionals — which is what made the ruleset possible to balance afterwards instead of dangerous to touch.

Rejected: Encoding the rules as conditionals spread through the turn loop

Designed an original economy rather than reskinning a Monopoly clone

Two property tracks, a pinjol mechanic with real debt consequences, role-specific passive abilities and structured deal negotiation, alongside the classic buy/rent/tax/jail loop. Designing the system is what makes the real-time synchronisation problem worth solving.

Rejected: Reskinning an existing clone

My Role & Impact

I designed and built the entire game solo, then played and balanced it myself: room creation, join, leave and reconnect with shareable URLs; the full turn loop; every meta-action (invest, work, hustle, sabotage, korupsi, negotiate); property and tier upgrades and sells; the pinjol loan and debt-resolution system; structured negotiation deals; role passives; voting; elimination and bankruptcy cascades; and win conditions. The architecture makes the server the single source of truth — clients only ever emit requests, a pure, I/O-free game engine resolves them, and the server broadcasts the authoritative GameState — which is what made keeping 2-8 concurrent clients in sync a tractable problem instead of a source of constant bugs, and let me test the entire ruleset with Vitest without ever spinning up a real socket connection.

What I Learned

  • Real-time state-sync design: pushing all mutation logic into a pure, I/O-free engine so "did the server and every client agree on what just happened" becomes testable, not a source of subtle desync bugs.
  • Modeling a genuinely complex economy — two property tracks, loans, negotiable deals, role-based passives — as data plus pure functions instead of scattered conditionals, which made the ruleset far easier to balance later.
  • That finishing a project means finishing the unglamorous 20% too: i18n gaps and balance tuning are what's left, and they matter as much to "done" as the real-time architecture did.

Showcase

Tuan Tanah — the board, rebuilt for here
Tuan Tanah — the board, rebuilt for here
Structured negotiation — players trade property, cash and debt through a proposal the server validates
Structured negotiation — players trade property, cash and debt through a proposal the server validates
Roles and room setup — each role carries its own passive, alongside a shareable join code
Roles and room setup — each role carries its own passive, alongside a shareable join code

Screenshots

The full game loop, as played.

Lobby — role picking, room-master settings, and a join code held by a reconnect token
Lobby — role picking, room-master settings, and a join code held by a reconnect token
A turn in progress — roll, move, and the meta-actions available this turn
A turn in progress — roll, move, and the meta-actions available this turn
Buying a property — the client only requests, the engine resolves
Buying a property — the client only requests, the engine resolves
Property detail — tiers, upgrade costs, and sell-to-bank value
Property detail — tiers, upgrade costs, and sell-to-bank value
Negotiation — a structured deal proposal between two players
Negotiation — a structured deal proposal between two players
Hustle — a meta-action resolving into a cash event
Hustle — a meta-action resolving into a cash event
Game over — final standings after the elimination and bankruptcy cascade
Game over — final standings after the elimination and bankruptcy cascade