42
Login With Github

Writing A Client

Warning: writing a client is an advanced subject for people who want to use programming languages outside the official clients, it is highly recommended for beginners to use the official clients

About This Guide

This guide will go through the steps of building a botskrieg client. The examples are written in Python3 and can be more or less translated into any language

Your programming language will need to support the following things


Connecting to Botskrieg

Botskrieg currently supports TCP connections (with Websocket Connections on the way)

Setting up the connection

Use your language's socket libraries to connect to botskrieg, use SSL to connect to port 4242 on "botskrieg.com"

Python3 Example Code:

import socket
import ssl

connection = socket.create_connection(("botskrieg.com", 4242))
context = ssl.create_default_context()
connection = context.wrap_socket(connection, server_hostname="botskrieg.com")

A Note on BattleBox Development Servers

Its useful to developers of the server to be able to point clients towards their development servers. As a matter of convention, it would be nice to allow your client to accept a parameter specifying the URI of the server to connect to. Since most dev servers are run without SSL, its nice if your client can handle unencrypted TCP connections

As a matter of convention, please use the scheme "battleboxs://" to specify a SSL TCP connection, and "battlebox://" to specify a plaintext TCP connection

Some Examples:

Most users will not run development servers, so default to battleboxs://botskrieg.com:4242


Reading and Writing Messages

The structure of messages is shown below. Messages coming from the server should be in this format. Messages going to the server should be encoded in the same way

The first two bytes of the message represent the length in bytes of the JSON encoded data. The message length is Network Order (Big Endian) Unsigned 16 bit integer

Message length is strictly limited to 2 ** 16 (65536) bytes. Your client should enforce this limit, as attempting to send a message larger than 65536 bytes will render the connection unusable

length (2 bytes)
JSON Encoded Message Data (length bytes)

A special ping message can be sent to the server to test your encoding

Python3 Example Code:

This uses the "connection" object from the previous example

import json
import struct

def send_message(connection, msg):
  msg_bytes = str.encode(json.dumps(msg))
  header = struct.pack("!H", len(msg_bytes)) # b'\x00\x06'
  connection.sendall(header + msg_bytes)

def receive_message(connection):
  msg_size_bytes = connection.recv(2)
  (msg_size,) = struct.unpack("!H", msg_size_bytes)
  message_json = connection.recv(msg_size)
  message = json.loads(message_json)
  return message

send_message(connection, "PING")
receive_message(connection) # PONG

Authenticating

After creating an account on botskrieg create an API key here (New API Key)

To authenticate, send the bot name and token in a JSON object of the following form

{
  "bot": "some-bot-name",
  "token": "{YOUR KEY HERE}"
}

Bot names can be any string providing the name meets the following rules

Python3 Example Code:

This builds on the functions from the previous examples

send_message(connection, {"bot": "some-bot-name", "token": "{your token here}"})
receive_message(connection)

On successful authentication you will receive

{
  "bot_server_id": "b90fa044-af5c-4ec2-a3a2-35a273451f09",
  "connection_id": "eb0642f4-02b6-4e90-9bad-69c071757d4c",
  "status": "idle",
  "watch": {
    "bot": "{some url}",
    "user": "{some url"
  }
}

Watch Links can be displayed to the user who can watch their bot/user play in browser

Some Authentication Errors You May Encounter (not limited to)

{"error":{"token":["Invalid API Key"]}}
{"error":{"user":["User is banned"]}}
{"error":{"user":["User connection limit exceeded"]}}
{"error":{"bot":{"name":["Can only contain alphanumeric characters or hyphens"]}}}
{"error":{"bot":{"name":["should be at most 39 character(s)"]}}}
{"error":{"bot":{"name":["Cannot end with a hyphen"]}}}
{"error":{"bot":{"name":["Cannot start with a hyphen"]}}}
{"error":{"bot":{"name":["Cannot contain two hyphens in a row"]}}}

Starting a Practice Match

Start a practice match in the following manner

{"action":"practice","arena":"some-arena","opponent":{}}

The opponent attribute can be composed of name and difficulty selectors, a random ai that meets the selectors will be choosen

"exact-name" # Match "exact-name"
["exact-name-1","exact-name-2"] # Match "exact-name-1" or "exact-name-2"
{"name":"exact-name"} # Match "exact-name"
{"name":["exact-name-1","exact-name-2"]} # Match "exact-name-1" or "exact-name-2"
{"difficulty":4} # Match difficulty of exactly 4
{"difficulty":{"min":1}} # Match any difficulty greater than or equal to 1
{"difficulty":{"max":10}} # Match any difficulty greater than or equal to 1

Python3 Example Code:

send_message(connection, {"action": "practice", "arena": "robot-game-default", "opponent": "kansas"})
receive_message(connection)

On success, you'll recieve the following message

{
  "bot_server_id": "028d7605-ceb9-4090-8c8d-6d947128b010",
  "connection_id": "1bfdaf91-8254-461a-9569-7f24bc8e9a04",
  "status": "match_making",
  "user_id": "8ca8bfc0-9715-4fa2-8e9c-ae4f63df7372"
}

Some Errors You May Encounter (Not Limited to)

{"error":{"arena":["Arena \"some-arena\" does not exist"]}}
{"error":{"opponent":["No opponent matching ({\"difficulty\":{\"min\":2},\"name\":\"not-real-opponent\"})"]}}

Start Match Making

Start match making in an arena in the following manner

{"action":"start_match_making","arena":"some-arena"}

Python3 Example Code:

