Skip to content

Team Messages ​

Introduction ​

Team messages allow bots in the same team to communicate with each other during a battle. This enables coordinated strategies where a leader bot can give orders to teammate bots, or teammates can share information like enemy positions.

This article demonstrates team messaging using the MyFirstTeam sample team, which consists of:

  • MyFirstLeader: A leader bot that scans for enemies and sends commands to teammates
  • MyFirstDroid: Droid bots (four instances) that follow the leader's orders

Note: Droids are special bots that have more energy but no radar. They rely entirely on team messages from their leader to know where enemies are located.

How Team Messages Work ​

Team messages are automatically serialized to JSON format when sent and deserialized back to typed objects when received. This happens internally—you simply send and receive objects without worrying about the serialization details.

The key concepts are:

  1. Broadcasting: Send a message to all teammates using broadcastTeamMessage()
  2. Direct messaging: Send a message to a specific teammate using sendTeamMessage()
  3. Receiving: Override the onTeamMessage() event handler to process incoming messages
  4. Type matching: Message classes are matched by name between sender and receiver

Message Types ​

In the MyFirstTeam example, two message types are used:

Message TypePurposeSent When
RobotColorsSynchronize team colors across all botsAt the start of a round
PointShare enemy position coordinates for droids to fireWhen an enemy is scanned

Sending a Batch in One Event ​

When several updates belong together, send them as one ordered batch. The receiving bot gets one TeamMessageEvent whose message is a TeamMessageBatch; iterate messages to process the entries. The batch remains one packet and one callback, so the server and Bot API do not create a separate event for every entry. Batching reduces per-message framing and callback work, but does not compress the JSON entries.

java
sendTeamMessageBatch(teammateId, List.of(new Point(250, 300), new Point(400, 180)));

@Override
public void onTeamMessage(TeamMessageEvent event) {
    if (event.getMessage() instanceof TeamMessageBatch batch) {
        for (Object message : batch.getMessages()) {
            // Process each entry in send order.
        }
    }
}
csharp
SendTeamMessageBatch(teammateId, new object[] { new Point(250, 300), new Point(400, 180) });

public override void OnTeamMessage(TeamMessageEvent evt) {
    if (evt.Message is TeamMessageBatch batch) {
        foreach (var message in batch.Messages) {
            // Process each entry in send order.
        }
    }
}
python
self.send_team_message_batch(teammate_id, [Point(250, 300), Point(400, 180)])

async def on_team_message(self, event: TeamMessageEvent) -> None:
    if isinstance(event.message, TeamMessageBatch):
        for message in event.message.messages:
            # Process each entry in send order.
            pass
typescript
this.sendTeamMessageBatch(teammateId, [point1, point2]);

override onTeamMessage(event: TeamMessageEvent) {
    if (event.message instanceof TeamMessageBatch) {
        for (const message of event.message.messages) {
            // Process each entry in send order.
        }
    }
}

All members of the team must advertise batch version 1; otherwise the server rejects the sender's intent. A batch is delivered on the next turn, and entries keep their send order.

Limits and errors ​

Each bot may send up to 64 team-message packets per turn and up to 128 logical payloads total across ordinary messages and batches. An encoded packet may contain at most 48 KiB (49,152 UTF-8 bytes), and the compact JSON teamMessages array may contain at most 256 KiB (262,144 UTF-8 bytes) per turn. The byte counts use UTF-8 after compact JSON encoding. Each client API checks a call before enqueueing it and throws if the packet or the complete turn would exceed a limit; empty batches and null entries are rejected. The server rejects an invalid intent as a whole, so no messages from that intent are delivered. Incoming WebSocket text frames are limited to 1 MiB before JSON parsing.

Batching groups payloads in one packet and callback; it does not compress their contents. These Tank Royale per-turn limits are separate from classic Robocode's 32,768-byte limit on the original Java-serialized message object.

Legacy Robocode robots can put a serializable collection inside one broadcastMessage or sendMessage call and iterate that collection from one MessageEvent. The original Java-serialized object must fit classic Robocode's 32,768-byte message limit.

