# 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](./adding-a-game.md).

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.

[[toc]]

## Required methods

### init

```ts
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](./game-options.md); 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...).

### move

```ts
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](#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](#undo-and-tentative-moves).

### ended

```ts
ended (data: GameData): boolean
```

Returns whether or not the game is ended.

### scores

```ts
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](#rankings) is implemented.

### dropPlayer

```ts
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](./timing.md)).

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](./bots.md) for how this differs from platform bot slots.

### moveAI

```ts
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](./bots.md) 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](#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](https://codeberg.org/boardgamers/boardgamers/issues/251) for real AI opponents.

### currentPlayer

```ts
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

```ts
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

```ts
logSlice (data: GameData, options: {player?: number; start: number; end?: number}): any
```

Returns a log slice to be sent [player](#options-player)'s viewer from [start](#options-start) to [end](#options-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

```ts
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

```ts
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](./game-options.md).

Only update settings for the given keys. Other settings are left unchanged.

### playerSettings

```ts
playerSettings(data: GameData, player: number): Record<string, unknown>
```

Get player settings.

### rankings

```ts
rankings (data: GameData): number[]
```

Rankings for the players.

Only necessary if [scores](#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

```ts
round (data: GameData): number | undefined
```

The current round in the game.

It is shown in the game listings, and used for statistic purposes.

### cancelled

```ts
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

```ts
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

```ts
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

```ts
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.

The platform adds the per-move time increment (`timePerMove`) to the player's
clock every time `toSave` returns a state. That's why `toSave` should only
return a state for **full, completed turns**. Anything short of a full turn (tentative moves,
partial input, dry runs) must return `undefined`: it costs nothing and grants
nothing.

#### Undo and tentative moves

If your game lets a player undo, implement it with the tentative-turn model
(as gaia-project does), never as an engine move that mutates the saved state:

- The viewer accumulates the current turn locally and resends the **whole turn so
  far** on every action; the engine replays it from the last saved state.
- While the turn is incomplete (still undoable), `toSave` returns `undefined`: the
  tentative state is returned to the acting player's viewer but never persisted, and
  no time increment is granted.
- Undo is the viewer truncating its turn buffer and resending (or nothing at all,
  when the buffer empties — the saved state _is_ the turn start).
- Once the turn completes (the player can no longer undo), `toSave` returns the
  state: it is persisted, broadcast, and the increment is granted — once per real turn.

Do **not** implement undo as a move that pops entries from the saved log:

- the platform persists per move and grants the increment per saved move, so a
  player can farm time by repeatedly moving and undoing, and the undo itself is
  rewarded;
- the saved log must grow monotonically — incremental log consumers
  (`logLength`/`logSlice` bookkeeping, last-move info) assume entries are never
  removed.

### messages

```ts
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

```ts
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

```ts
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:

```ts
{
  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`.
