Skip to content

Controller

python
class Controller:
    # ...
java
public class Controller {
    // ...
}

Main interface provided to the bot for interacting with the game.

Exposes various methods that allow the bot to retrieve the current game state and perform actions in the game, such as attacking or sending quick chats.

TIP

In Python, many of the methods in Controller are asynchronous. You do not need to understand what asynchronous programming is to program your bot, however make sure to call these methods with await if you are waiting for their returned value. For example:

python
fresh_game_state = await controller.get_fresh_state()

In Java, however, these methods are synchronous and do not require await. You can call them directly:

java
SimplifiedGame freshGameState = controller.getFreshState();

accept_alliance

python
async def accept_alliance(player_id: PlayerID) -> None
java
public void acceptAlliance(@NotNull PlayerID playerId)

Accepts an alliance request from the player that corresponds to the given PlayerID.

Accepting an alliance with a player that has not sent your bot an alliance request or that does not exist will have no effect.

Accepting an alliance from the None/null player will no effect.

attack

python
async def attack(target: Optional[PlayerID], troops: int) -> None
java
public void attack(@Nullable PlayerID target, int troops)

Attacks the player corresponding to the provided PlayerID with the specified number of troops.

You can attack the None/null player to claim empty territory for yourself if there are empty territories adjacent to your borders.

Attacking players or territory that are not adjacent to your borders will have no effect. You can find out which players are adjacent to you by looking at the neighbors attribute of the SimplifiedPlayer object that corresponds to your bot in the game state.

Attacking with 0 or fewer troops will have no effect.

You can figure out how many troops your bot has available to attack by looking at the troops attribute of your SimplifiedPlayer.

break_alliance

python
async def break_alliance(ally_id: PlayerID) -> None
java
public void breakAlliance(@NotNull PlayerID allyId)

Breaks the alliance between your bot and the player that corresponds to the given PlayerID.

Breaking an alliance with a player that is not currently your ally or that does not exist will have no effect.

cancel_attack

python
async def cancel_attack(attack_id: AttackID) -> None
java
public void cancelAttack(@NotNull AttackID attackId)

Cancels an ongoing attack that was initiated by your bot, identified by the provided AttackID.

Canceling an attack that has already ended, that does not exist, or that was launched by someone else will have no effect.

Corresponds to manually clicking the red cross button on the side of an ongoing attack in the chat.

get_alliances_to_renew

python
def get_alliances_to_renew(state: SimplifiedGame, tick_threshold: int = 600) -> list[PlayerID]
java
// ====================
//  Multiple overloads
// ====================

@NotNull
public List<@NotNull PlayerID> getAlliancesToRenew(
    @NotNull SimplifiedGame state,
    int tickThreshold
)

@NotNull
public List<@NotNull PlayerID> getAlliancesToRenew(
    @NotNull SimplifiedGame state,
    @NotNull Tick tickThreshold
)

Helper function that finds the players that are currently allied with you and whose alliance is set to expire within the specified tick threshold.

The default tick threshold is 600 ticks, which translates to 60 seconds or 1 minute. This means that by default, this function will return the list of allies whose alliance is set to expire within the next minute. See the documentation of Tick for more information about what a tick is and how it relates to seconds.

Does not renew any alliances by itself, but can be used in conjunction with the renew_alliance method to renew alliances before they expire.

get_cached_state

python
async def get_cached_state() -> SimplifiedGame
java
@Nullable
public SimplifiedGame getCachedState()

Retrieves the last known game state without asking the game itself for the most up-to-date information.

If no state has been retrieved from the game yet, this method will behave the same as get_fresh_state and send a request to the game for the current state.

TIP

Instead of using get_cached_state, it is recommended to use get_fresh_state at the start of each loop and store that state in a variable to reference throughout the loop.

get_fresh_state

python
async def get_fresh_state() -> SimplifiedGame
java
@Nullable
public SimplifiedGame getFreshState()

Sends a request to the game asking for the most up-to-date game state and waits for the response before returning it.

This method is recommended to be used at the start of each loop to get the most recent game state, which can then be stored in a variable and referenced throughout the loop instead of calling get_fresh_state multiple times.

TIP

In Python, do not forget to use await when calling this method.

python
fresh_game_state = await controller.get_fresh_state()

In Java, however, this method is synchronous and does not require await. You can call it directly:

java
SimplifiedGame freshGameState = controller.getFreshState();

get_map_dimensions

python
def get_map_dimensions() -> GameMapDimensions
java
@Nullable
public GameMapDimensions getMapDimensions()

Provides the dimensions of the game map as a GameMapDimensions object.

Has limited use cases when programming a bot.

get_update_batch

python
async def get_update_batch() -> list[RelevantGameUpdate]
java
@NotNull
public List<@NotNull Object> getUpdateBatch()

