Game engine API
Your game engine should be a NPM module and export a number of methods.
To host the engine on the platform — from the npm registry or an uploaded npm pack tarball — see Adding a game.
Most of the methods return GameData, the game state. You can alter the GameData
passed in the argument and return, or return a whole new object.
Required methods
init
init (players: number, expansions: string[], options: any, seed: string, creator?: number): Promise<GameData>
Creates the initial game data. The function can be asynchronous - if you need to make an API call to an external tool like a map generator, for example.
After initialization, the players are 0-indexed: a 4 player games will have players 0, 1, 2 and 3.
creator is the index of the player that created the game. It can be undefined if the creator of the game is not a player.
players
The number of players
expansions
The list of expansions activated
options
An object containing the options chosen for the game — the values the creator picked from the options declared
on the game info. See Game options, preferences & settings; always read defensively, keys may
be absent.
seed
The random seed for the game. Games with the same seed should always produce the same random results (map generation, card shuffling, dice throws...).
The seed is stored on the game document and visible to players. If your engine keeps secrets
(defines stripSecret), the platform hashes it with a server-only key before
passing it here, so players cannot precompute the hidden random results (card order, dice
throws, ...) from the stored seed. Override with the optional hashSeed engine export:
export const hashSeed = true; // force hashing (no stripSecret, but the seed drives hidden data)
export const hashSeed = false; // force the raw seed (stripSecret engine whose secrets don't depend on the seed)
When omitted, hashing follows the presence of stripSecret.
move
move (data: GameData, move: any, player: number): GameData
Execute a move by player.
Throw an error if the move is invalid.
Note that if you implement toSave to not save the result, you can send the result to the UI without storing the modified game state in the database. It is useful when you want to allow the user to play around and only confirm at the very end. If your game supports undoing moves, this is the required way to implement it — see Undo and tentative moves.
ended
ended (data: GameData): boolean
Returns whether or not the game is ended.
scores
scores (data: GameData): number[]
Give the score of each player. The scores are displayed in the sidebar next to player games. They are also used to determine the player ranking at the end for elo calculation, unless rankings is implemented.
dropPlayer
dropPlayer(data: GameData, player: number): Promise<GameData>
Drop a player out. Called when a player quits or is dropped after running out of time (see Game clocks & timing).
You should either change the player to an AI or completely remove them from the game. If it is the current player, the turn should automatically go to the next, non dropped-player. It must be safe at any point of the game — see Bot players for how this differs from platform bot slots.
moveAI
moveAI(data: GameData, player: number): Promise<GameData> | GameData
Play one move for player, chosen by the engine. This powers the platform's bot players:
when a bot slot becomes the current player, the game-server calls moveAI and stores the
result like a regular move. It should be exported from the engine's entry point (the
module the game-server loads), since that's what the bot driver calls. See
Bot players for the full platform behavior (scheduling, failure policy, requirements).
Optional — only games whose engine implements moveAI can have bot players. The game-server
auto-detects it when an engine version is installed (probing the entry point for a
moveAI export) and records it as meta.bots on the game info, which the creation UI uses to
offer bot seats. All five current games implement it: Power Grid, Container, 6nimmt! (take6),
Splendor and Gaia Project. Reusing the auto-play logic from dropPlayer is a good
starting point; a simple random legal move is fine.
Bots are deliberately dumb — they're for testing the UI solo, not for enjoyment. See issue #251 for real AI opponents.
currentPlayer
currentPlayer(data: GameData): number | number[] | undefined
Get the current player(s). Some games like 6nimmt can have multiple players that can play at the same time.
When the game is ended, the currentPlayer can be undefined.
logLength
logLength(data: GameData): number
Returns the length of the log of the game.
It is used to send log slices to the viewer, especially when executing a move, when we want to send only the new log items to the viewer.
logSlice
logSlice (data: GameData, options: {player?: number; start: number; end?: number}): any
Returns a log slice to be sent player's viewer from start to end included.
The return value can have any structure. We recommend something like {log: items[], availableMoves?: moves[]} to show the log and the available moves
at the final state.
options.player
The player to whom to send the log.
If undefined, it is to a spectator. Otherwise it corresponds to the player id of the player.
The secrets should be stripped from the log items, depending on who receives the log info. For example, in a card game, the spectators or other players should not see a player's card - unless the game is already ended.
options.start
The beginning of the log slice.
options.end
The end of the log slice. If undefined, it corresponds to the very end of the existing log.
Optional methods
setPlayerMetaData
setPlayerMetaData(data: GameData, player: number, metaData: {name: string}): GameData
Set metadata on the player, from BGS.
name: Name of the player
Other metadata such as an avatar, clan name, ... could be given in the future.
setPlayerSettings
setPlayerSettings(data: GameData, player: number, settings: Record<string, unknown>): GameData
Update player settings - such as autocharge in Gaia Project. The available settings are declared on the game info — see Game options, preferences & settings.
Only update settings for the given keys. Other settings are left unchanged.
playerSettings
playerSettings(data: GameData, player: number): Record<string, unknown>
Get player settings.
rankings
rankings (data: GameData): number[]
Rankings for the players.
Only necessary if scores does not give enough information to rank the players. For example, if the player with the smallest score is the winner, or if there are tie-breaking conditions not reflected in the score.
The first player should be ranked 1, the second player ranked 2, ...
If there are ties, just give the same ranking.
round
round (data: GameData): number | undefined
The current round in the game.
It is shown in the game listings, and used for statistic purposes.
cancelled
cancelled (data: GameData): boolean
Returns true if the game is cancelled. For example if a player drops out too early in the game, during faction selection.
Note that this is probably going to be deprecated. It can complicate things for tournaments, ...
factions
factions (data: GameData): Array<string | undefined>
Return the faction of each player, if applicable.
Used for thumbnails in game lists and sidebar - images can be defined for each faction.
Also used for statistics.
stripSecret
stripSecret (data: GameData, player?: number): any;
Middleware to process data to be sent to a player's viewer, strip secrets if needed.
In case of a spectator, player is undefined.
toSave
toSave(data: GameData): any | undefined
Middleware to process data to be stored in the database.
Return undefined to NOT store the data. It can happen for example
when a player executes a move without confirming it, a dry run so to speak.
By default, saving a state credits the mover with timePerMove and publishes a turn summary.
Use isLiveUpdate to share intermediate changes and
timeIncrements to control when players earn time.
Return undefined for private tentative input that should not be saved.
isLiveUpdate
isLiveUpdate(data: GameData): boolean
Called on the saved state returned by toSave. Return true to share a change without publishing a turn summary:
- Update active players from
currentPlayer(data). - Pause clocks for players who finish; resume them when they become active again.
- Add no turn summary or notification. Time increments follow
timeIncrementswhen provided.
Return false (or omit this hook) for normal turn handling. Game endings always
use normal handling, regardless of this flag.
timeIncrements
timeIncrements(data: GameData): number[]
Return each player's cumulative number of earned increments, for example [3, 2, 3].
Each increase credits timePerMove immediately, on live and normal saves alike.
For cooperative choices, increase it on the first confirmation. Undoing and reconfirming
keeps the count unchanged. Confirming a new choice forced by a teammate increases it again.
Replay and admin edits never grant time. Without this hook, normal saves credit only the mover.
Undo and tentative moves
For private, undoable choices, keep the turn in the viewer and return undefined
from toSave until it is confirmed. Undo edits that local turn. For shared choices,
use live saves instead. Never remove published log entries; other viewers read
logs incrementally.
canMoveOutOfTurn
canMoveOutOfTurn(data: GameData, move: unknown, player: number): boolean
Return true to allow this move from an inactive player, for example Change my choice
after clicking Ready. The engine then validates the move normally.
Absent or false, only active players may move.
For repeatable ready/reopen actions, keep completion counts unchanged and coalesce the toggles in replay history.
messages
messages(data: GameData): {messages: string[], data: GameData}
Important messages / game events to show in the game's chat.
data should be modified so that a subsequent call does not show the same messages.
replay
replay (data: GameData, options?: { to?: number }): GameData
Replays a game.
It can be called after the database is manually edited, or the game engine is updated.
to is optional and means replaying to that move, e.g. replay(data, {to: 1}) will only redo the first move, and
the rest of the moves will not be played.
stats
stats (data: GameData): Record<string, Many<Record<string, number | string>>>
Gets stats on a game, to be used to write to CSV. The Many type is type Many<T> = T | T[].
For example, here is what it could return for one game:
{
basic: {
point: 120,
turns: 500
},
detailed: [{
player: 0,
boosters: 5,
turns: 120
}, {
player: 1,
boosters: 9,
turns: 100
}]
}
Here two CSVs would be generated, basic.csv and detailed.csv.