Controller
class Controller:
# ...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:
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:
SimplifiedGame freshGameState = controller.getFreshState();accept_alliance
async def accept_alliance(player_id: PlayerID) -> Nonepublic 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
async def attack(target: Optional[PlayerID], troops: int) -> Nonepublic 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
async def break_alliance(ally_id: PlayerID) -> Nonepublic 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
async def cancel_attack(attack_id: AttackID) -> Nonepublic 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
def get_alliances_to_renew(state: SimplifiedGame, tick_threshold: int = 600) -> list[PlayerID]// ====================
// 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
async def get_cached_state() -> SimplifiedGame@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
async def get_fresh_state() -> SimplifiedGame@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.
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:
SimplifiedGame freshGameState = controller.getFreshState();get_map_dimensions
def get_map_dimensions() -> GameMapDimensions@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
async def get_update_batch() -> list[RelevantGameUpdate]@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
async def get_update_batch_for_me(state: SimplifiedGame | None = None) -> list[RelevantGameUpdate]@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
def message_handler(func: Callable[["Controller"], asyncio.Task]) -> Nonepublic 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:
@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)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
def on_loop(func: Callable[["Controller"], asyncio.Task]) -> Nonepublic 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:
@controller.on_loop
async def game_loop(controller: Controller):
while True:
print("Your main game loop logic goes here")
await asyncio.sleep(0.1)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
async def reject_alliance(player_id: PlayerID) -> Nonepublic 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
async def renew_alliance(ally_id: PlayerID) -> Nonepublic 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
async def request_alliance(player_id: PlayerID) -> Nonepublic 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
def run(host: str | None = None, port: int | None = None) -> Nonepublic 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:
if __name__ == "__main__":
MY_BOT_USERNAME = "My Bot"
controller.set_username(MY_BOT_USERNAME)
controller.run()public static void main(String[] args) {
String MY_BOT_USERNAME = "My Bot";
controller.setUsername(MY_BOT_USERNAME);
controller.run();
}send_emoji
async def send_emoji(emoji: str, recipient: PlayerSmallID | PlayerID | Literal["AllPlayers"]) -> bool// ====================
// 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
async def send_quickchat(category: str, key: str, recipient: PlayerSmallID | PlayerID, target: Optional[PlayerID] = None) -> boolpublic 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
def set_username(username: str) -> Nonepublic 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
def small_id_to_player_id(small_id: PlayerSmallID) -> Optional[PlayerID]@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
async def username(id: PlayerSmallID | PlayerID | None) -> str// ====================
// 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
PlayerSmallIDthat 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
PlayerIDthat does not exist in the game state, this function will return"UnknownPlayerID({id})"where{id}is the provided player ID.
