Skip to main content

Documentation Index

Fetch the complete documentation index at: https://openturn.io/docs/llms.txt

Use this file to discover all available pages before exploring further.

A gamekit move’s run function always returns an outcome built with the move helper:
move({
  run({ G, args, move, player }) {
    // return one of:
    move.stay(patch?)
    move.endTurn(patch?)
    move.goto(phase, patch?, { endTurn?: boolean })
    move.finish(result, patch?)
    move.invalid(reason?, details?)
  },
})
Each outcome compiles to a core transition. Knowing which is which helps when you are reading the state graph or the replay log.

stay(patch)

Keep the current phase, keep the current player’s turn. The most common outcome for rounds where multiple players submit before anyone ends a turn (rock-paper-scissors plan phase). Compiles to a core transition with to: <same phase>, turn: "preserve".

endTurn(patch)

Keep the current phase, advance the turn counter by one. The most common outcome for classic turn-based games (tic-tac-toe, chess). Compiles to to: <same phase>, turn: "increment".

goto(phase, patch, options?)

Move to a different phase. By default keeps the turn counter; pass { endTurn: true } to advance it. Compiles to to: <target phase>, turn: "preserve" (or "increment"). Use goto when the game has multiple phases (planning → battle → game-over, bidding → play). The target phase must be listed in phases.

finish(result, patch)

End the match. result is any JSON-compatible record — the conventional shapes are { winner: playerID } and { draw: true }, but multi-winner, ranked, scored, and co-op results are all valid. The result is recorded on snapshot.meta.result and profile.commit reads it. The engine moves to the internal __gamekit_finished terminal state; no further moves are accepted.
return move.finish({ winner: player.id }, { board });
return move.finish({ draw: true }, { board });
return move.finish({ scores: { "0": 12, "1": 7 } });
Compiles to to: "__gamekit_finished", turn: "increment", with the result field set.

invalid(reason, details)

Reject the move. The engine returns { ok: false, error: "invalid_event", ... } to the dispatcher and leaves G unchanged. Use it when the move was legal enough to try (the player had permission to dispatch) but invalid at the current state (the square was already occupied, the dice roll was out of range, etc). Compiles to rejectTransition(reason, details) from core.

Patches

Every outcome (except invalid) takes an optional patch: a Partial<TState> that is shallow-merged into G. If you omit it, G is unchanged.
return move.endTurn({ board, lastMove: { row, col } });
Patches only express the fields that change. The rest of G is preserved.

enqueue

Every outcome accepts { enqueue: [...] } for follow-up internal events:
return move.endTurn({ board }, {
  enqueue: [{ kind: "resolveEffects" }],
});
Gamekit forwards enqueue straight to the core reducer. Internal events run through your move definitions as if they had been dispatched, but with no player attribution.

Picking the right outcome

  • “I want the same player to go again”: stay.
  • “I want the next player to go”: endTurn.
  • “I want to change phases”: goto.
  • “The game is over”: finish — pass { winner }, { draw: true }, or any JSON-shaped result your game records.
  • “The move is illegal”: invalid.