Skip to content

Tutorial on writing a Bot API for Tank Royale

Davide edited this page Apr 26, 2025 · 10 revisions

Introduction

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.

Common Considerations for Writing a Bot API

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.

Suggestions on where to start

  1. 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.

  2. Find how to allow someone to import your API, initialize and start a bot.

  3. 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.

test 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.

Automated test

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.

Utility Diagrams

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.

The GO() function and event handling

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
Loading

State diagram for the full engine

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
Loading

Diagrams created with Mermaid