Trivium REST API

Overview

This documentation describes the REST API for building Trivium bots. See the game rules for how to play.

Important: Bot team names must include a version number (e.g., MyBot/1.0 or MyBot/2.1.3).

Endpoints

POST/api/games/{gameId}/moves

Make a move.

// Request:
{
  "playerId": "uuid",
  "type": "P | S | M",
  "pawn": 0,
  "to": [1, 2]
}

// Response:
{
  "success": true,
  "message": "...",
  "gameState": null
}

POST/api/games/{gameId}/valid-moves

Get all valid moves for the current turn.

// Request:
{
  "playerId": "uuid"
}

// Response:
{
  "validMoves": [
    { "type": "P" },
    { "type": "S", "to": [5] },
    { "type": "M", "pawn": 0, "to": [3] },
    { "type": "M", "pawn": 1, "to": [7, 12] }
  ]
}

Note: In the response, pawn is the absolute pawn index (0–8). When making a move, use your relative pawn index (0–2).

Game Request Endpoints

Use these endpoints to create and join games. You can add constraints such as self-play (your bot vs itself) or invite-only matches.

POST/api/requests/create

Create a new game request. Other bots can discover and join your request.

// Request:
{
  "teamName": "MyBot/1.0",
  "constraints": {
    "selfPlayOnly": false,
    "excludeTeams": [],
    "inviteOnly": []
  }
}

// Response:
{
  "playerId": "uuid",
  "requestId": "uuid",
  "status": "created",
  "slotsFilledCount": 1
}

Constraint Options

POST/api/requests/{requestId}/join

Join an existing game request.

// Request:
{
  "teamName": "MyBot/1.0"
}

// Response:
{
  "playerId": "uuid",
  "requestId": "uuid",
  "status": "joined",
  "gameId": "uuid",
  "slotsFilledCount": 2,
  "message": null
}

When status is "started", the game has begun and gameId contains the game ID.

GET/api/requests/{requestId}

Get the status of a game request.

{
  "requestId": "uuid",
  "createdBy": "MyBot/1.0",
  "constraints": {},
  "slotsFilledCount": 2,
  "slots": [
    { "colour": "RED", "teamName": "MyBot/1.0" },
    { "colour": "GREEN", "teamName": "OtherBot/2.0" },
    { "colour": "BLUE", "teamName": null }
  ]
}

GET/api/requests?teamName={teamName}

List open game requests. Optionally filter by team name to see only requests you can join.

{
  "requests": [
    {
      "requestId": "uuid",
      "createdBy": "SomeBot/1.0",
      "constraints": {},
      "slotsFilledCount": 1,
      "slots": []
    }
  ]
}

Data Structures

GameState

Represents the current state of the game.

{
  "colourInTurn": "RED" | "GREEN" | "BLUE",
  "scoreBoard": [int, int, int],
  "board": [int, ...]
}

scoreBoard Array

Contains three integers representing each player's current score (0–60):

The first player to reach exactly 60 wins. You cannot exceed 60.

board Array

Contains nine integers representing the positions of all nine pawns:

Board Layout

The Board is a triangular pyramid with 21 cells (numbered 0–20):

[20] [18][19] [15][16][17] [11][12][13][14] [ 6][ 7][ 8][ 9][10] [ 0][ 1][ 2][ 3][ 4][ 5]
Row 5 (top) Row 4 Row 3 Row 2 Row 1 Row 0 (bottom)

Pawns enter from the bottom row (0–5) and can move to adjacent cells. Jumping over an opponent's pawn knocks it OUT.

Scoring

When you make a score move, you advance on the Score Track by the row number of your highest pawn plus 1:

Your new position = current score + points. You cannot make a score move if the sum thereby exceeds 60.

Note: The web UI displays rows as 1–6 for readability, but the API is 0-indexed throughout: rows are 0–5, cells are 0–20, and pawns are 0–2.

Move Types

P (Pass)

Skip your turn. Always valid.

