from typing import Callable
import math
from .base_bot import BaseBot
from .bot_abc import BotABC
from .bot_info import BotInfo
from .events import Condition, TickEvent, HitBotEvent, ScannedBotEvent
from .internal.base_bot_internals import BaseBotInternals
from .internal.bot_internals import BotInternals
from .internal.stop_resume_listener_abs import StopResumeListenerABC
from .internal.event_interruption import EventInterruption
from .internal.thread_interrupted_exception import ThreadInterruptedException
from .constants import *
[docs]
class Bot(BaseBot, BotABC):
"""Base class for a bot in Robocode Tank Royale.
This class provides the basic structure and methods that all bots must implement.
It inherits from BaseBot and BotABC, which define the core functionality and
abstract methods that must be overridden by any specific bot implementation.
"""
def __init__(
self,
bot_info: None | BotInfo = None,
server_url: None | str = None,
server_secret: None | str = None,
):
super().__init__(bot_info, server_url, server_secret)
self._bot_internals = BotInternals(
bot=self, base_bot_internals=self._internals
)
[docs]
def run(self) -> None:
"""Runs the bot program.
Example:
def run(self) -> None:
while self.running:
self.forward(100)
self.turn_gun_left(360)
self.back(100)
self.turn_gun_right(360)
Note:
When running a loop that could potentially run forever, the best practice is to check if
the bot is still running to stop and exit the loop. This gives the game a chance to stop
the thread running the loop. If the thread is not stopped correctly, the bot may behave
strangely in new rounds.
See Also:
running
"""
pass
[docs]
def on_event(self, event: Condition) -> None:
"""Handle events that occur during the bot's execution."""
pass
@property
def turn_rate(self) -> float:
"""Get the turn rate of the bot."""
return self._bot_internals.turn_rate
@turn_rate.setter
def turn_rate(self, turn_rate: float) -> None:
"""Set the turn rate of the bot."""
self._bot_internals.turn_rate = turn_rate
@property
def gun_turn_rate(self) -> float:
return self._bot_internals.gun_turn_rate
@gun_turn_rate.setter
def gun_turn_rate(self, gun_turn_rate: float) -> None:
self._bot_internals.gun_turn_rate = gun_turn_rate
@property
def radar_turn_rate(self) -> float:
return self._bot_internals.radar_turn_rate
@radar_turn_rate.setter
def radar_turn_rate(self, radar_turn_rate: float) -> None:
self._bot_internals.radar_turn_rate = radar_turn_rate
@property
def target_speed(self) -> float:
ts = self._bot_internals.target_speed
return 0.0 if ts is None else ts
@target_speed.setter
def target_speed(self, speed: float) -> None:
# Ensure Java parity: setting target speed should clear overrideTargetSpeed and set distance_remaining to +/-inf
self._bot_internals.target_speed = speed
@property
def running(self) -> bool:
"""Check if this bot is running.
Returns:
True when the bot is running, False otherwise.
"""
return self._internals.is_running()
[docs]
def set_forward(self, distance: float) -> None:
"""Set the bot to move forward until it has traveled a specific distance from its current position.
The speed is limited by max_speed. While moving forward, ACCELERATION determines the
acceleration (+1 unit per turn), and DECELERATION determines braking (-2 units per turn).
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to target_speed, as set_forward and
set_back call target_speed each turn until distance_remaining reaches 0.
Args:
distance: Distance to move forward. If negative, the bot moves backward. If positive
or negative infinity, the bot moves infinitely in that direction.
"""
self._bot_internals.set_forward(distance)
[docs]
def forward(self, distance: float) -> None:
"""Move the bot forward until it has traveled a specific distance from its current position.
The speed is limited by max_speed. While moving forward, ACCELERATION determines the
acceleration (+1 unit per turn), and DECELERATION determines braking (-2 units per turn).
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to target_speed, set_forward, and set_back.
Args:
distance: Distance to move forward. If negative, the bot moves backward. If positive
or negative infinity, the bot moves infinitely in that direction.
"""
self._bot_internals.forward(distance)
[docs]
def set_back(self, distance: float) -> None:
"""Set the bot to move backward until it has traveled a specific distance from its current position.
The speed is limited by max_speed. While moving forward, ACCELERATION determines the
acceleration (+1 unit per turn), and DECELERATION determines braking (-2 units per turn).
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to target_speed, as set_forward and
set_back call target_speed each turn until distance_remaining reaches 0.
Args:
distance: Distance to move backward. If negative, the bot moves forward. If positive
or negative infinity, the bot moves infinitely in that direction.
"""
self.set_forward(-distance)
[docs]
def back(self, distance: float) -> None:
"""Move the bot backward until it has traveled a specific distance from its current position.
The speed is limited by max_speed. While moving forward, ACCELERATION determines the
acceleration (+1 unit per turn), and DECELERATION determines braking (-2 units per turn).
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to target_speed, set_forward, and set_back.
Args:
distance: Distance to move backward. If negative, the bot moves forward. If positive
or negative infinity, the bot moves infinitely in that direction.
"""
self.forward(-distance)
@property
def distance_remaining(self) -> float:
"""Return the remaining distance before the current movement is completed.
When the remaining distance is positive, the bot is moving forward. When negative, the
bot is moving backward. Positive/negative infinity means infinite movement in that
direction.
"""
return self._bot_internals.distance_remaining
[docs]
def set_turn_left(self, degrees: float) -> None:
"""Set the bot to turn left (following the increasing degrees of the unit circle).
The bot turns until turn_remaining is 0. The amount of degrees to turn each turn is limited
by max_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_right.
Args:
degrees: Degrees to turn left. If negative, the bot turns right. If positive or negative
infinity, the bot turns infinitely in that direction.
"""
self._bot_internals.set_turn_left(degrees)
[docs]
def turn_left(self, degrees: float) -> None:
"""Turn the bot left (following the increasing degrees of the unit circle).
The bot turns until turn_remaining is 0. The amount of degrees to turn each turn is limited
by max_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_left and set_turn_right.
Args:
degrees: Degrees to turn left. If negative, the bot turns right. If positive or negative
infinity, the bot turns infinitely in that direction.
"""
self._bot_internals.turn_left(degrees)
[docs]
def set_turn_right(self, degrees: float) -> None:
"""Set the bot to turn right (following the decreasing degrees of the unit circle).
The bot turns until turn_remaining is 0. The amount of degrees to turn each turn is limited
by max_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_left.
Args:
degrees: Degrees to turn right. If negative, the bot turns left. If positive or negative
infinity, the bot turns infinitely in that direction.
"""
self.set_turn_left(-degrees)
[docs]
def turn_right(self, degrees: float) -> None:
"""Turn the bot right (following the increasing degrees of the unit circle).
The bot turns until turn_remaining is 0. The amount of degrees to turn each turn is limited
by max_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_left and set_turn_right.
Args:
degrees: Degrees to turn right. If negative, the bot turns left. If positive or negative
infinity, the bot turns infinitely in that direction.
"""
self.turn_left(-degrees)
@property
def turn_remaining(self) -> float:
"""Return the remaining degrees before the current turn is completed.
Positive means turning left along the unit circle; negative means turning right. Positive
or negative infinity means infinite turning in that direction.
"""
return self._bot_internals.turn_remaining
[docs]
def set_turn_gun_left(self, degrees: float) -> None:
"""Set the gun to turn left (following the increasing degrees of the unit circle).
The gun turns until gun_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_gun_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_gun_right.
Args:
degrees: Degrees to turn left. If negative, the gun turns right. If positive or negative
infinity, the gun turns infinitely in that direction.
"""
self._bot_internals.set_turn_gun_left(degrees)
[docs]
def turn_gun_left(self, degrees: float) -> None:
"""Turn the gun left (following the increasing degrees of the unit circle).
The gun turns until gun_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_gun_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_gun_left and set_turn_gun_right.
Args:
degrees: Degrees to turn left. If negative, the gun turns right. If positive or negative
infinity, the gun turns infinitely in that direction.
"""
self._bot_internals.turn_gun_left(degrees)
[docs]
def set_turn_gun_right(self, degrees: float) -> None:
"""Set the gun to turn right (following the decreasing degrees of the unit circle).
The gun turns until gun_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_gun_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_gun_left.
Args:
degrees: Degrees to turn right. If negative, the gun turns left. If positive or negative
infinity, the gun turns infinitely in that direction.
"""
self.set_turn_gun_left(-degrees)
[docs]
def turn_gun_right(self, degrees: float) -> None:
"""Turn the gun right (following the decreasing degrees of the unit circle).
The gun turns until gun_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_gun_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_gun_left and set_turn_gun_right.
Args:
degrees: Degrees to turn right. If negative, the gun turns left. If positive or negative
infinity, the gun turns infinitely in that direction.
"""
self.turn_gun_left(-degrees)
@property
def gun_turn_remaining(self) -> float:
"""Return the remaining degrees before the current gun turn is completed.
Positive means turning left along the unit circle; negative means turning right. Positive
or negative infinity means infinite turning in that direction.
"""
return self._bot_internals.gun_turn_remaining
[docs]
def set_turn_radar_left(self, degrees: float) -> None:
"""Set the radar to turn left (following the increasing degrees of the unit circle).
The radar turns until radar_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_radar_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_radar_right.
Args:
degrees: Degrees to turn left. If negative, the radar turns right. If positive or negative
infinity, the radar turns infinitely in that direction.
"""
self._bot_internals.set_turn_radar_left(degrees)
[docs]
def turn_radar_left(self, degrees: float) -> None:
"""Turn the radar left (following the increasing degrees of the unit circle).
The radar turns until radar_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_radar_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_radar_left and set_turn_radar_right.
Args:
degrees: Degrees to turn left. If negative, the radar turns right. If positive or negative
infinity, the radar turns infinitely in that direction.
"""
self._bot_internals.turn_radar_left(degrees)
[docs]
def set_turn_radar_right(self, degrees: float) -> None:
"""Set the radar to turn right (following the decreasing degrees of the unit circle).
The radar turns until radar_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_radar_turn_rate.
This method is executed when go() is called, making it possible to call other setter
methods and run them in parallel in the same turn. If this method is called multiple times,
the last call before go() is executed counts.
This method cancels the effect of prior calls to set_turn_radar_left.
Args:
degrees: Degrees to turn right. If negative, the radar turns left. If positive or negative
infinity, the radar turns infinitely in that direction.
"""
self.set_turn_radar_left(-degrees)
[docs]
def turn_radar_right(self, degrees: float) -> None:
"""Turn the radar right (following the increasing degrees of the unit circle).
The radar turns until radar_turn_remaining is 0. The amount of degrees to turn each turn is
limited by max_radar_turn_rate.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_turn_radar_left and set_turn_radar_right.
Args:
degrees: Degrees to turn right. If negative, the radar turns left. If positive or negative
infinity, the radar turns infinitely in that direction.
"""
self.turn_radar_left(-degrees)
@property
def radar_turn_remaining(self) -> float:
"""Return the remaining degrees before the current radar turn is completed.
Positive means turning left along the unit circle; negative means turning right. Positive
or negative infinity means infinite turning in that direction.
"""
return self._bot_internals.radar_turn_remaining
[docs]
def fire(self, firepower: float) -> None:
"""Fire the gun in the direction the gun is pointing.
Firing spends energy from the bot. The energy loss equals firepower, and hitting another bot
rewards energy equal to 3x firepower. The gun can fire only when firepower is at least
MIN_FIREPOWER. If firepower exceeds MAX_FIREPOWER, it is truncated to MAX_FIREPOWER.
The gun heats when firing and must cool down before it can fire again. Gun heat generated by
firing is 1 + (firepower / 5). The gun cooling rate is read from gun_cooling_rate.
Damage dealt is 4x firepower, and if firepower > 1, it adds 2 * (firepower - 1).
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes. To run
multiple commands in parallel, use setter methods instead of this blocking method.
This method cancels the effect of prior calls to set_fire.
Args:
firepower: Energy to spend on firing. Must be at least MIN_FIREPOWER.
"""
self._bot_internals.fire(firepower)
[docs]
def stop(self, overwrite: bool = False) -> None:
"""Stop all movement including turning the gun and radar.
Remaining movement is saved for a call to set_resume or resume. This call executes
immediately by calling go() internally and blocks until completed, which can take one to
several turns. New commands take place after this method completes.
Args:
overwrite: True to override a previously saved movement from stop or set_stop. If False,
this method is identical to set_stop.
"""
self._bot_internals.stop(overwrite)
[docs]
def resume(self) -> None:
"""Resume the movement prior to calling set_stop or stop.
This call executes immediately by calling go() internally and blocks until completed, which
can take one to several turns. New commands take place after this method completes.
"""
self._bot_internals.resume()
[docs]
def rescan(self) -> None:
"""Scan again with the radar.
This is useful when the radar is not turning and cannot automatically scan bots, e.g. after
stop has been called. The last radar direction and sweep angle are used for rescanning.
"""
self._bot_internals.rescan()
[docs]
def wait_for(self, condition: Callable[[], bool]) -> None:
"""Block until a condition is met (condition returns True).
Args:
condition: Callable that returns True when the condition is met.
"""
self._bot_internals.wait_for(condition)