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;
}
}