API Changes
Origins
Classic Robocode's event-driven API was designed by Mathew A. Nelson (Mat Nelson). Flemming Nørnberg Larsen (fnl) kept the same event model for Robocode Tank Royale while renaming classes and methods to fit a multi-language, multi-language-client design.
A bot ported from classic Robocode almost always fails to compile in Tank Royale, and the errors point at names, not logic. Robot is gone. ScannedRobotEvent is gone. The turn-by-turn loop, the events, and the physics survive the move intact, as the Physics Differences page shows. What changes is what to call things.
One base class becomes one interface
Classic Robocode offers three base classes, each unlocking more control:
Robot: basic movement, firing, and simple events.AdvancedRobot: adds non-blocking setter calls (setAhead,setTurnGunRight) and custom events.TeamRobot: adds team messaging on top ofAdvancedRobot.
Tank Royale replaces all three with a single Bot interface that any bot can implement. There is no beginner tier to graduate out of: setter calls, custom events, and team messaging are available from the first line of code. A bot only needs TeamRobot in classic Robocode to send messages, but in Tank Royale, any bot on a team can send them.
Renamed methods
Bearing, movement, and gun methods keep their shape but drop "Robot" from the vocabulary, and a few gain symmetry they never had in classic Robocode:
| Classic Robocode | Tank Royale | Note |
|---|---|---|
ahead(distance) | forward(distance) | renamed |
back(distance) | back(distance) | unchanged |
turnGunRight(degrees) | turnGunRight(degrees) | unchanged |
setAhead(distance) | setForward(distance) | non-blocking form, also renamed |
setTurnGunRight(deg) | setTurnGunRight(deg) | unchanged |
fire(power) | fire(power) | unchanged |
execute() | go() | commits the turn's setter calls |
isTeammate(name) | isTeammate(id) | Tank Royale identifies teammates by numeric ID |
The rename that trips up the most bots is execute() becoming go(). Both do the same job: apply every setter call queued this turn and end it. See Blocking vs Non-Blocking Movement for what happens if a bot forgets to call it.
Renamed events
Event names drop "Robot" for "Bot", matching the class rename:
| Classic Robocode | Tank Royale |
|---|---|
ScannedRobotEvent | ScannedBotEvent |
onScannedRobot(event) | onScannedBot(event) |
HitRobotEvent | HitBotEvent |
onHitRobot(event) | onHitBot(event) |
RobotDeathEvent | BotDeathEvent |
onRobotDeath(event) | onBotDeath(event) |
Two more events were renamed for clarity rather than to match the Robot → Bot swap. Classic Robocode's BulletHitEvent fires when one of your bullets hits an enemy, easy to confuse with HitByBulletEvent, which fires when an enemy's bullet hits you. Tank Royale renames it to BulletHitBotEvent, so the "who got hit" is in the name instead of implied by which event class it is. Classic Robocode's BulletMissedEvent fires when a bullet reaches the edge of the battlefield without hitting anything, a name that reads more like the shooter's bad aim than what actually happened. Tank Royale renames it to BulletHitWallEvent, describing the event, not a verdict on it.
| Classic Robocode | Tank Royale |
|---|---|
BulletHitEvent | BulletHitBotEvent |
onBulletHit(event) | onBulletHitBot(event) |
BulletMissedEvent | BulletHitWallEvent |
onBulletMissed(event) | onBulletHitWall(event) |
HitByBulletEvent, HitWallEvent, BulletHitBulletEvent, and onWin keep the same shape and meaning on both platforms, only the renamed classes above changed.
Independent turning: "robot turn" becomes "body turn"
AdvancedRobot lets the gun and radar turn independently of the body, so a locked radar keeps tracking a target while the body dodges. Classic Robocode names this after the "robot" whose turn the gun or radar is compensating for. Tank Royale renames it after the "body", matching its own vocabulary for the chassis:
| Classic Robocode | Tank Royale |
|---|---|
setAdjustGunForRobotTurn(adjust) | setAdjustGunForBodyTurn(adjust) |
isAdjustGunForRobotTurn() | isAdjustGunForBodyTurn() |
setAdjustRadarForRobotTurn(adjust) | setAdjustRadarForBodyTurn(adjust) |
isAdjustRadarForRobotTurn() | isAdjustRadarForBodyTurn() |
setAdjustRadarForGunTurn(adjust) | setAdjustRadarForGunTurn(adjust) |
isAdjustRadarForGunTurn() | isAdjustRadarForGunTurn() |
The last pair, radar independent of the gun, keeps its name on both platforms since "gun" was never ambiguous. Set adjust to true to decouple that part from the turn it would otherwise inherit.
Graphical debugging moves out of an event
Classic Robocode paints debug graphics inside an overridden onPaint(Graphics2D g) method, calling getGraphics() from within it to get the same canvas. Tank Royale drops the dedicated event: check isDebuggingEnabled() and call getGraphics() directly, which returns an IGraphics canvas with its own drawing methods (setStrokeColor, fillRectangle, and similar) instead of Graphics2D. See Debugging Tips for how to put this to use.
Team messaging renames
Classic TeamRobot sends a Serializable payload with broadcastMessage(message) for the whole team or sendMessage(name, message) for one teammate. Tank Royale splits the same two operations into broadcastTeamMessage(message) and sendTeamMessage(teammateId, message), and the payload travels as JSON instead of serialized Java. See Team Communication & Coordination for the message-size limits on each platform.
A utility class becomes built-in methods
Classic Robocode's angle math lives outside any bot, in the static robocode.util.Utils class. A gun that needs a bearing to a point has to import it and call it by hand:
// Classic Robocode
import static robocode.util.Utils.normalRelativeAngleDegrees;
double bearing = normalRelativeAngleDegrees(absoluteAngleTo(x, y) - getHeading());Tank Royale drops the standalone class and puts the same math directly on Bot, aware of the bot's own position and each of its three headings:
Classic Robocode (robocode.util.Utils) | Tank Royale (Bot methods) |
|---|---|
normalRelativeAngleDegrees(angle) | normalizeRelativeAngle(angle) |
normalAbsoluteAngleDegrees(angle) | normalizeAbsoluteAngle(angle) |
| not provided | directionTo(x, y), bearingTo(x, y) |
| not provided | gunBearingTo(x, y), radarBearingTo(x, y) |
| not provided | distanceTo(x, y) |
bearingTo(x, y) folds a call to directionTo and a normalization into one step, and gunBearingTo / radarBearingTo give the gun and radar the same shortcut aimed at their own heading. None of these are new physics, they are the formulas from Coordinate Systems & Angles written once so every bot stops reimplementing them by hand.
What stayed the same
The event-driven shape of the API did not change. A bot still overrides run() for its main loop and overrides one handler per event type it cares about. The minimal bot below fires at anything it scans, using only names verified against the official sample bots:
import robocode.Robot;
import robocode.ScannedRobotEvent;
public class MyBot extends Robot {
public void run() {
while (true) {
ahead(100);
turnGunRight(360);
}
}
public void onScannedRobot(ScannedRobotEvent event) {
fire(1);
}
}from robocode_tank_royale.bot_api import Bot
from robocode_tank_royale.bot_api.events import ScannedBotEvent
class MyBot(Bot):
def run(self) -> None:
while self.running:
self.forward(100)
self.turn_gun_right(360)
def on_scanned_bot(self, event: ScannedBotEvent) -> None:
self.fire(1)import dev.robocode.tankroyale.botapi.Bot;
import dev.robocode.tankroyale.botapi.events.ScannedBotEvent;
public class MyBot extends Bot {
public void run() {
while (isRunning()) {
forward(100);
turnGunRight(360);
}
}
public void onScannedBot(ScannedBotEvent event) {
fire(1);
}
}using Robocode.TankRoyale.BotApi;
using Robocode.TankRoyale.BotApi.Events;
public class MyBot : Bot
{
public override void Run()
{
while (IsRunning)
{
Forward(100);
TurnGunRight(360);
}
}
public override void OnScannedBot(ScannedBotEvent evt)
{
Fire(1);
}
}import { Bot, ScannedBotEvent } from "@robocode.dev/tank-royale-bot-api";
class MyBot extends Bot {
override run() {
while (this.isRunning()) {
this.forward(100);
this.turnGunRight(360);
}
}
override onScannedBot(event: ScannedBotEvent) {
this.fire(1);
}
}Notice the loop condition itself changed too. Classic Robocode's run() can loop while (true) forever, because the engine stops a bot by killing its thread outright. Modern Java no longer allows forcibly killing a thread, and doing so was always risky, since it can abandon a bot mid-update with inconsistent state. Tank Royale's engine instead sets a flag when the round or battle ends, and every language's client exposes that flag as isRunning() (self.running in Python), so the loop exits on its own instead of being cut off from outside.
Everything past the class and method names, the turn loop, the event dispatch, the physics behind forward() and fire(), is the material the rest of this book already covers.
Porting checklist
- Replace
Robot/AdvancedRobot/TeamRobotwithBot. - Rename every
XxxRobotEventandonXxxRobothandler to itsXxxBotEvent/onXxxBotform. - Rename
BulletHitEventtoBulletHitBotEventandBulletMissedEventtoBulletHitWallEvent. - Rename every
AdjustXxxForRobotTurncall toAdjustXxxForBodyTurn. - Replace
execute()withgo(). - Convert headings and turn signs, see Physics Differences.
- Re-check hitbox math that assumed a square bot, see the same page.
- Re-check team message sizes against Tank Royale's per-turn packet limits.
- Drop hand-rolled
robocode.util.Utilscalls in favor ofBot's built-in bearing and distance methods. - Move
onPaintgraphics code to agetGraphics()call guarded byisDebuggingEnabled().
None of this changes strategy. A wave surfing gun or a GuessFactor targeting scheme ports over unchanged once the names and angles line up. The next page, Migration Guide, walks through porting one real bot end to end.
Further Reading
- Robocode/RobocodeAPI - RoboWiki (classic Robocode)
- Bot API - Tank Royale documentation
- Tank Royale - Tank Royale documentation
- Physics Differences - angle, turn-sign, and hitbox differences
- Blocking vs Non-Blocking Movement (Setters)