Defining Message Classes ​

Each bot must define its own message classes. The classes are matched by name, so they must have the same name and compatible properties on both the sender and receiver.

java
// Point (x,y) class
class Point {
    public final double x;
    public final double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

// Robot colors class
class RobotColors {
    public Color bodyColor;
    public Color tracksColor;
    public Color turretColor;
    public Color gunColor;
    public Color radarColor;
    public Color scanColor;
    public Color bulletColor;
}
csharp
// Point (x,y) class
class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }
}

// Robot colors class
class RobotColors
{
    public Color BodyColor { get; set; }
    public Color TracksColor { get; set; }
    public Color TurretColor { get; set; }
    public Color GunColor { get; set; }
    public Color RadarColor { get; set; }
    public Color ScanColor { get; set; }
    public Color BulletColor { get; set; }
}
python
from dataclasses import dataclass
from typing import Optional
from robocode_tank_royale.bot_api import team_message_type
from robocode_tank_royale.bot_api.color import Color

# Point (x,y) class
@team_message_type
@dataclass
class Point:
    x: float
    y: float

# Robot colors class
@team_message_type
@dataclass
class RobotColors:
    body_color: Optional[Color] = None
    tracks_color: Optional[Color] = None
    turret_color: Optional[Color] = None
    gun_color: Optional[Color] = None
    radar_color: Optional[Color] = None
    scan_color: Optional[Color] = None
    bullet_color: Optional[Color] = None
typescript
// Point (x,y) interface
interface Point {
    type: "Point";
    x: number;
    y: number;
}

// Robot colors interface
interface RobotColors {
    type: "RobotColors";
    bodyColor: string;
    tracksColor: string;
    turretColor: string;
    gunColor: string;
    radarColor: string;
    scanColor: string;
    bulletColor: string;
}

type TeamMessage = Point | RobotColors;

Python Note: In Python, you must use the @team_message_type decorator to register message classes for automatic serialization and deserialization. The decorator enables typed team messages that work like Java and C#.

TypeScript Note: In TypeScript, team messages are plain JSON objects. Use JSON.stringify() when sending and JSON.parse() when receiving. A discriminated union with a type field is the recommended pattern for handling multiple message types.

The Leader Bot: Sending Messages ​

The leader bot is responsible for scanning enemies and coordinating the team. Here's how MyFirstLeader sends messages:

Sending Robot Colors at Round Start ​

At the beginning of each round, the leader sets up team colors and broadcasts them to all teammates:

java

