In a multiplayer board game, the dangerous bug is not a crash. It's two players seeing different boards, or one player doing something the rules don't allow. The fix that has worked best for me is boring: put the rules in pure functions, and run them only on the server.
One shape for every game
Each game's rules live in a reducer: given the current state and an action ("roll", "buy", "vote"), it returns the next state or an error. It doesn't read the clock, call the network or touch the database. A thin layer around it loads the state, calls the reducer, and saves the result.
function reduce(state: State, action: Action, player: PlayerId): State | Error {
// validate that it's this player's turn, the action is legal,
// then return a new state. No side effects.
}
Why that pays off
- Testing is cheap. A reducer is a function, so a test is "start here, do this, expect that". The project now runs close to a thousand such tests in seconds, and many of them are rules a player would argue about.
- The client can't cheat. The browser sends an intent ("I roll"), never a result. The server rolls the dice.
- Secret information stays secret. In a hidden-role game, the secret word never reaches anyone it shouldn't, because the part of the code that picks it only runs on the server and isn't imported by any browser code. A test fails if that ever changes.
Where it still gets hard
Timers are the awkward part. A turn clock isn't a pure input, so I pass the current time in as part of the action and let the server decide when an action like "time out" is sent. Reconnection is the other: a player who drops and returns should get the authoritative state, not replay missed events.
If you build a turn-based multiplayer board game, start with the reducer before the UI. The UI is the part you'll rewrite anyway. My own project is Boardit, a set of free multiplayer board games online that run in the browser. You can play with friends by sharing a room code, and it isn't only for friends: open tables and Quick match seat you with other players.
Top comments (0)