{
  "playerId": "uuid",
  "type": "P",
  "pawn": -1,
  "to": []
}

S (Score Points)

Advance on the Score Track. The to field contains your new score position (current score + points from your highest pawn's row + 1).

{
  "playerId": "uuid",
  "type": "S",
  "pawn": -1,
  "to": [newPosition]
}

Example: If your score is 37 and your highest pawn is on row 3, you send "to": [41] (37 + 4 points).

M (Move a Pawn)

Move one of your pawns on the Board.

Enter the board (pawn is OUT, move to the bottom row)

{
  "playerId": "uuid",
  "type": "M",
  "pawn": 0,
  "to": [3]
}

Simple move (move to an adjacent cell)

{
  "playerId": "uuid",
  "type": "M",
  "pawn": 1,
  "to": [12]
}

Jump move (from cell 1, jump over opponent in cell 7, land on cell 12)

{
  "playerId": "uuid",
  "type": "M",
  "pawn": 2,
  "to": [7, 12]
}

Multi-jump (from cell 1, chain jumps along diagonal 1-7-12-16-19)

{
  "playerId": "uuid",
  "type": "M",
  "pawn": 0,
  "to": [7, 12, 16, 19]
}

This knocks OUT opponents in cells 7 and 16, landing on cell 19.

Remark: when jumping over opponent's pawns, the "to" field specifies not only the destination cells, but also the cells over which the pawn is jumping. Jumps must follow straight lines on the board (horizontal or diagonal).

Example Bot Flow

Open Match (Recommended)

Create a game request and wait for other bots to join:

1. POST /api/requests/create with { "teamName": "MyBot/1.0" }
   -> Get playerId, requestId
2. Connect to SSE: GET /api/players/{playerId}/events
3. Wait for 'started' event     -> Get gameId, yourColour, teamNames
4. Loop until 'ended' event:
   - On 'state' event with isYourTurn=true:
     - POST /api/games/{gameId}/valid-moves with { "playerId": "..." }
     - Choose a move
     - POST /api/games/{gameId}/moves

Self-Play (Bot vs Itself)

Create a game where only your bot can join all three slots:

1. POST /api/requests/create with:
   { "teamName": "MyBot/1.0", "constraints": { "selfPlayOnly": true } }
   -> Get requestId, playerId1
   -> Connect to SSE: GET /api/players/{playerId1}/events

2. POST /api/requests/{requestId}/join with { "teamName": "MyBot/1.0" }
   -> Get playerId2
   -> Connect to SSE: GET /api/players/{playerId2}/events

3. POST /api/requests/{requestId}/join with { "teamName": "MyBot/1.0" }
   -> Get playerId3
   -> Connect to SSE: GET /api/players/{playerId3}/events

4. All three SSE connections will receive 'started' event
5. Play the game with all three playerIds

Join Existing Game

Find and join an open game request created by another bot:

1. GET /api/requests?teamName=MyBot/1.0
   -> Get list of joinable requests
2. POST /api/requests/{requestId}/join with { "teamName": "MyBot/1.0" }
   -> Get playerId
3. Connect to SSE: GET /api/players/{playerId}/events
4. Wait for 'started' event when the game fills up

Real-time Updates (SSE)

Use Server-Sent Events for real-time game updates. Connect immediately after joining the queue to receive the started event when matched.

GET/api/players/{playerId}/events

Opens an SSE stream for real-time game events. Connect immediately after /api/games/join.

Event Types

Event Data Format

// started event (received when matched with opponents)
{
  "gameId": "uuid",
  "yourColour": "RED",
  "gameState": GameState,
  "teamNames": { "RED": "Bot1/1.0", "GREEN": "Bot2/1.0", "BLUE": "Bot3/1.0" }
}

// state event
{
  "gameId": "uuid",
  "gameState": GameState,
  "isYourTurn": true,
  "isFinished": false,
  "winner": null
}

// ended event
{
  "gameId": "uuid",
  "winner": "RED",
  "finalState": GameState
}

Usage Notes