send_message(connection, {"action": "start_match_making", "arena": "robot-game-default"})
receive_message(connection)

On success, you'll recieve the following message

{
  "bot_server_id": "85c7defb-2d95-4951-bc4f-4f75a4f71136",
  "connection_id": "c2bcfb62-a57c-40e9-afce-834adabb4870",
  "status": "match_making",
  "watch": {
    "bot": "{some url}",
    "user": "{some url"
  }
}

Some Errors You May Encounter (Not Limited to)

{"error":{"arena":["Arena \"some-arena\" does not exist"]}}

Accepting/Rejecting a Game (The Game Request)

While in the "match_making" state (either from match making or from a practice match) you will receive a message asking to accept a game

You'll get a message the looks like the following

{
  "game_info": {
    "game_id": "{some uuid}",
    "player": 1,
    "settings": {
      "{game_specific_keys}": "{game_specific_vals}"
    }
  },
  "game_type": "robot_game",
  "request_type": "game_request"
}

Accepting

To accept the game send the following message

{
  "action": "accept_game",
  "game_id": "{the game_id from the request}"
}

Python3 Example Code:

# After getting into the match making state, accept the game as follows
game_request = receive_message(connection)
send_message(connection, {"action": "accept_game", "game_id": game_request["game_info"]["game_id"]}

The game request settings key will also contain information specific to the game you're playing, see the specifics for each supported game below

Important, check the "game_type" key to make sure that the arena game type is compatible with the type of code being run

Rejecting

If the game type key is not the one that is expected, you should "reject" the game and warn the user

{
  "action": "reject_game",
  "game_id": "{the game_id from the request}"
}

Python3 Example Code:

# If you need to reject a game, do the following
game_request = receive_message(connection)
send_message(connection, {"action": "reject_game", "game_id": game_request["game_info"]["game_id"]}

Note: if you don't respond to a game request within a certain amount of time (currently 2 seconds but subject to change), you automatically reject the game. As a courtesy if your client isn't able to play a game, promptly reject the game


Playing a Game (The Commands Request)

For each time during the game in which your bot will need to take action, you will receive a "commands request"

{
  "commands_request": {
    "game_id": "{The game_id from the game request}",
    "game_state": {
      "{game_specific_key_1}": "{game_specific_val_1}",
      "{game_specific_key_2}": "{game_specific_val_2}"
    },
    "maximum_time": 1000,
    "minimum_time": 250,
    "player": 2,
    "request_id": "{request id}"
  },
  "request_type": "commands_request"
}

Explanation of the Commands Request

Responding to a Commands Request

In order to respond to a commands request, send a message in the following form

{
  "action": "send_commands",
  "commands": "{game specific type}",
  "request_id": "{request_id_from_commands_request}"
}

Python3 Example Code:

commands_request = receive_message(connection)
commands = create_commands(commands_request) # some user provided implementation of game logic
send_message(connection, {
  "action": "send_commands",
  "request_id": commands_request["commands_request"]["request_id"],
  "commands": commands
})

When the Commands Deadline is Missed

In certain cases you may not respond to the server in time. The server will respond in the following way

{
  "error": "invalid_commands_submission",
  "request_id": "{your request id}"
}

This error could mean either

  1. You have an error in your logic around pulling the request_id out of the commands request (unlikely and fixable)
  2. You have not responded in time to the server for whatever reason (Your code took too long, the internet was flaky, ...etc)

The second case if very likely in the normal course of game play. It is HIGHLY recommend to handle this case


When the Game is Cancelled

In certain circumstances a game may be cancelled, most likely this is the result of one of the other players rejecting the game, but can also occur in some circumstances when the game code on the server throws an exception.

In the case a game is cancelled you will receive the following message

{
  "game_id": "{game id}",
  "info": "game_cancelled"
}

Following this message all things related to this game are over

The client TCP connection can be reused, and you can issue a "start_match_making" or "practice" command to play again


When the Game is Over

The game will eventually end, either through the natural end of the game, or an opponent disconnecting. The end of the game can potentially happen at any time

When the game ends you will receive a message that looks like this

{
  "game_id": "{game_id}",
  "info": "game_over",
  "result": {
    "1": 10,
    "2": 4,
    "{some player id}": "{some score}"
  },
  "watch": "{link to watch game}"
}

Once this message is received, no further actions on the current game

Player IDs will Always be integers, scores themselves may be integers or may be any JSON term. See the specific game's wire protocol to know how scoring works

The TCP can (should) can be reused by issuing a "start_match_making" or "practice" command


Other Topics

Assorted Errors

Invalid Message Sent

If your client sends an unexpected message to the server, the server will respond with the following error

{
  "error": "invalid_msg_sent"
}

This error is especially tricky because a message can be valid in some contexts, but valid in others (i.e. while submitting a valid commands request, your opponent disconnects ending the game causing the commands request to become invalid). Long term the goal is to provide more actionable information within the error, but I'm still figuring out how to do that

Invalid Json

If you send the server JSON it isn't able to parse it will respond with the following message

{
  "error": "invalid_json"
}

This is likely caused by one of the following reasons

Bot Instance Failure

Though technically it should never happen, you may receive the following error

{
  "error": "bot_instance_failure"
}

Bot instance failures happen when the server code throws an exception, and represent a logic error in the server code

Bot instance failures are unrecoverable, the server will likely close the TCP connection, and if it does not, you should close the connection as it is unusable

Bot intance failures show up in the server logs, and will be debugged by the maintainers