Retrieves a list of pending game updates that have not yet been processed by the bot.

These updates may include information about other players that does not directly impact you specifically. For example, you may receives updates that Player A has asked for an alliance with Player B, but if neither of those players are you, then that update may not be relevant to you. Consider using the get_update_batch_for_me method instead to filter out irrelevant updates.

If no new batch is available, returns an empty list immediately, does not wait for new updates.

get_update_batch_for_me

python
async def get_update_batch_for_me(state: SimplifiedGame | None = None) -> list[RelevantGameUpdate]
java
@NotNull
public List<@NotNull Object> getUpdateBatchForMe(@Nullable SimplifiedGame state)

Retrieves a list of pending game updates that have not yet been processed by the bot, but only returns the updates that are relevant to your bot.

Relevant updates are determined using the update_is_for_me utility function.

If no new batch is available, returns an empty list immediately, does not wait for new updates.

TIP

It is recommended to use this function inside your message_handler implementation.

INFO

You may pass a None/null value for the state parameter. In that case, the function will use the last cached game state. However, if you never queried the game state before, the function will return an empty list and log a warning.

message_handler

python
def message_handler(func: Callable[["Controller"], asyncio.Task]) -> None
java
public void messageHandler(@NotNull Consumer<@NotNull Controller> func)

This function allows you to define a message handler that will be in charge of processing incoming game updates and performing actions in response to those updates.

WARNING

You do not need to worry about calling this function yourself, as it is already called by the initial scaffold for your chosen language.

You only need to implement the logic inside the function.

The message_handler function is used as:

python
@controller.message_handler
async def handle_messages(controller: Controller):
    while True:
        updates = await controller.get_update_batch_for_me()
        for update in updates:
            print("Your logic to process updates goes here")
        await asyncio.sleep(0.1)
java
public class Main {

    // ...

    public static void main(String[] args) {
        // ...
        controller.messageHandler(Main::handleMessages);
        // ...
    }

    private static void handleMessages(@NotNull Controller controller) {
        // Note: for brevity this is not exactly what is in Main.java
        while (true) {
            Thread.sleep(100);

            List<Object> updates = controller.getUpdateBatchForMe(null);
            // ...
        }
    }
}

Note that your message handler must contain a while True loop that keeps it running indefinitely. You are responsible for handling the interval at which the handler should run. If your function returns without any error, it will not be called again, and your bot will stop processing messages.

If you prefer implementing a single iteration, you are free to define a separate function, and call it in a loop inside the message handler.

on_loop

python
def on_loop(func: Callable[["Controller"], asyncio.Task]) -> None
java
public void onLoop(@NotNull Consumer<@NotNull Controller> func)

This function allows you to define your main game loop for the bot. This is where most decisions will me made and most actions will be performed.

WARNING

You do not need to worry about calling this function yourself, as it is already called by the initial scaffold for your chosen language.

You only need to implement the logic inside the function.

The on_loop function is used as:

python
@controller.on_loop
async def game_loop(controller: Controller):
    while True:
        print("Your main game loop logic goes here")
        await asyncio.sleep(0.1)
java
public class Main {

    // ...

    public static void main(String[] args) {
        // ...
        controller.onLoop(Main::gameLoop);
        // ...
    }

    private static void gameLoop(@NotNull Controller controller) {
        // Note: for brevity this is not exactly what is in Main.java
        while (true) {
            Thread.sleep(GAME_LOOP_INTERVAL_MS);

            SimplifiedGame state = controller.getFreshState();
            // ...
        }
    }
}

Note that your game loop must contain a while True loop that keeps it running indefinitely. You are responsible for handling the interval at which the loop should run. If your function returns without any error, it will not be called again, and your bot will stop playing.

If you prefer implementing a single iteration, you are free to define a separate function, and call it in a loop inside the game loop.

reject_alliance

python
async def reject_alliance(player_id: PlayerID) -> None
java
public void rejectAlliance(@NotNull PlayerID playerId)

Rejects an alliance request from the player that corresponds to the given PlayerID.

Rejecting an alliance from a player that has not sent your bot an alliance request or that does not exist has no effect.

Corresponds to manually clicking the "Reject" button in the alliance request chat message in the game.

renew_alliance

python
async def renew_alliance(ally_id: PlayerID) -> None
java
public void renewAlliance(@NotNull PlayerID allyId)

Marks your bot's wish to renew the alliance between your bot and the player that corresponds to the given PlayerID for the next time the alliance is up for renewal.

Note that renewing the alliance on your side does not mean the ally will ask to renew the alliance as well. The alliance is only renewed if both sides ask for renewal before the alliance expires.

Corresponds to manually clicking the "Renew" button in the alliance expiration chat message in the game.

request_alliance

