package net.sf.robocode.battle.peer; import static java.lang.Math.cos; import static java.lang.Math.sin; import static robocode.util.Utils.isNear; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import net.sf.robocode.battle.Battle; import net.sf.robocode.battle.BoundingRectangle; import net.sf.robocode.peer.ExecCommands; import robocode.Rules; import robocode.control.snapshot.RobotState; public class RobotPeerEngine { protected Battle battle; public static final int WIDTH = 40; public static final int HEIGHT = 40; protected static final int HALF_WIDTH_OFFSET = (WIDTH / 2 - 2); protected static final int HALF_HEIGHT_OFFSET = (HEIGHT / 2 - 2); protected double lastHeading; protected boolean isEnergyDrained; protected boolean isWinner; private boolean isOverDriving; protected RobotState state; protected final BoundingRectangle boundingBox; protected double velocity; protected double bodyHeading; protected double x; protected double y; protected AtomicReference<ExecCommands> commands = new AtomicReference<ExecCommands>(); protected ExecCommands currentCommands; protected double energy; protected boolean isExecFinishedAndDisabled; public void updateEnergy(double delta) { if ((!isExecFinishedAndDisabled && !isEnergyDrained) || delta < 0) { setEnergy(energy + delta, true); } } public double getEnergy() { return energy; } protected void setEnergy(double newEnergy, boolean resetInactiveTurnCount) { if (resetInactiveTurnCount && (energy != newEnergy)) { battle.resetInactiveTurnCount(energy - newEnergy); } energy = newEnergy; if (energy < .01) { energy = 0; ExecCommands localCommands = commands.get(); localCommands.setDistanceRemaining(0); localCommands.setBodyTurnRemaining(0); } } public RobotPeerEngine(Battle battle){ boundingBox = new BoundingRectangle(); this.battle = battle; } public RobotState getState() { return state; } public void setState(RobotState state) { this.state = state; } public boolean isDead() { return state == RobotState.DEAD; } public boolean isAlive() { return state != RobotState.DEAD; } public boolean isWinner() { return isWinner; } public BoundingRectangle getBoundingBox() { return boundingBox; } public double getBodyHeading() { return bodyHeading; } public double getVelocity() { return velocity; } public double getX() { return x; } public double getY() { return y; } protected boolean validSpot(List<? extends RobotPeerEngine> robots) { for (RobotPeerEngine otherRobot : robots) { if (otherRobot != null && otherRobot != this) { if (getBoundingBox().intersects(otherRobot.getBoundingBox())) { return false; } } } return true; } public void updateBoundingBox() { boundingBox.setRect(x - WIDTH / 2 + 2, y - HEIGHT / 2 + 2, WIDTH - 4, HEIGHT - 4); } /** * Updates the robots movement. * * This is Nat Pavasants method described here: * http://robowiki.net/wiki/User:Positive/Optimal_Velocity#Nat.27s_updateMovement */ protected void updateMovement() { double distance = currentCommands.getDistanceRemaining(); if (Double.isNaN(distance)) { distance = 0; } velocity = getNewVelocity(velocity, distance); // If we are over-driving our distance and we are now at velocity=0 // then we stopped. if (isNear(velocity, 0) && isOverDriving) { currentCommands.setDistanceRemaining(0); distance = 0; isOverDriving = false; } // If we are moving normally and the breaking distance is more // than remaining distance, enabled the overdrive flag. if (Math.signum(distance * velocity) != -1) { if (getDistanceTraveledUntilStop(velocity) > Math.abs(distance)) { isOverDriving = true; } else { isOverDriving = false; } } currentCommands.setDistanceRemaining(distance - velocity); if (velocity != 0) { x += velocity * sin(bodyHeading); y += velocity * cos(bodyHeading); updateBoundingBox(); } } private double getDistanceTraveledUntilStop(double velocity) { double distance = 0; velocity = Math.abs(velocity); while (velocity > 0) { distance += (velocity = getNewVelocity(velocity, 0)); } return distance; } /** * Returns the new velocity based on the current velocity and distance to move. * * @param velocity the current velocity * @param distance the distance to move * @return the new velocity based on the current velocity and distance to move * * This is Patrick Cupka (aka Voidious), Julian Kent (aka Skilgannon), and Positive's method described here: * http://robowiki.net/wiki/User:Voidious/Optimal_Velocity#Hijack_2 */ private double getNewVelocity(double velocity, double distance) { if (distance < 0) { // If the distance is negative, then change it to be positive // and change the sign of the input velocity and the result return -getNewVelocity(-velocity, -distance); } final double goalVel; if (distance == Double.POSITIVE_INFINITY) { goalVel = currentCommands.getMaxVelocity(); } else { goalVel = Math.min(getMaxVelocity(distance), currentCommands.getMaxVelocity()); } if (velocity >= 0) { return Math.max(velocity - Rules.DECELERATION, Math.min(goalVel, velocity + Rules.ACCELERATION)); } // else return Math.max(velocity - Rules.ACCELERATION, Math.min(goalVel, velocity + maxDecel(-velocity))); } static final double getMaxVelocity(double distance) { final double decelTime = Math.max(1, Math.ceil(// sum of 0... decelTime, solving for decelTime using quadratic formula (Math.sqrt((4 * 2 / Rules.DECELERATION) * distance + 1) - 1) / 2)); if (decelTime == Double.POSITIVE_INFINITY) { return Rules.MAX_VELOCITY; } final double decelDist = (decelTime / 2.0) * (decelTime - 1) // sum of 0..(decelTime-1) * Rules.DECELERATION; return ((decelTime - 1) * Rules.DECELERATION) + ((distance - decelDist) / decelTime); } private static double maxDecel(double speed) { double decelTime = speed / Rules.DECELERATION; double accelTime = (1 - decelTime); return Math.min(1, decelTime) * Rules.DECELERATION + Math.max(0, accelTime) * Rules.ACCELERATION; } }