Skip to content
L0laapk3 edited this page Nov 24, 2020 · 29 revisions

Litama Communication Protocol

Documentation for Litama, a server for the Onitama board game.

See the end of this document for a Python client example.

Connecting

Connect to the server at wss://litama.herokuapp.com with WebSocket. Note that you can also use the ws protocol instead of wss, but your connection will not be secure.

After connecting, you will be able to send commands and receive data from the server.

Commands

create - Create a game

Format: create [username]

Example: create SomeUsername

join - Join a game

Format: join [matchId] [username]

Example: join 5fa04e6ec83ffd1ff3e70217 AnotherUsername

state - Get the game state for a game

Format: state [matchId]

Request the state of the game. This is the same information given to you if you have subscribed to a game state with the spectate command.

Example: state 5fa04e6ec83ffd1ff3e70217

move - Make a move on the board

Format: move [matchId] [token] [card] [move]

  • [card] must be in lowercase.
  • For [move], letters are used for columns and numbers are used for rows. This is the same as in chess.

The server responds to this command with a confirmation that the move was made. If you are on a connection that has been subscribed to the game state (i.e. you sent a spectate command), you will receive the game state as well in a following message.

Example: move 5fa04e6ec83ffd1ff3e70217 DpEI2fMXGur0yKKLPljlLjrbDegn53RN boar a1a2

spectate - Subscribe to a game's state broadcasts

Format: spectate [matchId]

This command subscribes you to all future game state broadcasts of this game. This is useful after you create or join a game, or as a spectator.

After a successful spectate command, you will receive 2 messages from the server: one to confirm the spectate command and one which broadcasts the current game state.

Example: spectate 5fa04e6ec83ffd1ff3e70217

Server Responses

All responses by the server are in JSON format.

  • All responses contain a "messageType" key (string value) which tells you what the server is responding to or sending, as well as what you should expect in the message.
  • All responses contain a "matchId" key (string value) so that you know which game it is responding to. This allows you to play concurrent games over the same Websocket connection. Note that the matchId does not always have to be valid. For example, when trying to join a game with an invalid match id, the error message will use the id you sent.

The possible values for "messageType" are:

  • "create"
  • "join"
  • "state"
  • "move"
  • "spectate"
  • "error"

These mirror the commands because a reply is sent in response to a command. The messageType of the reply will almost always be the same as the command you just used (exceptions include error and state messages). The format of each is described below.

create

Fields:

  • messageType (string)
  • matchId (string) - You must give this ID to your opponent so that they can connect to the game.
  • token (string) - Use this token to make moves during the game. Do not share this.
  • index (int) - Keep track of this to know which side you are on. You need to check the state packet to see which side is your index.

Example:

{
    "messageType": "create",
    "matchId": "5f9e0312cf7016b31ea197c0",
    "token": "waN5rQjbbZJcPs4QuejoApUqDzcqcPSe",
    "index": 0
}

join

Fields:

  • messageType (string)
  • matchId (string) - The id of the match that you just joined.
  • token (string) - Same as create command.
  • index (int) - Same as create command.

Example

{
    "messageType": "join",
    "matchId": "5f9e0312cf7016b31ea197c0",
    "token": "DpEI2fMXGur0yKKLPljlLjrbDegn53RN",
    "index": 1
}

state

Fields:

  • messageType (string)
  • gameState (string) - Either "waiting for player" (when only 1 player has joined), "in progress", or "ended".
  • usernames (JSON object containing strings) - The usernames of the players connected to the match. Both values will be the username of the first player if a second player has not connected yet. This is to prevent bruteforcing a desired side.
  • matchId (string)
  • indices* (JSON object containing ints) - The indices associated to each side.
  • currentTurn* (string) - Either "blue" or "red" and indicates who must make the next move.
  • cards* (JSON object containing string and 2 string arrays) - Contains the cards in play for each player and on the side.
  • startingCards* - Same as cards but for the cards at the start of the game.
  • moves* (string array) - Contains all the moves that have been made. Moves are in "card:move" format. E.g. "boar:e1e2".
  • board* (string) - String representation of the current board. The leftmost digit is blue side. Each number represents a different piece, where 0 = no piece, 1 = blue student, 2 = blue master, 3 = red student, 4 = red master. See https://git.io/onitama to see how the board is laid out. The first character represents the piece of a1, second on b1, then c1, etc.
  • winner* (string) - Either "blue", "red", or "none".

Fields marked with * are only included in games with a gameState of in progress or ended.

Example:

{
    "messageType": "state",
    "usernames": {
        "red": "username1",
        "blue": "username2"
    },
    "indices": {
        "red": 1,
        "blue": 0
    },
    "matchId": "5f9e0312cf7016b31ea197c0",
    "currentTurn": "red",
    "cards": {
        "red": ["tiger", "crane"],
        "blue": ["crab", "ox"],
        "side": "elephant"
    },
    "startingCards": {
        "blue": ["crab", "elephant"],
        "red": ["tiger", "crane"],
        "side": "ox"
    },
    "moves": ["elephant:a1b2"],
    "board": "0121101000000000000033433",
    "gameState": "in progress",
    "winner": "none"
}

move

Just replies to let you know it succeeded.

Fields:

  • messageType (string)
  • matchId (string)

Example:

{
    "messageType": "move",
    "matchId": "5f9e0312cf7016b31ea197c0"
}

spectate

Just replies to let you know it succeeded.

Fields:

  • messageType (string)
  • matchId (string)

Example:

{
    "messageType": "spectate",
    "matchId": "5f9e0312cf7016b31ea197c0"
}

error

  • messageType (string)
  • matchId (string)
  • error (string) - An error message. One of "Invalid command sent", "Match not found", "Not allowed to join", "Game ended", "Token is incorrect", "'move' or 'card' not given properly", "Cannot move opponent's pieces or empty squares", or "Invalid move".
  • query (string) - Shows which command this error is a response to

Example:

{
    "messageType": "error",
    "matchId": "5f9e0312cf7016b31ea197c0",
    "error": "Invalid move",
    "query": "move"
}

Python client

Here is a Python client to test out the server. Note that you need to press enter whenever you are expecting multiple server responses (i.e. after a spectate command or after any other command when subscribed to a game state).

import asyncio
import websockets

async def litama():
    uri = "wss://litama.herokuapp.com"
    async with websockets.connect(uri) as websocket:
        while True:
            sent = input("> ")

            # Press enter to just wait for the server's next response instead of sending anything
            # This is useful when you get multiple responses for a single command.
            # I.e. After sending a create, join, move, or spectate command.
            if sent != "":
                await websocket.send(sent)

            received = await websocket.recv()
            print(f">>> {received}")


asyncio.get_event_loop().run_until_complete(litama())