/**
* MovementManager.java
*/
package rampancy_old.management;
import java.awt.geom.Point2D;
import java.util.*;
import java.awt.Graphics2D;
import rampancy_old.*;
import rampancy_old.statistics.MovementStatistic;
import rampancy_old.util.*;
import robocode.util.Utils;
/**
* This class handles Durandal's movement calculation
* @author Matthew Chun-Lum
*
*/
public class MovementManager {
public static final double WALL_STICK = 160;
public static final int SEARCH_DEPTH = 1;
private static final int MAX_HISTORY_DEPTH = 5;
public static Battlefield battlefield;
private RampantRobot reference;
private MovSim movementSimulator;
private ArrayList<RampantRobotState> movementHistory;
private int lastDirection;
public static Battlefield getBattlefield() {
if(battlefield == null)
System.out.println("Accessing a null battlefield!");
return battlefield;
}
/**
* Default constructor
*/
public MovementManager() {
movementSimulator = new MovSim();
}
/**
* Sets the reference to Durandal
* @param reference
*/
public void setInitialState(RampantRobot reference, double battlefieldWidth, double battlefieldHeight) {
this.reference = reference;
if(battlefield == null)
battlefield = new Battlefield(battlefieldWidth, battlefieldHeight);
movementHistory = new ArrayList<RampantRobotState>();
}
/**
* Adds the robot's current state to the movement history
* @param enemy
*/
public void updateCurrentState(EnemyRobot enemy) {
int historyDepth = movementHistory.size();
RampantRobotState last = (historyDepth > 0 ? movementHistory.get(0) : null);
movementHistory.add(0, new RampantRobotState(reference, enemy, last));
if(historyDepth >= MAX_HISTORY_DEPTH)
movementHistory.remove(MAX_HISTORY_DEPTH);
}
/**
* @return a {@link RampantRobotState} representing the robot's state two ticks ago
*/
public RampantRobotState getLastUsableState() {
return (movementHistory.size() < 3 ? null : movementHistory.get(2));
}
/**
* Compute the next move the robot should make
*/
public void computeNextMove(EnemyRobot enemy) {
EnemyWave surfableWave = reference.getWaveManager().getClosestEnemyWaveToImpact(reference.getLocation());
if(surfableWave == null) {
reference.getWaveManager().createVirtualWave(enemy);
} else {
double angleAdjustment = computeAngleAdjustment(enemy);
double[] options = new double[3];
for(int i = 0; i < options.length; i++)
options[i] = getMinimumDanger(angleAdjustment, i);
int min = getMinimumIndex(options);
double goAngle = Util.computeAbsoluteBearing(surfableWave.getOrigin(), reference.getLocation());
switch(min) {
case 0:
//goAngle = wallSmoothing(reference.getLocation(), goAngle + Math.PI / 2 + angleAdjustment, 1);
//Util.setBackAsFront(reference, goAngle, 0);
return;
case 1: // right
goAngle = wallSmoothing(reference.getLocation(), goAngle + Math.PI / 2 + angleAdjustment, 1);
lastDirection = 1;
break;
case 2:
goAngle = wallSmoothing(reference.getLocation(), goAngle - Math.PI / 2 - angleAdjustment, -1);
lastDirection = -1;
}
Util.setBackAsFront(reference, goAngle, 200);
}
}
/**
* Precisely predicts the location the wave will intercept at given the start location
* and the direction we wish to orbit
* @param startLocation
* @param wave
* @param direction
* @return
*/
public MovSimStat predictedPosition(MovSimStat state, EnemyWave wave, double angleAdjustment, int direction) {
MovSim sim = new MovSim();
Point2D.Double predictedPosition = new Point2D.Double(state.x, state.y);
double moveAngle, moveDir;
MovSimStat predictedState = state;
int tickOffset = state.offset;
for(int i = tickOffset; i < tickOffset + 500; i++) {
moveAngle = wallSmoothing(predictedPosition,
Util.computeAbsoluteBearing(wave.getOrigin(), predictedPosition) + (direction * (Math.PI / 2) + direction * angleAdjustment),
direction);
moveAngle -= predictedState.h;
moveDir = 1;
if(Math.cos(moveAngle) < 0) {
moveAngle += Math.PI;
moveDir = -1;
}
moveAngle = Utils.normalRelativeAngle(moveAngle);
MovSimStat[] stats = sim.futurePos(1, predictedPosition,
predictedState.v,
reference.getMaxVelocity(),
predictedState.h,
1000 * moveDir,
moveAngle,
battlefield.width, battlefield.height);
MovSimStat stat = stats[0];
predictedPosition = new Point2D.Double(stat.x, stat.y);
predictedState = stat;
predictedState.offset = i;
if(wave.intercepted(predictedPosition, i))
break;
}
return predictedState;
}
/**
* @return the location Durandal will be at after the next tick.
*/
public MovSimStat nextMovSim() {
return movSim(1);
}
/**
* @return the location Durandal will be at after numTicks.
*/
public MovSimStat movSim(int numTicks) {
if (movementSimulator != null) {
MovSimStat[] next = movementSimulator.futurePos(numTicks, reference);
return next[numTicks - 1];
}
return null;
}
/**
* Compensate for the walls during movement
* @param botLocation
* @param angle
* @param orientation
* @return
*/
public double wallSmoothing(Point2D.Double botLocation, double angle, int orientation) {
if(orientation == 0) orientation = 1;
while (!battlefield.contains(Util.project(botLocation, angle, WALL_STICK))) {
angle += orientation * 0.05;
}
return angle;
}
public void draw(Graphics2D g) {
}
// -------------------- Private Helpers --------------------- //
/*
* Computes the proper angle adjustment to bring the robot close to the enemy or to increase distance
*/
private double computeAngleAdjustment(EnemyRobot enemy) {
double distance = reference.getLocation().distance(enemy.getLocation());
double angle = 0;
double orbitDistance = Constants.IDEAL_DISTANCE + getSuggestedOrbitAdjustment(enemy);
if(distance < Constants.ABSOLUTE_MINIMUM_DISTANCE) {
angle = -Math.PI / 10;
} else if (distance < orbitDistance) {
angle = -Math.PI / 12;
} else if (distance > orbitDistance + 50) {
angle = Math.PI / 12;
}
return angle;
}
private double getSuggestedOrbitAdjustment(EnemyRobot enemy) {
double adjustment = 0;
MovementStatistic enemyStat = reference.getStatisticsManager().getMovementStatsForEnemy(enemy.getName());
if(enemyStat.getHitPercentage() < 0.05) {
adjustment = (1 - enemyStat.getHitPercentage()) * -60;
} else if(enemyStat.getHitPercentage() < 0.15) {
adjustment = (1 - enemyStat.getHitPercentage()) * 100;
} else if(enemyStat.getHitPercentage() > 0.17) {
adjustment = (1- enemyStat.getHitPercentage()) * 250;
}
return adjustment;
}
/*
* Determine the minimum danger for a given movement choice
*/
private double getMinimumDanger(double angleAdjustment, int initialOption) {
MovSimStat[] initialStats = new MovSim().futurePos(1, reference);
MovSimStat initialStat = initialStats[0];
initialStat.offset = 1;
return recursiveGetMinimumDanger(initialStat, null, angleAdjustment, initialOption, SEARCH_DEPTH);
}
/*
* Recursively determine the minimum danger
*/
private double recursiveGetMinimumDanger(MovSimStat state, EnemyWave ignore, double angleAdjustment, int initialOption, int depth) {
Point2D.Double zeroPoint = new Point2D.Double(state.x, state.y);
EnemyWave surfableWave = reference.getWaveManager().getClosestEnemyWaveToImpact(zeroPoint, ignore, state.offset);
if(surfableWave == null)
return 0;
MovSimStat predictedZero = predictedPosition(state, surfableWave, angleAdjustment, lastDirection);
zeroPoint = new Point2D.Double(predictedZero.x, predictedZero.y);
MovSimStat predictedLeft = predictedPosition(state, surfableWave, angleAdjustment, -1);
Point2D.Double leftPoint = new Point2D.Double(predictedLeft.x, predictedLeft.y);
MovSimStat predictedRight = predictedPosition(state, surfableWave, angleAdjustment, 1);
Point2D.Double rightPoint = new Point2D.Double(predictedRight.x, predictedRight.y);
double[] options = getOptionValues(zeroPoint, leftPoint, rightPoint, surfableWave, initialOption);
//if(surfableWave.isVirtual())
// options[0] += 2000;
int minIndex = (initialOption == -1 ? getMinimumIndex(options) : initialOption);
double value = 0;
if(--depth > 0) {
switch(minIndex) {
case 0:
value = recursiveGetMinimumDanger(state, surfableWave, angleAdjustment, -1, depth);
break;
case 1:
value = recursiveGetMinimumDanger(predictedRight, surfableWave, angleAdjustment, -1, depth);
break;
case 2:
value = recursiveGetMinimumDanger(predictedLeft, surfableWave, angleAdjustment, -1, depth);
break;
}
}
return options[minIndex] + value;
}
/*
* returns an array of the danger values for each option
*/
private double[] getOptionValues(Point2D.Double zero, Point2D.Double left, Point2D.Double right, EnemyWave wave, int initialOption) {
double[] options = new double[3];
if(initialOption < 0 ) {
options[2] = reference.getStatisticsManager().getDanger(left, wave);
options[1] = reference.getStatisticsManager().getDanger(right, wave);
options[0] = reference.getStatisticsManager().getDanger(zero, wave);
} else {
switch(initialOption) {
case 0:
options[0] = reference.getStatisticsManager().getDanger(zero, wave);
break;
case 1:
options[1] = reference.getStatisticsManager().getDanger(right, wave);
break;
case 2:
options[2] = reference.getStatisticsManager().getDanger(left, wave);
}
}
return options;
}
/*
* Gets the index for the minimum value of the passed array
*/
private int getMinimumIndex(double[] options) {
int minIndex = 1;
for(int i = 0; i < options.length; i++)
if(options[minIndex] > options[i])
minIndex = i;
return minIndex;
}
}