@Override
public void run() {
    // Prepare robot colors to send to teammates
    var colors = new RobotColors();
    colors.bodyColor = Color.RED;
    colors.tracksColor = Color.CYAN;
    colors.turretColor = Color.RED;
    colors.gunColor = Color.YELLOW;
    colors.radarColor = Color.RED;
    colors.scanColor = Color.YELLOW;
    colors.bulletColor = Color.YELLOW;

    // Set our own colors
    setBodyColor(colors.bodyColor);
    setTracksColor(colors.tracksColor);
    setTurretColor(colors.turretColor);
    setGunColor(colors.gunColor);
    setRadarColor(colors.radarColor);
    setScanColor(colors.scanColor);
    setBulletColor(colors.bulletColor);

    // Send RobotColors object to every member in the team
    broadcastTeamMessage(colors);

    // Set radar to turn forever and start moving
    setTurnRadarLeft(Double.POSITIVE_INFINITY);
    while (isRunning()) {
        forward(100);
        back(100);
    }
}
csharp
public override void Run()
{
    // Prepare robot colors to send to teammates
    var colors = new RobotColors();
    colors.BodyColor = Color.Red;
    colors.TracksColor = Color.Cyan;
    colors.TurretColor = Color.Red;
    colors.GunColor = Color.Yellow;
    colors.RadarColor = Color.Red;
    colors.ScanColor = Color.Yellow;
    colors.BulletColor = Color.Yellow;

    // Set our own colors
    BodyColor = colors.BodyColor;
    TracksColor = colors.TracksColor;
    TurretColor = colors.TurretColor;
    GunColor = colors.GunColor;
    RadarColor = colors.RadarColor;
    ScanColor = colors.ScanColor;
    BulletColor = colors.BulletColor;

    // Send RobotColors object to every member in the team
    BroadcastTeamMessage(colors);

    // Set radar to turn forever and start moving
    SetTurnRadarLeft(Double.PositiveInfinity);
    while (IsRunning)
    {
        Forward(100);
        Back(100);
    }
}
python
async def run(self) -> None:
    # Prepare robot colors to send to teammates
    colors = RobotColors()
    colors.body_color = Color.RED
    colors.tracks_color = Color.CYAN
    colors.turret_color = Color.RED
    colors.gun_color = Color.YELLOW
    colors.radar_color = Color.RED
    colors.scan_color = Color.YELLOW
    colors.bullet_color = Color.YELLOW

    # Set our own colors
    self.body_color = colors.body_color
    self.tracks_color = colors.tracks_color
    self.turret_color = colors.turret_color
    self.gun_color = colors.gun_color
    self.radar_color = colors.radar_color
    self.scan_color = colors.scan_color
    self.bullet_color = colors.bullet_color

    # Send RobotColors object to every member in the team
    self.broadcast_team_message(colors)

    # Set radar to turn forever and start moving
    self.set_turn_radar_left(float("inf"))
    while self.running:
        await self.forward(100)
        await self.back(100)
typescript
override run() {
    // Prepare robot colors to send to teammates
    const colors: RobotColors = {
        type: "RobotColors",
        bodyColor: ColorUtil.toHex(Color.RED),
        tracksColor: ColorUtil.toHex(Color.CYAN),
        turretColor: ColorUtil.toHex(Color.RED),
        gunColor: ColorUtil.toHex(Color.YELLOW),
        radarColor: ColorUtil.toHex(Color.RED),
        scanColor: ColorUtil.toHex(Color.YELLOW),
        bulletColor: ColorUtil.toHex(Color.YELLOW),
    };

    // Set our own colors
    this.setBodyColor(Color.RED);
    this.setTracksColor(Color.CYAN);
    this.setTurretColor(Color.RED);
    this.setGunColor(Color.YELLOW);
    this.setRadarColor(Color.RED);
    this.setScanColor(Color.YELLOW);
    this.setBulletColor(Color.YELLOW);

    // Send RobotColors object to every member in the team
    this.broadcastTeamMessage(JSON.stringify(colors));

    // Set radar to turn forever and start moving
    this.setTurnRadarLeft(Number.POSITIVE_INFINITY);
    while (this.isRunning()) {
        this.forward(100);
        this.back(100);
    }
}

Sending Enemy Positions ​

When the leader scans an enemy (not a teammate), it broadcasts the enemy's position to all droids:

java

@Override
public void onScannedBot(ScannedBotEvent e) {
    // Ignore teammates
    if (isTeammate(e.getScannedBotId())) {
        return;
    }
    // Send enemy position to teammates
    broadcastTeamMessage(new Point(e.getX(), e.getY()));
}
csharp
public override void OnScannedBot(ScannedBotEvent evt)
{
    // Ignore teammates
    if (IsTeammate(evt.ScannedBotId))
    {
        return;
    }
    // Send enemy position to teammates
    BroadcastTeamMessage(new Point(evt.X, evt.Y));
}
python
async def on_scanned_bot(self, e: ScannedBotEvent) -> None:
    # Ignore teammates
    if self.is_teammate(e.scanned_bot_id):
        return
    # Send enemy position to teammates
    self.broadcast_team_message(Point(x=e.x, y=e.y))
typescript
override onScannedBot(e: ScannedBotEvent) {
    // Ignore teammates
    if (this.isTeammate(e.scannedBotId)) {
        return;
    }
    // Send enemy position to teammates
    const point: Point = { type: "Point", x: e.x, y: e.y };
    this.broadcastTeamMessage(JSON.stringify(point));
}

