Skip to content

Game Interface

Every Ludus game implements the Game<TState, TAction> generic interface. This enables the protocol to run any game through the same engine, record deterministic replays, and generate commentary — without knowing anything about the game’s specific rules.

Both Konquista and Destreect have been migrated to this interface.

import type { GameConfig, GameMetadata, Player, PlayerRanking, SeededRNG } from '@ludus/game-engine';
interface Game<TState extends BaseGameState, TAction extends BaseAction> {
/** Game metadata (name, player range, description) */
metadata: GameMetadata;
/** Create initial game state from config */
initialize(config: GameConfig, players: Player[], rng: SeededRNG): TState;
/** Get valid actions for a player in current state */
getValidActions(state: TState, playerId: string): TAction[];
/** Apply an action to produce a new state (must return new object) */
executeAction(state: TState, action: TAction, rng: SeededRNG): TState;
/** Check if the game is over */
isGameOver(state: TState): boolean;
/** Get the winner (null if draw or not over) */
getWinner(state: TState): Player | null;
/** Get final rankings for all players */
getRankings(state: TState): PlayerRanking[];
/** Human-readable description of an action */
describeAction(action: TAction, state: TState): string;
}

All game states extend BaseGameState:

interface BaseGameState {
gameId: string;
turn: number;
activePlayerId: string;
playerOrder: string[];
eliminated: string[];
maxTurns: number;
}

All actions extend BaseAction:

interface BaseAction {
type: string;
playerId: string;
}

Every game receives a SeededRNG instance (Mulberry32 algorithm) instead of Math.random(). This guarantees identical results from the same seed, enabling replay verification.

import { SeededRNG } from '@ludus/game-engine';
const rng = new SeededRNG(42);
rng.next(); // Always the same value for seed 42
rng.range(1, 6); // Deterministic dice roll
rng.shuffle(arr); // Deterministic shuffle

The StateManager enforces that executeAction returns a new state object. If it returns the same reference, a StateImmutabilityError is thrown. This prevents accidental mutation bugs.

GameLoopController runs the game loop for any Game<TState, TAction>:

  1. Initialize state with SeededRNG
  2. Each turn: get valid actions → agent decides → validate action → execute → emit events
  3. Record every action via ReplayRecorder
  4. On game over: produce GameResult with replay, rankings, final state

See @ludus/game-engine SDK Reference for the full API.