Home/Guides/Make an online turn-based game

guide · turn-based

Make an online turn-based game

Turn-based games look easy online until two clients play the same turn at once. This guide uses atomic writes and compare-and-swap so that can never happen.

Free sandbox · $19.99/mo to go live · cancel anytime

pattern

Everything lives in shared state

Unlike realtime games, a turn-based game sends almost nothing on the direct channel. The board, the current turn and the round number all live in shared state, validated by the server. Use preset turn_based, and leave peer-to-peer on: the direct channel carries no moves, but it lets the SDK tell the opponent a move was played so the client can call the server only every few seconds while still reacting in milliseconds.

const room = await mp.quickmatch({ mode: 'ranked', max_players: 2 });
room.setnx('round', 0);

// A move = place the piece AND pass the turn, together or not at all.
async function play(cell) {
  room.set(`board.r${round}.${cell}`, myMark);
  room.cas('turn', mp.account.id, opponentId);   // fails if it is not my turn
  try { await room.flush({ atomic: true }); }
  catch (e) { showError('Not your turn'); }
}

room.state.watch(`board.r${round}.*`, redraw);
room.state.watch('turn', (who) => setMyTurn(who === mp.account.id));

Rules that make client checks unnecessary

{
  "state": {
    "turn":     { "write": "any", "type": "string" },
    "board.**": { "write": "any", "immutable": true },
    "round":    { "write": "any", "type": "int", "monotonic": "up" }
  }
}

immutable means a placed cell can never be overwritten. cas on turn means two simultaneous moves cannot both succeed. atomic means a move never lands without its turn handoff.

Rematch and spectators

Advance the round with room.cas('round', n, n + 1). Remember that cas expecting 0 fails on a key never written — that is why the code sets it with setnx first. Enable max_spectators and friends can watch with mp.watch(code).

Play the tic-tac-toe demo to see this exact pattern, source included.

Questions
How do I stop a player from moving twice?

Use compare-and-swap on the turn: room.cas('turn', me, opponent) only succeeds if it is currently your turn. Send it atomically with the move.

Can players reconnect mid-game?

Yes. Shared state survives reconnection and is sent to anyone who joins late, so a reloaded tab resumes the game where it was.

Make your game online today

Free sandbox keys in one call, no account and no card. $19.99/month when you ship, for every game you make.

Free sandbox · $19.99/mo to go live · cancel anytime