The Droid Bot: Receiving Messages ​

The droid bots wait for team messages and react accordingly. Since droids have no radar, they depend entirely on the leader for target information.

Handling Team Messages ​

The droid overrides onTeamMessage() to handle incoming messages:

java

@Override
public void onTeamMessage(TeamMessageEvent e) {
    var message = e.getMessage();

    if (message instanceof Point) {
        // Message is a point towards a target
        var target = (Point) message;

        // Turn body to target
        turnRight(bearingTo(target.x, target.y));

        // Fire hard!
        fire(3);

    } else if (message instanceof RobotColors) {
        // Message contains new robot colors
        var colors = (RobotColors) message;

        setBodyColor(colors.bodyColor);
        setTracksColor(colors.tracksColor);
        setTurretColor(colors.turretColor);
        setGunColor(colors.gunColor);
        setRadarColor(colors.radarColor);
        setScanColor(colors.scanColor);
        setBulletColor(colors.bulletColor);
    }
}
csharp
public override void OnTeamMessage(TeamMessageEvent evt)
{
    var message = evt.Message;

    if (message is Point target)
    {
        // Message is a point towards a target

        // Turn body to target
        TurnRight(BearingTo(target.X, target.Y));

        // Fire hard!
        Fire(3);
    }
    else if (message is RobotColors colors)
    {
        // Message contains new robot colors
        BodyColor = colors.BodyColor;
        TracksColor = colors.TracksColor;
        TurretColor = colors.TurretColor;
        GunColor = colors.GunColor;
        RadarColor = colors.RadarColor;
        ScanColor = colors.ScanColor;
        BulletColor = colors.BulletColor;
    }
}
python
async def on_team_message(self, e: TeamMessageEvent) -> None:
    message = e.message

    if isinstance(message, Point):
        # Message is a point towards a target

        # Turn body to target and fire hard
        await self.turn_right(self.bearing_to(message.x, message.y))
        await self.fire(3)

    elif isinstance(message, RobotColors):
        # Message contains new robot colors
        self.body_color = message.body_color
        self.tracks_color = message.tracks_color
        self.turret_color = message.turret_color
        self.gun_color = message.gun_color
        self.radar_color = message.radar_color
        self.scan_color = message.scan_color
        self.bullet_color = message.bullet_color
typescript
override onTeamMessage(e: TeamMessageEvent) {
    const message = JSON.parse(e.message) as TeamMessage;

    if (message.type === "Point") {
        // Message is a point towards a target
        this.turnRight(this.bearingTo(message.x, message.y));
        this.fire(3);

    } else if (message.type === "RobotColors") {
        // Message contains new robot colors
        this.setBodyColor(ColorUtil.fromHexColor(message.bodyColor));
        this.setTracksColor(ColorUtil.fromHexColor(message.tracksColor));
        this.setTurretColor(ColorUtil.fromHexColor(message.turretColor));
        this.setGunColor(ColorUtil.fromHexColor(message.gunColor));
        this.setRadarColor(ColorUtil.fromHexColor(message.radarColor));
        this.setScanColor(ColorUtil.fromHexColor(message.scanColor));
        this.setBulletColor(ColorUtil.fromHexColor(message.bulletColor));
    }
}

The Droid Run Loop ​

The droid's main loop is simple—it just waits for messages:

java

@Override
public void run() {
    System.out.println("MyFirstDroid ready");

    while (isRunning()) {
        go(); // Execute next turn, team messages drive the logic
    }
}
csharp
public override void Run()
{
    Console.WriteLine("MyFirstDroid ready");

    while (IsRunning)
    {
        Go(); // Execute next turn, team messages drive the logic
    }
}
python
async def run(self) -> None:
    print("MyFirstDroid ready")

    while self.running:
        await self.go()  # Execute next turn, team messages drive the logic
typescript
override run() {
    console.log("MyFirstDroid ready");

    while (this.isRunning()) {
        this.go(); // Execute next turn, team messages drive the logic
    }
}

