dev.robocode.tankroyale.botapi.internal.BotInternals Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of robocode-tankroyale-bot-api Show documentation
Show all versions of robocode-tankroyale-bot-api Show documentation
Robocode Tank Royale Bot API for Java
The newest version!
package dev.robocode.tankroyale.botapi.internal;
import dev.robocode.tankroyale.botapi.Bot;
import dev.robocode.tankroyale.botapi.BotException;
import dev.robocode.tankroyale.botapi.events.*;
import java.util.concurrent.Callable;
import static dev.robocode.tankroyale.botapi.Constants.MAX_SPEED;
import static java.lang.Math.abs;
public final class BotInternals implements IStopResumeListener {
private final Bot bot;
private final BaseBotInternals baseBotInternals;
private Thread thread;
private boolean overrideTurnRate;
private boolean overrideGunTurnRate;
private boolean overrideRadarTurnRate;
private boolean overrideTargetSpeed;
private double previousDirection;
private double previousGunDirection;
private double previousRadarDirection;
private double distanceRemaining;
private double turnRemaining;
private double gunTurnRemaining;
private double radarTurnRemaining;
private boolean isOverDriving;
private double savedPreviousDirection;
private double savedPreviousGunDirection;
private double savedPreviousRadarDirection;
private double savedDistanceRemaining;
private double savedTurnRemaining;
private double savedGunTurnRemaining;
private double savedRadarTurnRemaining;
public BotInternals(Bot bot, BaseBotInternals baseBotInternals) {
this.bot = bot;
this.baseBotInternals = baseBotInternals;
baseBotInternals.setStopResumeHandler(this);
BotEventHandlers botEventHandlers = baseBotInternals.getBotEventHandlers();
botEventHandlers.onGameAborted.subscribe(e -> onGameAborted(), 100);
botEventHandlers.onNextTurn.subscribe(this::onNextTurn, 90);
botEventHandlers.onRoundEnded.subscribe(e -> onRoundEnded(), 90);
botEventHandlers.onGameEnded.subscribe(this::onGameEnded, 90);
botEventHandlers.onDisconnected.subscribe(this::onDisconnected, 90);
botEventHandlers.onHitWall.subscribe(e -> onHitWall(), 90);
botEventHandlers.onHitBot.subscribe(this::onHitBot, 90);
botEventHandlers.onDeath.subscribe(this::onDeath, 90);
}
private void onNextTurn(TickEvent e) {
if (e.getTurnNumber() == 1) {
onFirstTurn();
}
processTurn();
}
private void onFirstTurn() {
stopThread(); // sanity before starting a new thread (later)
clearRemaining();
startThread();
}
private void clearRemaining() {
distanceRemaining = 0;
turnRemaining = 0;
gunTurnRemaining = 0;
radarTurnRemaining = 0;
previousDirection = bot.getDirection();
previousGunDirection = bot.getGunDirection();
previousRadarDirection = bot.getRadarDirection();
}
private void onGameAborted() {
stopThread();
}
private void onRoundEnded() {
stopThread();
}
private void onGameEnded(GameEndedEvent e) {
stopThread();
}
private void onDisconnected(DisconnectedEvent e) {
stopThread();
}
private void processTurn() {
// No movement is possible, when the bot has become disabled
if (bot.isDisabled()) {
clearRemaining();
} else {
updateTurnRemaining();
updateGunTurnRemaining();
updateRadarTurnRemaining();
updateMovement();
}
}
private void startThread() {
thread = new Thread(() -> {
baseBotInternals.setRunning(true);
try {
baseBotInternals.enableEventHandling(true);
bot.run();
// Skip every turn after the run method has exited
while (baseBotInternals.isRunning()) {
bot.go();
}
} finally {
baseBotInternals.enableEventHandling(false); // prevent event queue max limit to be reached
}
});
thread.start();
}
private void stopThread() {
if (!isRunning())
return;
baseBotInternals.setRunning(false);
if (thread != null) {
thread.interrupt();
try {
thread.join(1000);
if (thread.isAlive()) {
System.err.println("The thread of the bot could not be interrupted causing the bot to hang.\nSo the bot was stopped by force.");
System.exit(-1); // last resort without Thread.stop()
}
} catch (InterruptedException e) {
thread.interrupt();
} finally {
thread = null;
}
}
}
private void onHitWall() {
distanceRemaining = 0;
}
private void onHitBot(HitBotEvent e) {
if (e.isRammed()) {
distanceRemaining = 0;
}
}
private void onDeath(DeathEvent e) {
stopThread();
}
public boolean isRunning() {
return baseBotInternals.isRunning();
}
public void setTurnRate(double turnRate) {
overrideTurnRate = false;
turnRemaining = toInfiniteValue(turnRate);
baseBotInternals.setTurnRate(turnRate);
}
public void setGunTurnRate(double gunTurnRate) {
overrideGunTurnRate = false;
gunTurnRemaining = toInfiniteValue(gunTurnRate);
baseBotInternals.setGunTurnRate(gunTurnRate);
}
public void setRadarTurnRate(double radarTurnRate) {
overrideRadarTurnRate = false;
radarTurnRemaining = toInfiniteValue(radarTurnRate);
baseBotInternals.setRadarTurnRate(radarTurnRate);
}
private static double toInfiniteValue(double turnRate) {
if (turnRate > 0) {
return Double.POSITIVE_INFINITY;
}
if (turnRate < 0) {
return Double.NEGATIVE_INFINITY;
}
return 0;
}
public double getDistanceRemaining() {
return distanceRemaining;
}
public double getTurnRemaining() {
return turnRemaining;
}
public double getGunTurnRemaining() {
return gunTurnRemaining;
}
public double getRadarTurnRemaining() {
return radarTurnRemaining;
}
public void setTargetSpeed(double targetSpeed) {
overrideTargetSpeed = false;
if (targetSpeed > 0) {
distanceRemaining = Double.POSITIVE_INFINITY;
} else if (targetSpeed < 0) {
distanceRemaining = Double.NEGATIVE_INFINITY;
} else {
distanceRemaining = 0;
}
baseBotInternals.setTargetSpeed(targetSpeed);
}
public void setForward(double distance) {
overrideTargetSpeed = true;
if (Double.isNaN(distance)) {
throw new IllegalArgumentException("'distance' cannot be NaN");
}
getAndSetNewTargetSpeed(distance);
distanceRemaining = distance;
}
public void forward(double distance) {
if (bot.isStopped()) {
bot.go(); // skip turn by doing nothing in the turn
} else {
setForward(distance);
waitFor(() -> distanceRemaining == 0 && bot.getSpeed() == 0);
}
}
public void setTurnLeft(double degrees) {
overrideTurnRate = true;
turnRemaining = degrees;
baseBotInternals.setTurnRate(degrees);
}
public void turnLeft(double degrees) {
if (bot.isStopped()) {
bot.go(); // skip turn by doing nothing in the turn
} else {
setTurnLeft(degrees);
waitFor(() -> turnRemaining == 0);
}
}
public void setTurnGunLeft(double degrees) {
overrideGunTurnRate = true;
gunTurnRemaining = degrees;
baseBotInternals.setGunTurnRate(degrees);
}
public void turnGunLeft(double degrees) {
if (bot.isStopped()) {
bot.go(); // skip turn by doing nothing in the turn
} else {
setTurnGunLeft(degrees);
waitFor(() -> gunTurnRemaining == 0);
}
}
public void setTurnRadarLeft(double degrees) {
overrideRadarTurnRate = true;
radarTurnRemaining = degrees;
baseBotInternals.setRadarTurnRate(degrees);
}
public void turnRadarLeft(double degrees) {
if (bot.isStopped()) {
bot.go(); // skip turn by doing nothing in the turn
} else {
setTurnRadarLeft(degrees);
waitFor(() -> radarTurnRemaining == 0);
}
}
public void fire(double firepower) {
if (bot.setFire(firepower)) {
bot.go();
}
}
public void rescan() {
baseBotInternals.setScannedBotEventInterruptible();
bot.setRescan();
bot.go();
}
public void waitFor(Callable condition) {
do {
bot.go();
} while (isRunning() && !call(condition));
}
private boolean call(Callable condition) {
try {
return condition.call();
} catch (Exception ex) {
throw new BotException("Condition could not be computed", ex);
}
}
public void stop(boolean overwrite) {
baseBotInternals.setStop(overwrite);
bot.go();
}
public void resume() {
baseBotInternals.setResume();
bot.go();
}
public void onStop() {
savedPreviousDirection = previousDirection;
savedPreviousGunDirection = previousGunDirection;
savedPreviousRadarDirection = previousRadarDirection;
savedDistanceRemaining = distanceRemaining;
savedTurnRemaining = turnRemaining;
savedGunTurnRemaining = gunTurnRemaining;
savedRadarTurnRemaining = radarTurnRemaining;
}
public void onResume() {
previousDirection = savedPreviousDirection;
previousGunDirection = savedPreviousGunDirection;
previousRadarDirection = savedPreviousRadarDirection;
distanceRemaining = savedDistanceRemaining;
turnRemaining = savedTurnRemaining;
gunTurnRemaining = savedGunTurnRemaining;
radarTurnRemaining = savedRadarTurnRemaining;
}
private void updateTurnRemaining() {
double delta = bot.calcDeltaAngle(bot.getDirection(), previousDirection);
previousDirection = bot.getDirection();
if (!overrideTurnRate) {
return; // called after previous direction has been calculated and stored!
}
if (abs(turnRemaining) <= abs(delta)) {
turnRemaining = 0;
} else {
turnRemaining -= delta;
if (isNearZero(turnRemaining)) {
turnRemaining = 0;
}
}
baseBotInternals.setTurnRate(turnRemaining);
}
private void updateGunTurnRemaining() {
double delta = bot.calcDeltaAngle(bot.getGunDirection(), previousGunDirection);
previousGunDirection = bot.getGunDirection();
if (!overrideGunTurnRate) {
return; // called after previous direction has been calculated and stored!
}
if (abs(gunTurnRemaining) <= abs(delta)) {
gunTurnRemaining = 0;
} else {
gunTurnRemaining -= delta;
if (isNearZero(gunTurnRemaining)) {
gunTurnRemaining = 0;
}
}
baseBotInternals.setGunTurnRate(gunTurnRemaining);
}
private void updateRadarTurnRemaining() {
double delta = bot.calcDeltaAngle(bot.getRadarDirection(), previousRadarDirection);
previousRadarDirection = bot.getRadarDirection();
if (!overrideRadarTurnRate) {
return; // called after previous direction has been calculated and stored!
}
if (abs(radarTurnRemaining) <= abs(delta)) {
radarTurnRemaining = 0;
} else {
radarTurnRemaining -= delta;
if (isNearZero(radarTurnRemaining)) {
radarTurnRemaining = 0;
}
}
baseBotInternals.setRadarTurnRate(radarTurnRemaining);
}
private void updateMovement() {
if (!overrideTargetSpeed) {
if (abs(distanceRemaining) < abs(bot.getSpeed())) {
distanceRemaining = 0;
} else {
distanceRemaining -= bot.getSpeed();
}
} else if (Double.isInfinite(distanceRemaining)) {
baseBotInternals.setTargetSpeed(distanceRemaining == Double.POSITIVE_INFINITY ? MAX_SPEED : -MAX_SPEED);
} else {
double distance = distanceRemaining;
// This is Nat Pavasant's method described here:
// https://robowiki.net/wiki/User:Positive/Optimal_Velocity#Nat.27s_updateMovement
double newSpeed = getAndSetNewTargetSpeed(distance);
// If we are over-driving our distance, and we are now at speed=0 then we stopped
if (isNearZero(newSpeed) && isOverDriving) {
distance = 0;
isOverDriving = false;
}
// the overdrive flag
if (Math.signum(distance * newSpeed) != -1) {
isOverDriving = baseBotInternals.getDistanceTraveledUntilStop(newSpeed) > abs(distance);
}
distanceRemaining = distance - newSpeed;
}
}
private double getAndSetNewTargetSpeed(double distance) {
double speed = baseBotInternals.getNewTargetSpeed(bot.getSpeed(), distance);
baseBotInternals.setTargetSpeed(speed);
return speed;
}
private boolean isNearZero(double value) {
return abs(value) < .00001;
}
}