python
async def request_alliance(player_id: PlayerID) -> None
java
public void requestAlliance(@NotNull PlayerID playerId)

Sends an alliance request to the player that corresponds to the given PlayerID.

Requesting an alliance with a player that is already your ally or that does not exist will have no effect.

Corresponds to manually right-clicking on a player's territory in the game and clicking the "Request Alliance" button.

run

python
def run(host: str | None = None, port: int | None = None) -> None
java
public void run()

Blocking function that starts the bot and waits for the game to be ready.

Once the game is ready, the internals of this function will automatically start calling the functions you defined for handling messages and for your main game loop (see message_handler and on_loop), thus you do not need to call those functions yourself.

WARNING

You do not need to worry about calling this function yourself, as it is already called by the initial scaffold for your chosen language.

The run function is used as:

python
if __name__ == "__main__":
    MY_BOT_USERNAME = "My Bot"
    controller.set_username(MY_BOT_USERNAME)
    controller.run()
java
public static void main(String[] args) {
    String MY_BOT_USERNAME = "My Bot";
    controller.setUsername(MY_BOT_USERNAME);
    controller.run();
}

send_emoji

python
async def send_emoji(emoji: str, recipient: PlayerSmallID | PlayerID | Literal["AllPlayers"]) -> bool
java
// ====================
//  Multiple overloads
// ====================

public boolean sendEmoji(@NotNull String emoji, @NotNull String recipient)

public boolean sendEmoji(@NotNull String emoji, @NotNull PlayerID recipient)

public boolean sendEmoji(@NotNull String emoji, @NotNull PlayerSmallID recipient)

public boolean sendEmoji(@NotNull String emoji, int recipientSmallId)

Sends an emoji to the designated player, or to all players if recipient is set to "AllPlayers".

This can, for example, be used to communicate with other bots or to taunt opponents by sending a laughing emoji after a successful attack.

You can only send emojis that are included in the list of valid emojis here.

Trying to send another emoji or a non-emoji character will have no effect.

send_quickchat

python
async def send_quickchat(category: str, key: str, recipient: PlayerSmallID | PlayerID, target: Optional[PlayerID] = None) -> bool
java
public boolean sendQuickChat(
    @NotNull String category,
    @NotNull String key,
    @NotNull Object recipient,
    @Nullable PlayerID target
)

Sends a pre-defined quick chat message to the designated recipient.

An example of quick message could be attack.attack{target}, which means "attack player {target}".

Trying to send a quick chat message with an invalid category or key, or to an invalid recipient, will have no effect.

Some messages require a target player ID to be specified (such as the attack.attack{target} message mentioned above), while others do not. Failing to provide a required target will result in an invalid quick chat message and thus will have no effect. Providing a target for a message that does not require one will be ignored and the message will still be sent successfully as long as the category and key are valid.

Because a lot of the game features are limited during the hackaton (buildings, trading, missiles), some of the quick chat messages that reference those features may not be relevant, but you can still use them to communicate with other bots if you agree on a meaning for them.

Find a reference of valid categories and keys in the DisplayChatMessageUpdate documentation.

Returns true if the quickchat message was sent successfully, and false if the message was invalid and thus not sent.

set_username

python
def set_username(username: str) -> None
java
public void setUsername(@NotNull String username)

Sets the username of your bot to the provided string.

Must be called before the run method is called, otherwise the new username will not take effect.

INFO

It goes without saying, but please choose a username that is appropriate and abides by the rules of the event, as well as to the rules of common sense.

We reserve the right to disqualify bots with inappropriate usernames.

small_id_to_player_id

python
def small_id_to_player_id(small_id: PlayerSmallID) -> Optional[PlayerID]
java
@Nullable
public PlayerID smallIdToPlayerId(@NotNull PlayerSmallID smallId)

Converts a PlayerSmallID to its corresponding PlayerID using an internal pre-cached mapping.

Returns None/null if the provided small_id does not exist in the mapping.

username

python
async def username(id: PlayerSmallID | PlayerID | None) -> str
java
// ====================
//  Multiple overloads
// ====================

@NotNull
public String username(@Nullable PlayerID playerID)

@NotNull
public String username(@Nullable String playerID)

@NotNull
public String username(@Nullable PlayerSmallID smallID)

@NotNull
public String username(int smallID)

Retrieves the username corresponding to a given PlayerSmallID or PlayerID.

Can be used for nicer display purposes when printing information about other players, instead of showing their IDs.

  • If the provided ID value is None/null, this function will return "N/A".
  • If the provided ID is a PlayerSmallID that does not exist in the internal mapping, this function will return "UnknownSmallID({id})" where {id} is the provided small ID.
  • If the provided ID is a PlayerID that does not exist in the game state, this function will return "UnknownPlayerID({id})" where {id} is the provided player ID.