Creating a Team ​

To create a team, you need a team configuration file (JSON) that specifies the team members:

json
{
    "name": "MyFirstTeam",
    "version": "1.0",
    "authors": [
        "Mathew Nelson",
        "Flemming N. Larsen"
    ],
    "license": "Apache-2.0",
    "description": "A sample team. MyFirstLeader scans for enemies, and orders the droids to fire.",
    "teamMembers": [
        "MyFirstLeader",
        "MyFirstDroid",
        "MyFirstDroid",
        "MyFirstDroid",
        "MyFirstDroid"
    ]
}

Note that MyFirstDroid is listed four times—this means four instances of the droid will be created in the battle.

Implementing the Droid Interface ​

To create a droid bot, you must implement the Droid interface (or DroidABC in Python). This signals to the game that your bot is a droid with the special characteristics (more energy, no radar):

java
public class MyFirstDroid extends Bot implements Droid {
    // ... bot implementation
}
csharp
public class MyFirstDroid : Bot, Droid
{
    // ... bot implementation
}
python
from robocode_tank_royale.bot_api.droid_abc import DroidABC

class MyFirstDroid(Bot, DroidABC):
    # ... bot implementation
typescript
class MyFirstDroid extends Bot implements Droid {
    // ... bot implementation
}

Message Sending Methods ​

MethodDescription
broadcastTeamMessage(message)Send a message to all teammates
sendTeamMessage(teammateId, message)Send a message to a specific teammate

Team bot IDs are numeric in Tank Royale. To show or log the corresponding team-visible name, use getBotName(botId) in Java and TypeScript, GetBotName(botId) in .NET, or get_bot_name(bot_id) in Python. The lookup returns the bot's own name or a teammate's name; it returns null/None for an opponent or an unknown ID. It is available after the game-start event. The server sends names only for the bot and its teammates.

Each Bot API can send optional BotInfo.teamMemberName metadata in bot-handshake. The server then appends the bot version and a battle-wide (n) suffix when names repeat. If teamMemberName is absent, the regular display name is used. This lets bridge APIs preserve long classic robot names without changing the bot's displayed name.

Limitations ​

  • Maximum packets per turn: 64 per bot, including batches
  • Maximum logical payloads per turn: 128, counting all messages inside batches
  • Maximum packet size: 49,152 UTF-8 bytes; the compact teamMessages array is limited to 262,144 UTF-8 bytes
  • Delivery: accepted packets arrive on the next turn; batch entries remain ordered and share one event
  • Failure behavior: a client rejects an invalid call before enqueueing it, and the server rejects an invalid intent in full
  • Batch compatibility: every recipient must use a Bot API that advertises batch version 1
  • WebSocket input: text frames above 1 MiB are closed before the server parses the JSON
  • Serialization: Messages must be serializable to JSON (no circular references)

Best Practices ​

  1. Define identical message classes: Both sender and receiver must have message classes with the same name and compatible properties

  2. Check for teammates: Always use isTeammate() before processing scanned bots to avoid targeting your own team

  3. Keep messages simple: Use simple data types that serialize well to JSON

  4. Handle all message types: Your onTeamMessage() handler should handle all expected message types gracefully

  5. Consider message timing: Messages are processed on the next turn, so factor in potential movement when broadcasting positions

Summary ​

Team messaging in Tank Royale enables powerful team coordination:

  • Leaders scan the battlefield and broadcast commands
  • Droids (or other teammates) receive and act on those commands
  • Message classes are matched by name, allowing typed communication
  • Colors, positions, and custom commands can all be shared via team messages

Try running MyFirstTeam against other bots to see team coordination in action!

Advanced Team Strategies ​

Ready to take your team coordination to the next level? Explore advanced strategies in The Book of Robocode:

For a hands-on guide to implementing team messages in each language, see the Team Messages Guide in the Bot API repository.

Also see Team Strategies for foundational team battle concepts and testing strategies.

Released under the Apache License 2.0.