-
Notifications
You must be signed in to change notification settings - Fork 55
Tutorial on writing a Bot API for Tank Royale
Hello, my name is Davide and I'm working my way through the creation of a Bot API library written in NIM. I'm writing this post to share my experience and give some insights. This is my current repository: Tank Royale Bot API in NIM
This document is intended to help you create a Bot API for Tank Royale, a game where you can create your own bots and make them fight against each other. My guidelines are based on my experience and I can't guarantee that they are the best way to do it, but I hope they can help anybody who wants to start this journey.
When writing a Bot API, you will need to address several common considerations:
- WebSocket: These are unavoidable as the RTR (Robocode Tank Royale) engine relies on them.
- Multi-threading/async: The library must use multi-threading or asynchronous programming to manage incoming messages from the server and execute custom bot code triggered by events.
- Overriding/overloading: Bot creators will need to override or overload methods to make the bot programmable.
- Environmental variables: To ensure bots created with your API can be booted by the original Booter, your bot must read from environment variables.
- Reading from disk: Bots must ingest at least one file from the disk, specifically the JSON file containing the bot's "profile."
- JSON: JSON is the format for all messages between the bot and the game server, so you need to program the bot to handle these.
- Efficient handling: The bot API engine must be highly efficient. In a standard game, the bot has 30ms or less each turn to send its intent. It needs to handle everything quickly to provide more "CPU space" for user calculations.
-
I recommend reviewing the basic schemas available in the Game schemas README. This will help you understand how the game and bots communicate to make the game function.
-
Find how to allow someone to import your API, initialize and start a bot.
-
Start with creating the connection to the server with WebSockets; this is the first thing you need to do to start receiving and sending messages to the server. As soon as you connect to a server, you will receive its handshake json message, if you do you have successfully connected to the server, answer it with your botHandshake and the programming fun will follow.
With these tasks completed, you should now have a solid understanding of the basics and a clear direction for further developing your API.
As you progress, you will eventually want to test your API. But how should you do it?
I found it useful to test my library by re-implementing the Sample Bots. Each bot will challenge you in different ways.
There are no automated tests available for your API/Bots. Depending on your chosen language, you will need to create all tests yourself. One approach is to create a test that starts a server, initializes a Controller to handle messages from the server, and launches two or more bots. The Controller can then command the server to start the game. The Controller will receive updates, or the bot can test its own values.
All these steps are possible without using the GUI
How you test your API/Bots is entirely up to you.
Note
Here I will add/remove/modify the diagrams I use keep track of how the bot "engine" works. I want to keep these diagrams the most generic as possible, in order to keep them separated from any particular implementation.
This state diagram should accurately represent the functioning of the original API, starting from the point at which the go() function is called. This is the most critical and intricate aspect of the API, and merits a diagram of its own.
stateDiagram-v2
[*] --> GO
GO --> [*]: bot is not running
GO --> CHECK_INTENT_TURN: bot is running
CHECK_INTENT_TURN --> DISPATCH_EVENTS: intent not sent this turn
CHECK_INTENT_TURN --> WAIT_NEXT_TURN: intent already sent
DISPATCH_EVENTS --> REMOVE_OLD_EVENTS
REMOVE_OLD_EVENTS --> SORT_EVENTS
SORT_EVENTS --> WHILE_RUNNING
WHILE_RUNNING --> PICK_EVENT: is running
PICK_EVENT --> SAVE_CURRENT_EVENT: else
SAVE_CURRENT_EVENT --> REMOVE_FROM_QUEUE
REMOVE_FROM_QUEUE --> DISPATCH_EVENT
DISPATCH_EVENT --> SET_CURRENT_PRIORITY: no GO called
SET_CURRENT_PRIORITY --> WHILE_RUNNING
PICK_EVENT --> SEND_INTENT: same event or <br>no event picked
SEND_INTENT --> WAIT_NEXT_TURN
WHILE_RUNNING --> SEND_INTENT: not running
WAIT_NEXT_TURN --> [*]: NEXT_TURN
DISPATCH_EVENT --> GO: GO can be called
Caution
This diagram is a tentative to put down the full API engine, I don't like it to the very end and I suggest you to use a critical eye when looking at it. I will, one day, replace it with a better version, hopefully...
stateDiagram-v2
state check_connection <<choice>>
[*] --> CONNECT
CONNECT --> check_connection: connection success?
check_connection --> [*]: no
state running_fork <<fork>>
state running_join <<join>>
check_connection --> running_fork: yes
running_fork --> MAIN
state MAIN {
state IO_fork <<fork>>
state IO_join <<join>>
CALL_FOR<br><i>DISCONNECTION</i> --> [*]
[*] --> IO_fork
IO_fork --> LISTEN_MESSAGES
IO_fork --> CHECK<br><i>OUT_QUEUE</i>
CHECK<br><i>OUT_QUEUE</i> --> SEND_MESSAGE: message
SEND_MESSAGE --> CHECK<br><i>OUT_QUEUE</i>
CHECK<br><i>OUT_QUEUE</i> --> IO_join: disconnected
LISTEN_MESSAGES --> IO_join: disconnected
LISTEN_MESSAGES --> HANDLE_MESSAGE<br><br>RUNNIG_and<br>nextTurn<br>handled_here: message
HANDLE_MESSAGE<br><br>RUNNIG_and<br>nextTurn<br>handled_here --> CHECK_TYPE
state check_type <<choice>>
CHECK_TYPE --> check_type: message
check_type --> ADD_MESSAGE_TO<br><i>EVENT_QUEUE</i>: type of<br><i>bot event</i>
check_type --> LISTEN_MESSAGES: else
ADD_MESSAGE_TO<br><i>EVENT_QUEUE</i> --> LISTEN_MESSAGES
IO_join --> CALL_FOR<br><i>DISCONNECTION</i>
}
running_fork --> BOT
state BOT {
[*] --> WAIT_FOR_START
WAIT_FOR_START --> RUN<br><i>custom_code</i> : @START
RUN<br><i>custom_code</i> --> REMOVE_OLD_EVENTS<br><i>EVENT_QUEUE</i>: GO<br>from RUN()
REMOVE_OLD_EVENTS<br><i>EVENT_QUEUE</i> --> SORT_BY_PRIORITY<br><i>EVENT_QUEUE</i>
SORT_BY_PRIORITY<br><i>EVENT_QUEUE</i> --> HANDLE_QUEUE<br><i>EVENT_QUEUE</i>
HANDLE_QUEUE<br><i>EVENT_QUEUE</i> --> STILL_VALID?:pop()
state check_validity <<choice>>
STILL_VALID? --> check_validity
check_validity --> HANDLE_QUEUE<br><i>EVENT_QUEUE</i>: no<br>drop event
check_validity --> RUN_BOT_EVENT_CODE<br><i>custom_code</i>: yes
RUN_BOT_EVENT_CODE<br><i>custom_code</i> --> HANDLE_QUEUE<br><i>EVENT_QUEUE</i>: no GO
RUN_BOT_EVENT_CODE<br><i>custom_code</i> --> REMOVE_OLD_EVENTS<br><i>EVENT_QUEUE</i>: GO<br>from custom event
HANDLE_QUEUE<br><i>EVENT_QUEUE</i> --> SEND_INTENT: @queue empty
SEND_INTENT --> WAIT_NEXT_TURN
WAIT_NEXT_TURN --> RUN<br><i>custom_code</i>: @nextTurn<br>if from<br>RUN
WAIT_NEXT_TURN --> AUTOMATIC_GO: @nextTurn<br>if from<br>AUTOMATIC_GO
RUN<br><i>custom_code</i> --> AUTOMATIC_GO : @exit from RUN<br><i>custom_code</i>
AUTOMATIC_GO --> REMOVE_OLD_EVENTS<br><i>EVENT_QUEUE</i>: GO<br>from AUTOMATIC_GO
AUTOMATIC_GO --> WAIT_FOR_START: @RUNNING = false
}
running_join --> [*]
MAIN --> running_join
BOT --> running_join : exit @DISCONNECTION
Diagrams created with Mermaid