Difference between revisions of "User:Chase-san/NewTech/KakeruCode"

From Robowiki
Jump to navigation Jump to search
m
m (update)
 
Line 2: Line 2:
  
 
<syntaxhighlight>
 
<syntaxhighlight>
package cs;
+
/**
 
+
* Copyright (c) 2011 Chase
 +
*
 +
* This software is provided 'as-is', without any express or implied
 +
* warranty. In no event will the authors be held liable for any damages
 +
* arising from the use of this software.
 +
*
 +
* Permission is granted to anyone to use this software for any purpose,
 +
* including commercial applications, and to alter it and redistribute it
 +
* freely, subject to the following restrictions:
 +
*
 +
*    1. The origin of this software must not be misrepresented; you must not
 +
*    claim that you wrote the original software. If you use this software
 +
*    in a product, an acknowledgment in the product documentation would be
 +
*    appreciated but is not required.
 +
*
 +
*    2. Altered source versions must be plainly marked as such, and must not be
 +
*    misrepresented as being the original software.
 +
*
 +
*    3. This notice may not be removed or altered from any source
 +
*    distribution.
 +
*/
 
/*
 
/*
 
  * Dear Future Chase,
 
  * Dear Future Chase,
Line 15: Line 35:
 
  *      but you know how bad I am at those.
 
  *      but you know how bad I am at those.
 
  */
 
  */
 +
 +
package cs.move;
  
 
import java.awt.Color;
 
import java.awt.Color;
 
import java.util.Iterator;
 
import java.util.Iterator;
 
import java.util.LinkedList;
 
import java.util.LinkedList;
 +
import java.util.List;
 +
 +
import cs.Chikaku;
 +
import cs.geom.*;
 +
import cs.utils.*;
  
import robocode.Bullet;
+
import ags.utils.KdTree;
import robocode.BulletHitBulletEvent;
+
import ags.utils.KdTree.Entry;
import robocode.BulletHitEvent;
+
import robocode.*;
import robocode.Event;
 
import robocode.HitByBulletEvent;
 
import robocode.Rules;
 
import robocode.ScannedRobotEvent;
 
 
import robocode.util.Utils;
 
import robocode.util.Utils;
 
import cs.geom.*;
 
import cs.util.Simulate;
 
import cs.util.Wave;
 
  
 
/**
 
/**
Line 40: Line 59:
 
  */
 
  */
 
public abstract class Kakeru extends Chikaku {
 
public abstract class Kakeru extends Chikaku {
+
private static final int MaxTurnHistoryLength = 20;
private static class TurnEntry {
+
private static final int MaxHeatWaveQueueLength = 4;
public Vector myPosition;
+
private static final int AverageSingleRoundTime = 1200;
public Vector enemyPosition;
+
private static final int NearestNeighborsToUseInSurfing = 16;
public double myLateralVelocity;
+
 
}
 
 
private static class DriveData {
 
double angleToTurn = 0;
 
double maxVelocity = Rules.MAX_VELOCITY;
 
int direction = 1;
 
}
 
 
private static class DangerData {
 
double danger;
 
double maxVelocity;
 
int orbitDirection;
 
}
 
 
 
/**
 
/**
* Time needed to turn a quarter circle if we are not moving, the actual movement
+
* Keep in mind with heat waves we will have waves 2 rounds earlier
* should be against the current velocity for this, but since we stop moving when
+
* the the gun heat would otherwise indicate
* we goto turn its only 4 turns to stop moving and we have some time usually before
 
* the first bullet gets to us
 
 
*/
 
*/
private static final double turnsNeededToTurnQuarter = (Math.PI/2.0)/Rules.MAX_TURN_RATE_RADIANS;
+
private static final int TimeBeforeFirstWaveToStartTurning = 4;
+
 
public LinkedList<TurnEntry> history = new LinkedList<TurnEntry>();
+
private static final MaximumDeque<Double> enemyBulletPower;
public LinkedList<Wave> enemyWaves = new LinkedList<Wave>();
+
private static final KdTree<Data> tree;
public static LinkedList<Double> hitFactors = new LinkedList<Double>();
+
 
public static LinkedList<Double> enemyBulletPower = new LinkedList<Double>();
+
static {
public double enemyGunHeat = 0;
+
enemyBulletPower = new MaximumDeque<Double>(MaxHeatWaveQueueLength);
+
enemyBulletPower.addLast(3.0);
/**
+
 
* <pre>
+
int dimensions = new Data().getDataArray().length;
*  1 = clockwise
+
tree = new KdTree.SqrEuclid<Data>(dimensions, 0);
* -1 = counter-clockwise
+
 
* </pre>
+
Data data = new Data();
*/
+
data.weight = 0.1;
public int orbitDirection = 1;
+
tree.addPoint(data.getDataArray(), data);
+
}
public Vector lastEnemyPosition = new Vector();
+
 
public double lastEnemyEnergy = 100;
+
private final LinkedList<Wave<Data>> enemyWaves;
+
private final MaximumDeque<State> history;
public void onBattleStarted(Event e) {
+
private Vector lastEnemyPosition;
hitFactors.addLast((Double)0.0);
+
private double lastEnemyEnergy;
enemyBulletPower.addLast((Double)3.0);
+
private double lastEnemyVelocity;
 +
private double enemyGunHeat;
 +
 
 +
private int orbitDirection = 1;
 +
private boolean haveFiredImaginaryWave = false;
 +
 
 +
public Kakeru() {
 +
enemyWaves = new LinkedList<Wave<Data>>();
 +
history = new MaximumDeque<State>(MaxTurnHistoryLength);
 +
lastEnemyPosition = new Vector(-1,-1);
 +
lastEnemyEnergy = 100;
 
}
 
}
+
 
 +
@Override
 
public void onRoundStarted(Event e) {
 
public void onRoundStarted(Event e) {
 
super.onRoundStarted(e);
 
super.onRoundStarted(e);
+
 
println("hitFactors: " + hitFactors.size());
+
/* Set it to be the same as our gun heat on round start! */
+
enemyGunHeat = status.getGunHeat();
firstScan = true;
 
 
}
 
}
+
 
private boolean imaginaryFired = false;
+
@Override
private boolean firstScan = false;
 
 
public void onScannedRobot(ScannedRobotEvent e) {
 
public void onScannedRobot(ScannedRobotEvent e) {
 
super.onScannedRobot(e);
 
super.onScannedRobot(e);
+
 
enemyGunHeat -= peer.getGunCoolingRate();
+
double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians();
if(enemyGunHeat <= 0)
+
double enemyVelocity = e.getVelocity();
enemyGunHeat = 0;
+
 
+
State state = new State();
if(firstScan) {
+
 
enemyGunHeat = status.getGunHeat();
+
state.myPosition = myPosition;
firstScan = false;
+
state.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance());
 +
state.data.lateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians());
 +
state.data.advancingVelcity = status.getVelocity()*Math.cos(e.getBearingRadians());
 +
state.data.globalTime = globalTime;
 +
 
 +
history.addFirst(state);
 +
 
 +
int direction = Tools.sign(state.data.lateralVelocity);
 +
 
 +
orbitDirection = direction;
 +
 
 +
if(Math.abs(enemyVelocity) == 0 && lastEnemyVelocity > 2) {
 +
 
 +
//Hit the wall!
 +
lastEnemyEnergy -= Rules.getWallHitDamage(lastEnemyVelocity);
 
}
 
}
+
 
 
/*
 
/*
* Setup current history
+
* Fire new imaginary wave if we can, heat waves
 +
* are always 2 ticks previous to a real one 'possibly'
 +
* being fired
 
*/
 
*/
double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians();
+
if(!haveFiredImaginaryWave) {
+
if(enemyGunHeat <= 2.0*peer.getGunCoolingRate()) {
TurnEntry entry = new TurnEntry();
+
haveFiredImaginaryWave = true;
entry.myPosition = myPosition;
+
 
entry.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance());
+
double power = 0;
entry.myLateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians());
+
for(Double d : enemyBulletPower)
+
power += d;
orbitDirection = Tools.sign(entry.myLateralVelocity);
+
power /= enemyBulletPower.size();
+
 
history.addFirst(entry);
+
/* Create the Wave */
+
Wave<Data> wave = createMovementWave(power,direction);
 +
wave.set(simulatedEnemyPosition);
 +
wave.imaginary = true;
 +
 
 +
/*
 +
* Only +1 because real waves are -1 in time,
 +
* meaning that by the time we detect them, it has
 +
* already been 1 turn since it was fired, meaning
 +
* heat waves +2 makes it only +1 here. :-)
 +
*/
 +
wave.fireTime = time + 1;
 +
wave.directAngle = wave.angleTo(state.myPosition);
  
 +
wave.data = state.data;
  
/*
+
enemyWaves.add(wave);
* Check for new enemy wave
+
}
*/
+
}
 +
 
 +
/* Check for new enemy wave */
 
double enemyEnergy = e.getEnergy();
 
double enemyEnergy = e.getEnergy();
 
double energyDelta = lastEnemyEnergy - enemyEnergy;
 
double energyDelta = lastEnemyEnergy - enemyEnergy;
 
if(energyDelta > 0 && energyDelta <= 3.0) {
 
if(energyDelta > 0 && energyDelta <= 3.0) {
 +
 
enemyGunHeat += Rules.getGunHeat(energyDelta);
 
enemyGunHeat += Rules.getGunHeat(energyDelta);
imaginaryFired = false;
+
haveFiredImaginaryWave = false;
+
 
Wave wave = new Wave();
+
State old = history.get(2);
 +
direction = Tools.sign(old.data.lateralVelocity);
 +
 
 +
/* Create the Wave */
 +
Wave<Data> wave = createMovementWave(energyDelta,direction);
 
wave.set(lastEnemyPosition);
 
wave.set(lastEnemyPosition);
 +
 
wave.fireTime = time - 1;
 
wave.fireTime = time - 1;
wave.power = energyDelta;
+
wave.directAngle = wave.angleTo(old.myPosition);
wave.speed = Rules.getBulletSpeed(energyDelta);
+
 
+
wave.data = old.data;
TurnEntry stat = history.get(2);
+
 
wave.directAngle = stat.enemyPosition.angleTo(stat.myPosition);
+
enemyBulletPower.addLast(wave.power);
 
int direction = Tools.sign(stat.myLateralVelocity);
 
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
 
 
enemyBulletPower.addLast(energyDelta);
 
if(enemyBulletPower.size() == 5) {
 
enemyBulletPower.removeFirst();
 
}
 
 
 
enemyWaves.add(wave);
 
enemyWaves.add(wave);
 
}
 
}
+
 
/*
+
lastEnemyPosition = state.enemyPosition;
* Setup data for next round.
+
lastEnemyVelocity = enemyVelocity;
*/
 
lastEnemyPosition.set(entry.enemyPosition);
 
 
lastEnemyEnergy = enemyEnergy;
 
lastEnemyEnergy = enemyEnergy;
+
 
g.setColor(Color.YELLOW);
+
}
g.drawLine((int)myPosition.x, (int)myPosition.y, (int)lastEnemyPosition.x, (int)lastEnemyPosition.y);
+
 
+
private Wave<Data> createMovementWave(double power, int direction) {
g.setColor(Color.RED);
+
Wave<Data> wave = new Wave<Data>();
Vector v = myPosition.copy();
+
wave.power = power;
v.add(lastEnemyPosition);
+
wave.speed = Rules.getBulletSpeed(wave.power);
v.scale(0.5);
+
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
+
return wave;
g.drawString(""+e.getDistance(), (int)v.x, (int)v.y);
 
 
/**
 
* Calculate imaginary wave
 
*/
 
if(!imaginaryFired && enemyGunHeat <= peer.getGunCoolingRate()*2.0) {
 
imaginaryFired = true;
 
//add up average bullet power
 
double power = 0;
 
for(Double p : enemyBulletPower) {
 
power += p;
 
}
 
power /= enemyBulletPower.size();
 
 
//Add virtual wave :D
 
Wave wave = new Wave();
 
 
//lastEnemyPosition
 
 
wave.set(simulatedEnemyPosition);
 
wave.fireTime = time + 1;
 
wave.power = power;
 
wave.speed = Rules.getBulletSpeed(power);
 
 
wave.directAngle = entry.enemyPosition.angleTo(entry.myPosition);
 
 
int direction = Tools.sign(entry.myLateralVelocity);
 
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
 
wave.imaginary = true;
 
 
enemyWaves.add(wave);
 
}
 
 
}
 
}
+
 
 +
@Override
 
public void onTurnEnded(Event e) {
 
public void onTurnEnded(Event e) {
 
super.onTurnEnded(e);
 
super.onTurnEnded(e);
+
 
/*
+
/* Don't move if we are scanning, this is mostly start
* Don't move if we are scanning, this is mostly start
 
 
* of round fast radar finding protection
 
* of round fast radar finding protection
 
*/
 
*/
 
if(initialTick != 0)
 
if(initialTick != 0)
 
return;
 
return;
+
 
 +
/* We track the gun heat because it is important
 +
* for our heat/imaginary waves
 +
*/
 +
enemyGunHeat -= coolingRate;
 +
if(enemyGunHeat <= 0)
 +
enemyGunHeat = 0;
 +
 
 +
/* Take are of all waves that cannot possibly even conceivably hit us anymore */
 
checkWaves();
 
checkWaves();
+
 
 +
/* Run our 'actual' movement */
 
doMovement();
 
doMovement();
 
}
 
}
+
 
/**
+
private void checkWaves() {
* Run our actual movement
+
Iterator<Wave<Data>> waveIterator = enemyWaves.iterator();
*/
+
while(waveIterator.hasNext()) {
public void doMovement() {
+
Wave<Data> wave = waveIterator.next();
 +
 
 +
double radius = wave.getRadius(time);
 +
 
 +
/* /////////////////////// */
 +
/* DRAW WAVE */
 +
g.setColor(new Color(128,128,128));
 +
double escape = Tools.abs(wave.escapeAngle);
 +
g.drawArc(
 +
(int)(wave.x - radius), (int)(wave.y - radius),
 +
(int)(radius*2), (int)(radius*2),
 +
(int)Math.toDegrees(wave.directAngle-escape),
 +
(int)Math.toDegrees(escape*2));
 +
/* END DRAW WAVE */
 +
/* /////////////////////// */
 +
 
 +
if(wave.imaginary) {
 +
/*
 +
* if the wave is imaginary (a heat wave)
 +
* we should remove it automatically after
 +
* 2 ticks which is when we will detect their
 +
* actual wave, if they fire
 +
*/
 +
if(time - wave.fireTime > 2) {
 +
waveIterator.remove();
 +
}
 +
} else
 +
/* Clean up waves that can no longer hit us */
 +
if(wave.didIntersect(myPosition, time)) {
 +
waveIterator.remove();
 +
}
 +
}
 +
}
 +
 
 +
private Wave<Data> findBestSurfableWave() {
 +
double halfBotWidth = 18 + Math.sin(lastEnemyPosition.angleTo(myPosition))*7.4558441;
 
//find the wave soonest to hit us
 
//find the wave soonest to hit us
Wave wave = null;
+
Wave<Data> wave = null;
 
double bestETA = Double.POSITIVE_INFINITY;
 
double bestETA = Double.POSITIVE_INFINITY;
for(Wave check : enemyWaves) {
+
for(Wave<Data> check : enemyWaves) {
 
double distance = myPosition.distance(check) - check.getRadius(time);
 
double distance = myPosition.distance(check) - check.getRadius(time);
 
double ETA = distance / check.speed;
 
double ETA = distance / check.speed;
+
 
 +
if(distance < 0 && Math.abs(distance) + check.speed > halfBotWidth)
 +
continue;
 +
 
 
if(ETA < bestETA) {
 
if(ETA < bestETA) {
 
//If we are already surfing a strong wave
 
//If we are already surfing a strong wave
Line 237: Line 289:
 
//shot first for this to occur, which is why
 
//shot first for this to occur, which is why
 
//we don't need an else on the ETA < bestETA
 
//we don't need an else on the ETA < bestETA
+
 
 
//This is mostly just a hack for some score
 
//This is mostly just a hack for some score
 
if(wave != null && ETA > bestETA - 3) {
 
if(wave != null && ETA > bestETA - 3) {
Line 249: Line 301:
 
}
 
}
 
}
 
}
 
 
}
 
}
+
 
double heading = status.getHeadingRadians();
+
return wave;
double velocity = status.getVelocity();
+
}
+
 
//There is no wave for us to surf!
+
 
 +
private void checkMove(Simulate sim, Wave<Data> wave, int orbitDirection) {
 +
Move move = predictMove(sim.position,wave,
 +
sim.heading,sim.velocity,orbitDirection);
 +
 
 +
sim.angleToTurn = move.angleToTurn;
 +
sim.direction = move.direction;
 +
sim.maxVelocity = Tools.min(sim.maxVelocity, move.maxVelocity);
 +
}
 +
 
 +
/**
 +
* Calculate the driving
 +
*/
 +
protected Move predictMove(Vector myPosition, Vector orbitCenter,
 +
double myHeading, double myVelocity, int orbitDirection) {
 +
Move data = new Move();
 +
 
 +
/* Better safe then very very sorry */
 +
if(orbitDirection == 0)
 +
orbitDirection = 1;
 +
 
 +
double angleToRobot = orbitCenter.angleTo(myPosition);
 +
 
 +
/*
 +
* if the orbit direction is clockwise/counter, we want to try and
 +
* point our robot in that direction, which is why we multiply by it
 +
*/
 +
double travelAngle = angleToRobot + (Math.PI/2.0) * orbitDirection;
 +
 
 +
/* DONE add distancing to drive method */
 +
/* TODO add a better distancing method */
 +
double distance = myPosition.distance(orbitCenter);
 +
final double best = 500.0;
 +
double distancing = ((distance-best)/best);
 +
 
 +
travelAngle += distancing*orbitDirection;
 +
 
 +
/* DONE add a wall smoothing method */
 +
/* TODO add a better wall smoothing method */
 +
while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
 +
travelAngle += 0.08*orbitDirection;
 +
}
 +
 
 +
data.angleToTurn = Utils.normalRelativeAngle(travelAngle - myHeading);
 +
data.direction = 1;
 +
 
 +
/*
 +
* If our backend is closer to direction, use that instead, and
 +
* inform the caller that we are going to be going in reverse instead
 +
*/
 +
if(Tools.abs(data.angleToTurn) > Math.PI/2.0) {
 +
data.angleToTurn = Utils.normalRelativeAngle(data.angleToTurn - Math.PI);
 +
data.direction = -1;
 +
}
 +
 
 +
/*
 +
* Slow down so we do not ram head long into the walls and can instead turn to avoid them
 +
* FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
 +
*/
 +
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*3.25)))
 +
data.maxVelocity = 0;
 +
 
 +
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*5)))
 +
data.maxVelocity = 4;
 +
 
 +
return data;
 +
}
 +
 
 +
private void doMovement() {
 +
Wave<Data> wave = findBestSurfableWave();
 
if(wave == null) {
 
if(wave == null) {
double safeTurns = status.getGunHeat()/peer.getGunCoolingRate();
+
doWavelessMovement();
 
//Do we have enough time to move around before they can start firing?
 
if(safeTurns > turnsNeededToTurnQuarter) {
 
//No waves movement
 
 
//Here we drive away from the enemy as best as we can
 
 
double travelAngle = lastEnemyPosition.angleTo(myPosition);
 
 
//TODO better wall smoothing
 
while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
 
travelAngle += 0.08*orbitDirection;
 
}
 
 
double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians());
 
int direction = 1;
 
 
if(Math.abs(angleToTurn) > Math.PI/2.0) {
 
angleToTurn = Utils.normalRelativeAngle(angleToTurn - Math.PI);
 
direction = -1;
 
}
 
 
 
//Slow down so we do not ram head long into the walls and can instead turn to avoid them
 
//FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
 
double maxVelocity = Rules.MAX_VELOCITY;
 
 
if(!field.contains(myPosition.projectNew(heading, velocity*3.25)))
 
maxVelocity = 0;
 
 
if(!field.contains(myPosition.projectNew(heading, velocity*5)))
 
maxVelocity = 4;
 
 
 
peer.setMaxVelocity(maxVelocity);
 
peer.setTurnBody(angleToTurn);
 
peer.setMove(100*direction);
 
} else {
 
//turn to face perpendicular to the enemy while we have time to do so!
 
DriveData data = doDrive(myPosition,lastEnemyPosition,
 
status.getHeadingRadians(),status.getVelocity(),orbitDirection);
 
peer.setTurnBody(data.angleToTurn);
 
peer.setMaxVelocity(0);
 
}
 
 
 
return;
 
return;
 
}
 
}
+
 
//Do the actual surfing and such
+
g.setColor(Color.WHITE);
+
g.draw(field.toRectangle2D());
 +
 
 +
doSurfingMovement(wave);
 +
}
 +
 
 +
private void doSurfingMovement(Wave<Data> wave) {
 +
/* Do the actual surfing and such */
 
g.setColor(Color.ORANGE);
 
g.setColor(Color.ORANGE);
+
 
 
peer.setMaxVelocity(Rules.MAX_VELOCITY);
 
peer.setMaxVelocity(Rules.MAX_VELOCITY);
+
 
//DONE make the direction data a class instead
+
Risk[] directions = new Risk[] {
DangerData[] directions = new DangerData[] {
+
checkWaveRisk(wave,orbitDirection,0),
calculateDanger(wave,orbitDirection,0),
+
checkWaveRisk(wave,orbitDirection,Rules.MAX_VELOCITY),
calculateDanger(wave,orbitDirection,Rules.MAX_VELOCITY),
+
checkWaveRisk(wave,-orbitDirection,Rules.MAX_VELOCITY),
calculateDanger(wave,-orbitDirection,Rules.MAX_VELOCITY),
 
 
};
 
};
+
 
 
int bestIndex = 0;
 
int bestIndex = 0;
double leastDanger = Double.POSITIVE_INFINITY;
+
double minRisk = Double.POSITIVE_INFINITY;
for(int i = 0; i < directions.length; ++i) {
+
for(int i = 0; i < directions.length; ++i) {
if(directions[i].danger < leastDanger) {
+
if(directions[i].risk < minRisk) {
 
bestIndex = i;
 
bestIndex = i;
leastDanger = directions[i].danger;
+
minRisk = directions[i].risk;
 
}
 
}
 
}
 
}
+
 
DriveData driver = doDrive(myPosition,lastEnemyPosition,
+
Move move = predictMove(myPosition,lastEnemyPosition,
 
status.getHeadingRadians(),status.getVelocity(),
 
status.getHeadingRadians(),status.getVelocity(),
 
directions[bestIndex].orbitDirection );
 
directions[bestIndex].orbitDirection );
+
 
double forward = 100;
+
peer.setMaxVelocity(Tools.min(directions[bestIndex].maxVelocity,move.maxVelocity));
+
peer.setTurnBody(move.angleToTurn);
peer.setMaxVelocity(Math.min(directions[bestIndex].maxVelocity,driver.maxVelocity));
+
peer.setMove(1000*move.direction);
peer.setTurnBody(driver.angleToTurn);
 
peer.setMove(forward*driver.direction);
 
 
peer.setCall();
 
peer.setCall();
 
}
 
}
+
 
private DangerData calculateDanger(Wave wave, int orbitDirection, double maxVelocity) {
+
private Risk checkWaveRisk(Wave<Data> wave, int orbitDirection, double maxVelocity) {
//Here we just propagate the stuff we got from the input
+
/*
//so that we do not have to write it again in the array somehow
+
* Simplify the output to our direction chooser
DangerData data = new DangerData();
+
*/
data.orbitDirection = orbitDirection;
+
Risk waveRisk = new Risk();
data.maxVelocity = maxVelocity;
+
waveRisk.orbitDirection = orbitDirection;
+
waveRisk.maxVelocity = maxVelocity;
//Simulate ahead a single step, no need to calculate for a position
+
 
// (that is our current one) that we can do nothing about
+
/*
 +
* Create a simulator for our movement
 +
*/
 
Simulate sim = createSimulator();
 
Simulate sim = createSimulator();
 
sim.maxVelocity = maxVelocity;
 
sim.maxVelocity = maxVelocity;
 
sim.direction = orbitDirection;
 
sim.direction = orbitDirection;
doDrive(sim,wave,orbitDirection);
+
 
+
/*
sim.step();
+
* Used for our distance danger
sim.maxVelocity = maxVelocity;
+
*/
+
double currentDistance = wave.distance(sim.position);
//Originally I divided by this, at the moment I do not anymore
+
double predictedDistance = 0;
 
int intersected = 0;
 
int intersected = 0;
+
 
long timeOffset = 1;
+
long timeOffset = 0;
double danger = 0;
+
 
+
/*
//Reset the factors for the wave we are surfing, don't need old
+
* Reset the factors, so we do not get cross risk check contamination
// data messing up our calculations, now do we?
+
*/
 
wave.resetFactors();
 
wave.resetFactors();
+
 
while(true) {
+
/*
 +
* Since our radar is limited to 1200 and the slowest bullet moves at
 +
* 11 per second, calculating past 110 is pointlessly impossible, so
 +
* it is a safe number to use as our break point.
 +
*/
 +
while(timeOffset < 110) {
 
//DRAW DANGER MOVEMENT
 
//DRAW DANGER MOVEMENT
 
g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6);
 
g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6);
+
 
//Every time we intersect iterate
 
 
if(wave.doesIntersect(sim.position, time+timeOffset)) {
 
if(wave.doesIntersect(sim.position, time+timeOffset)) {
 
++intersected;
 
++intersected;
+
predictedDistance += wave.distance(sim.position);
 
} else if(intersected > 0) {
 
} else if(intersected > 0) {
//After the intersection is complete calculate the danger
+
predictedDistance /= intersected;
//for the entire span we intersected
+
 
double intersectDanger = 0;
+
waveRisk.risk += checkIntersectionRisk(wave,predictedDistance);
 
double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0;
 
 
for(Double d : hitFactors) {
 
intersectDanger += 0.1/(1.0+Math.abs(d-factorCenter));
 
if(wave.minFactor < d && wave.maxFactor > d) {
 
intersectDanger += 1.0;
 
}
 
}
 
 
danger += intersectDanger;
 
 
intersected = -1;
 
 
break;
 
break;
 
}
 
}
+
 
//Use our driving method to determine where we will go next
+
checkMove(sim,wave,orbitDirection);
doDrive(sim,wave,orbitDirection);
 
 
sim.step();
 
sim.step();
 
sim.maxVelocity = maxVelocity;
 
sim.maxVelocity = maxVelocity;
+
 
if(++timeOffset > 24)
+
++timeOffset;
break;
+
}
 +
 
 +
if(intersected > 0) {
 +
double distanceRisk = currentDistance / predictedDistance;
 +
distanceRisk *= distanceRisk;
 +
 
 +
//distanceRisk = (distanceRisk + 1.0) / 2.0;
 +
//System.out.println(distanceRisk);
 +
 
 +
waveRisk.risk *= distanceRisk;
 +
}
 +
 
 +
 
 +
return waveRisk;
 +
}
 +
 
 +
private double checkIntersectionRisk(Wave<Data> wave, double predicted) {
 +
double risk = 0;
 +
double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0;
 +
 
 +
double factorRange = Tools.abs(wave.maxFactor - wave.minFactor);
 +
double bandwidth = factorRange / Tools.abs(wave.escapeAngle);
 +
 
 +
List<Entry<Data>> list = tree.nearestNeighbor(wave.data.getWeightedDataArray(),
 +
NearestNeighborsToUseInSurfing, false);
 +
 
 +
for(Entry<Data> e : list) {
 +
Data data = e.value;
 +
 
 +
double entryRisk = 0.1/(1.0+Tools.abs(data.guessfactor-factorCenter));
 +
 
 +
if(wave.minFactor < data.guessfactor && wave.maxFactor > data.guessfactor) {
 +
entryRisk += 0.9;
 +
}
 +
 
 +
double timeWeight = 1.0+AverageSingleRoundTime/(double)(globalTime-data.globalTime);
 +
 
 +
//kernel calculation neh?
 +
 
 +
double density = 0;
 +
 
 +
for(Entry<Data> ex : list) {
 +
double ux = (e.value.guessfactor-data.guessfactor) / bandwidth;
 +
double sw = ex.value.weight / (1.0+ex.distance);
 +
density += Math.exp(-0.5*ux*ux)*sw;
 +
}
 +
 
 +
//System.out.println(density);
 +
 
 +
double weight = (data.weight / (1.0+e.distance)) * timeWeight * density;
 +
 
 +
//System.out.println(weight);
 +
//System.out.println(entryRisk);
 +
 
 +
risk += 1.0-(1.0/(1.0+weight*entryRisk));
 
}
 
}
+
 
data.danger = danger;
+
return risk/NearestNeighborsToUseInSurfing;
return data;
 
 
}
 
}
  
private void doDrive(Simulate sim, Wave wave, int orbitDirection) {
+
private void doWavelessMovement() {
DriveData data = doDrive(sim.position,wave,
+
final int initialTurns = (int)Math.ceil(3.0/coolingRate+4.0);
sim.heading,sim.velocity,orbitDirection);
+
double safeTurns = status.getGunHeat()/coolingRate;
+
 
sim.angleToTurn = data.angleToTurn;
+
if(time < initialTurns) {
sim.direction = data.direction;
+
/* Do we have enough time to move around before they can start firing? */
sim.maxVelocity = Math.min(sim.maxVelocity, data.maxVelocity);
+
if(safeTurns > TimeBeforeFirstWaveToStartTurning) {
 +
doMinimalRiskMovement();
 +
} else {
 +
Move data = predictMove(myPosition,lastEnemyPosition,
 +
status.getHeadingRadians(),status.getVelocity(),orbitDirection);
 +
peer.setTurnBody(data.angleToTurn);
 +
peer.setMaxVelocity(0);
 +
 
 +
peer.setMove(0);
 +
//peer.setTurnBody(0);
 +
peer.setMaxVelocity(0);
 +
}
 +
} else {
 +
/*
 +
* Things are no longer safe, set our movement to `sane`
 +
*
 +
* TODO: RAM if enemy is disabled
 +
*/
 +
if(status.getOthers() == 0) {
 +
/*
 +
* No bullets in the air, enemy dead, we can do our victory DANCE (YAY!)
 +
*/
 +
doVictoryDance();
 +
} else {
 +
//Some other thing to do when nothing is in the air??
 +
doMinimalRiskMovement();
 +
}
 +
}
 
}
 
}
+
 
/**
+
protected void doMinimalRiskMovement() {
* Calculate the driving
+
double heading = status.getHeadingRadians();
*/
+
double velocity = status.getVelocity();
private DriveData doDrive(Vector myPosition, Vector orbitCenter,
+
 
double myHeading, double myVelocity, int orbitDirection) {
+
Rectangle escapeField = new Rectangle(30,30,fieldWidth-60,fieldHeight-60);
DriveData data = new DriveData();
+
g.setColor(Color.WHITE);
+
g.draw(escapeField.toRectangle2D());
double angleToRobot = orbitCenter.angleTo(myPosition);
+
 
+
//Do minimal risk movement
//if the orbit direction is clockwise/counter, we want to try and point
+
Vector target = myPosition.copy();
//our robot in that direction, which is why we multiply by it
+
Vector bestTarget = myPosition;
double travelAngle = angleToRobot + (Math.PI/2.0) * orbitDirection;
+
double angle = 0;
+
double bestRisk = checkWavelessRisk(bestTarget);
//DONE add distancing to drive method
+
double enemyDistance = myPosition.distance(lastEnemyPosition)+18;
//TODO add a better distancing method
+
while(angle < Math.PI*2) {
double distance = myPosition.distance(orbitCenter);
+
double targetDistance = Tools.min(200,enemyDistance);
final double best = 500.0;
+
 
double distancing = ((distance-best)/best);
+
target.setProject(myPosition, angle, targetDistance);
+
if(escapeField.contains(target)) {
travelAngle += distancing*orbitDirection;
+
double risk = checkWavelessRisk(target);
+
if(risk < bestRisk) {
//DONE add a wall smoothing method
+
bestRisk = risk;
//TODO add a better wall smoothing method
+
bestTarget = target.copy();
while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
+
}
travelAngle += 0.08*orbitDirection;
+
g.setColor(Color.BLUE);
 +
g.drawRect((int)target.x-2, (int)target.y-2, 4, 4);
 +
 
 +
}
 +
angle += Math.PI/32.0;
 
}
 
}
+
g.setColor(bodyColor);
data.angleToTurn = Utils.normalRelativeAngle(travelAngle - myHeading);
+
g.drawRect((int)bestTarget.x-2, (int)bestTarget.y-2, 4, 4);
data.direction = 1;
+
 
+
double travelAngle = myPosition.angleTo(bestTarget);
//If our backend is closer to direction, use that instead, and inform
+
 
//the caller that we are going to be going in reverse instead
+
double forward = myPosition.distance(bestTarget);
if(Math.abs(data.angleToTurn) > Math.PI/2.0) {
+
 
data.angleToTurn = Utils.normalRelativeAngle(data.angleToTurn - Math.PI);
+
double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians());
data.direction = -1;
+
int direction = 1;
 +
 
 +
if(Tools.abs(angleToTurn) > Math.PI/2.0) {
 +
angleToTurn = Utils.normalRelativeAngle(angleToTurn - Math.PI);
 +
direction = -1;
 
}
 
}
+
 
 
//Slow down so we do not ram head long into the walls and can instead turn to avoid them
 
//Slow down so we do not ram head long into the walls and can instead turn to avoid them
//FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
+
double maxVelocity = Rules.MAX_VELOCITY;
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*3.25)))
+
 
data.maxVelocity = 0;
+
if(!field.contains(myPosition.projectNew(heading, velocity*3.25)))
+
maxVelocity = 0;
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*5)))
+
 
data.maxVelocity = 4;
+
if(!field.contains(myPosition.projectNew(heading, velocity*5)))
+
maxVelocity = 4;
return data;
+
 
 +
if(angleToTurn > 1.0) {
 +
maxVelocity = 0;
 +
}
 +
 
 +
peer.setMaxVelocity(maxVelocity);
 +
peer.setTurnBody(angleToTurn);
 +
peer.setMove(forward*direction);
 +
}
 +
 
 +
private double checkWavelessRisk(Vector pos) {
 +
double risk = lastEnemyEnergy/pos.distanceSq(lastEnemyPosition);
 +
 
 +
for(Line edge : field.getLines()) {
 +
risk += 5.0/(1.0+edge.ptSegDistSq(pos));
 +
}
 +
 
 +
g.setColor(Color.RED);
 +
/*
 +
* get points between enemy location and corner and add risk!!!!
 +
* these are bad places to be! Our hitbox is larger here if nothing else!
 +
*/
 +
for(Vector corner : field.getCorners()) {
 +
corner.add(lastEnemyPosition);
 +
corner.scale(0.5);
 +
if(corner.distanceSq(lastEnemyPosition) < 22500 /* 150 */) {
 +
g.drawRect((int)corner.x-2, (int)corner.y-2, 4, 4);
 +
}
 +
risk += 5.0/(1.0+corner.distanceSq(pos));
 +
}
 +
 
 +
return risk;
 
}
 
}
+
 
 
/**
 
/**
 
* Account for our bullets hitting the enemy
 
* Account for our bullets hitting the enemy
 
*/
 
*/
 +
@Override
 
public void onBulletHit(BulletHitEvent e) {
 
public void onBulletHit(BulletHitEvent e) {
 
super.onBulletHit(e);
 
super.onBulletHit(e);
+
 
//get the power of the bullet of ours that hit
+
/* Get the power of the bullet of ours that hit */
 
double bulletPower = e.getBullet().getPower();
 
double bulletPower = e.getBullet().getPower();
+
 
//determine how much damage our bullet does to the enemy
+
/* Determine how much damage our bullet does to the enemy
//and adjust our stored copy to reflect the amount lost
+
* and adjust our stored copy to reflect the amount lost
 +
*/
 
lastEnemyEnergy -= Rules.getBulletDamage(bulletPower);
 
lastEnemyEnergy -= Rules.getBulletDamage(bulletPower);
 
}
 
}
+
 
 
/**
 
/**
 
* Account for one of our bullets hitting an enemy bullet.
 
* Account for one of our bullets hitting an enemy bullet.
 
*/
 
*/
 +
 +
@Override
 
public void onBulletHitBullet(BulletHitBulletEvent e) {
 
public void onBulletHitBullet(BulletHitBulletEvent e) {
 
super.onBulletHitBullet(e);
 
super.onBulletHitBullet(e);
+
 
 
handleBullet(e.getHitBullet());
 
handleBullet(e.getHitBullet());
 
}
 
}
+
 
 
/**
 
/**
 
* Account for one of the enemies bullets hitting us.
 
* Account for one of the enemies bullets hitting us.
 
*/
 
*/
 +
@Override
 
public void onHitByBullet(HitByBulletEvent e) {
 
public void onHitByBullet(HitByBulletEvent e) {
 
super.onHitByBullet(e);
 
super.onHitByBullet(e);
+
 
//increase the enemy energy based on how powerful the bullet that hit us was
+
/*
//this is so we can get reliable energy drop detection
+
* Increase the enemy energy based on how powerful the bullet that
 +
* hit us was this is so we can get reliable energy drop detection
 +
*/
 
lastEnemyEnergy += Rules.getBulletHitBonus(e.getPower());
 
lastEnemyEnergy += Rules.getBulletHitBonus(e.getPower());
+
 
 
handleBullet(e.getBullet());
 
handleBullet(e.getBullet());
 
}
 
}
+
 
 
/**
 
/**
 
* Handle an actual enemy bullet
 
* Handle an actual enemy bullet
Line 506: Line 702:
 
private void handleBullet(Bullet b) {
 
private void handleBullet(Bullet b) {
 
Vector bulletPosition = new Vector(b.getX(),b.getY());
 
Vector bulletPosition = new Vector(b.getX(),b.getY());
+
 
//Find the matching wave
+
/* Find the matching wave */
Iterator<Wave> waveIterator = enemyWaves.iterator();
+
Iterator<Wave<Data>> waveIterator = enemyWaves.iterator();
 
while(waveIterator.hasNext()) {
 
while(waveIterator.hasNext()) {
Wave wave = waveIterator.next();
+
Wave<Data> wave = waveIterator.next();
+
 
//the current distance of the wave
+
/* the current distance of the wave */
 
double radius = wave.speed*(time - wave.fireTime);
 
double radius = wave.speed*(time - wave.fireTime);
+
 
//check if the power is close enough to be our wave
+
/* check if the power is close enough to be our wave. This
//this margin is small, only to allow room for rounding errors
+
* margin is small, only to allow room for rounding errors
if(Math.abs(b.getPower()-wave.power) < 0.001) {
+
*/
+
if(Tools.abs(b.getPower()-wave.power) < 0.001) {
//check if the bullets distance from the center of the wave is
+
 
//close to our waves radius.
+
/* check if the bullets distance from the center of
+
* the wave is close to our waves radius. This margin
//this margin is small, only to allow room for rounding errors
+
* is small, only to allow room for rounding errors
if(Math.abs(wave.distanceSq(bulletPosition)-radius*radius) < 0.1) {
+
*/
//FIXME extensively test this detection method
+
if(Tools.abs(wave.distanceSq(bulletPosition)-radius*radius) < 0.1) {
+
/* FIXME extensively test this detection method */
//Alternate method, but why recalculate something? Could be used as an extra check.
+
 
//double angleToBullet = wave.angleTo(bulletPosition);
+
/* Alternate method, but why recalculate something? Could be used as an extra check. */
+
/* updateTree(wave,wave.angleTo(bulletPosition)); */
double angleToBullet = b.getHeadingRadians();
+
updateTree(wave,b.getHeadingRadians());
double angleOffsetFromBase =
+
 
Utils.normalRelativeAngle(angleToBullet - wave.directAngle);
 
 
double bulletGuessfactor = angleOffsetFromBase / wave.escapeAngle;
 
 
debug("Hit @ GF " + bulletGuessfactor);
 
 
hitFactors.addLast(bulletGuessfactor);
 
 
//FIXME Temporary, only allow 17 factors to be in the buffer at a time
 
if(hitFactors.size() == 17) {
 
hitFactors.removeFirst();
 
}
 
 
 
waveIterator.remove();
 
waveIterator.remove();
+
 
break;
+
return;
 
}
 
}
 
}
 
}
 
}
 
}
 +
 +
/* If not found say so, cause someone (me) SCREWED UP!!! */
 +
println("Error: Unknown Bullet Collision");
 +
println("\tBullet:["+b.getX()+","+b.getY()+"] @ h"+b.getHeadingRadians()+" @ p" + b.getPower());
 +
}
 +
 +
private void updateTree(Wave<Data> wave, double angleToBullet) {
 +
double angleOffset = Utils.normalRelativeAngle(angleToBullet - wave.directAngle);
 +
 +
wave.data.guessfactor = angleOffset / wave.escapeAngle;
 +
 +
//TODO
 +
//tree.addPoint(wave.data.getDataArray(), wave.data);
 +
tree.addPoint(wave.data.getWeightedDataArray(), wave.data);
 
}
 
}
 +
}
 +
 +
 +
/**
 +
* Data gained from movement prediction
 +
*/
 +
class Move {
 +
double angleToTurn = 0;
 +
double maxVelocity = Rules.MAX_VELOCITY;
 +
int direction = 1;
 +
}
 +
 +
class Risk {
 +
double risk;
 +
double maxVelocity;
 +
int orbitDirection;
 +
}
 +
 +
public class State {
 +
Vector myPosition;
 +
Vector enemyPosition;
 +
Data data = new Data();
 +
}
 +
 +
public class Data {
 +
long globalTime;
 +
 +
double lateralVelocity;
 +
double advancingVelcity;
 +
double guessfactor;
 +
 +
double weight = 1.0;
 +
 +
public double[] getDataArray() {
 +
return new double[] {
 +
Math.abs(lateralVelocity)/8.0,
 +
(advancingVelcity+8.0)/16.0,
 +
};
 +
}
 +
 +
public double[] getDataWeights() {
 +
return new double[] {
 +
1,
 +
1,
 +
};
 +
}
 +
 +
public double[] getWeightedDataArray() {
 +
double[] data = getDataArray();
 +
double[] weights = getDataWeights();
 +
for(int i=0;i<data.length;++i) {
 +
data[i] *= weights[i];
 +
}
 +
return data;
 +
}
 +
}
 +
 +
public class Wave<T> extends Vector {
 +
public long fireTime;
 +
public double escapeAngle;
 +
public double directAngle;
 +
public double speed;
 +
public double power;
 +
public boolean imaginary = false;
 +
 +
public T data;
 +
 +
public double getRadius(long time) {
 +
return speed*(time - fireTime);
 +
}
 +
 
 
 +
public int intersected = 0;
 
/**
 
/**
* Check waves to be removed after they pass
+
* Used to determine if we can safely remove this wave
 
*/
 
*/
private void checkWaves() {
+
public final boolean didIntersect(Vector target, long time) {
Iterator<Wave> waveIterator = enemyWaves.iterator();
+
hitbox.set(target, 36, 36);
while(waveIterator.hasNext()) {
+
Wave wave = waveIterator.next();
+
double radius = getRadius(time);
 +
double nextRadius = getRadius(time+1);
 +
Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
 +
Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
 +
 +
if(current.length != 0 || next.length != 0) {
 +
++intersected;
 +
} else {
 +
if(intersected > 0) {
 +
return true;
 +
}
 +
}
 +
 +
return false;
 +
}
 +
 +
public static Rectangle hitbox = new Rectangle();
 +
/**
 +
* Used for calculating where a bullet will intersect a target
 +
*/
 +
public final boolean doesIntersect(Vector target, long time) {
 +
hitbox.set(target, 36, 36);
 +
 +
double radius = getRadius(time);
 +
double nextRadius = getRadius(time+1);
 +
Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
 +
Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
 +
 +
if(current.length != 0 || next.length != 0) {
 +
for(Vector v : current)
 +
expandFactors(v);
 
 
double radius = wave.getRadius(time);
+
for(Vector v : next)
 +
expandFactors(v);
 
 
///////////////////////
+
Vector[] corners = hitbox.getCorners();
//DRAW WAVE
+
for(Vector v : corners) {
if(wave.imaginary) {
+
double distance = distanceSq(v);  
g.setColor(new Color(32,32,32));
+
if(distance < nextRadius*nextRadius
} else {
+
&& distance > radius*radius) {
g.setColor(new Color(128,128,128));
+
expandFactors(v);
 +
}
 
}
 
}
double escape = Math.abs(wave.escapeAngle);
 
g.drawArc(
 
(int)(wave.x - radius), (int)(wave.y - radius),
 
(int)(radius*2), (int)(radius*2),
 
(int)Math.toDegrees(wave.directAngle-escape),
 
(int)Math.toDegrees(escape*2));
 
//END DRAW WAVE
 
///////////////////////
 
 
 
if(wave.imaginary) {
+
return true;
//if the wave is imaginary (a heat wave)
 
//we should remove it automatically after
 
//2 ticks which is when we will detect their
 
//actual wave, if they fire
 
if(time - wave.fireTime > 2) {
 
waveIterator.remove();
 
}
 
} else
 
 
/*
 
* Clean up waves that can no longer hit us
 
*/
 
if(wave.didIntersect(myPosition, time)) {
 
waveIterator.remove();
 
}
 
 
}
 
}
 +
 +
return false;
 +
}
 +
 +
public double minFactor = Double.POSITIVE_INFINITY;
 +
public double maxFactor = Double.NEGATIVE_INFINITY;
 +
 +
/**
 +
* Expand the guessfactors based on target location
 +
*/
 +
private void expandFactors(Vector pos) {
 +
double angle = Utils.normalRelativeAngle(angleTo(pos) - directAngle) / escapeAngle;
 +
if(angle < minFactor) minFactor = angle;
 +
if(angle > maxFactor) maxFactor = angle;
 +
}
 +
 +
/**
 +
* Reset our factors for calculating position (important!)
 +
*/
 +
public void resetFactors() {
 +
minFactor = Double.POSITIVE_INFINITY;
 +
maxFactor = Double.NEGATIVE_INFINITY;
 
}
 
}
 
}
 
}
 +
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 16:35, 5 August 2011

Code for version MC25

/**
 * Copyright (c) 2011 Chase
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *    1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 
 *    2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 
 *    3. This notice may not be removed or altered from any source
 *    distribution.
 */
/*
 * Dear Future Chase,
 * 
 * I cannot even begin to express how deeply sorry I am.
 * But I assure you, at the time, I (probably) knew what I was doing.
 * 
 * -Chase
 * 
 * P.S. I am trying to leave comments,
 *      but you know how bad I am at those.
 */

package cs.move;

import java.awt.Color;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import cs.Chikaku;
import cs.geom.*;
import cs.utils.*;

import ags.utils.KdTree;
import ags.utils.KdTree.Entry;
import robocode.*;
import robocode.util.Utils;

/**
 * Movement
 * <br/><br/>
 * In Japanese, Kakeru means "Dash" (in some cases)
 * @author Chase
 */
public abstract class Kakeru extends Chikaku {
	private static final int MaxTurnHistoryLength = 20;
	private static final int MaxHeatWaveQueueLength = 4;
	private static final int AverageSingleRoundTime = 1200;
	private static final int NearestNeighborsToUseInSurfing = 16;

	/**
	 * Keep in mind with heat waves we will have waves 2 rounds earlier
	 * the the gun heat would otherwise indicate
	 */
	private static final int TimeBeforeFirstWaveToStartTurning = 4;

	private static final MaximumDeque<Double> enemyBulletPower;
	private static final KdTree<Data> tree;

	static {
		enemyBulletPower = new MaximumDeque<Double>(MaxHeatWaveQueueLength);
		enemyBulletPower.addLast(3.0);

		int dimensions = new Data().getDataArray().length;
		tree = new KdTree.SqrEuclid<Data>(dimensions, 0);

		Data data = new Data();
		data.weight = 0.1;
		tree.addPoint(data.getDataArray(), data);
	}

	private final LinkedList<Wave<Data>> enemyWaves;
	private final MaximumDeque<State> history;
	private Vector lastEnemyPosition;
	private double lastEnemyEnergy;
	private double lastEnemyVelocity;
	private double enemyGunHeat;

	private int orbitDirection = 1;
	private boolean haveFiredImaginaryWave = false;

	public Kakeru() {
		enemyWaves = new LinkedList<Wave<Data>>();
		history = new MaximumDeque<State>(MaxTurnHistoryLength);
		lastEnemyPosition = new Vector(-1,-1);
		lastEnemyEnergy = 100;
	}

	@Override
	public void onRoundStarted(Event e) {
		super.onRoundStarted(e);

		/* Set it to be the same as our gun heat on round start! */
		enemyGunHeat = status.getGunHeat();
	}

	@Override
	public void onScannedRobot(ScannedRobotEvent e) {
		super.onScannedRobot(e);

		double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians();
		double enemyVelocity = e.getVelocity();

		State state = new State();

		state.myPosition = myPosition;
		state.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance());
		state.data.lateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians());
		state.data.advancingVelcity = status.getVelocity()*Math.cos(e.getBearingRadians());
		state.data.globalTime = globalTime;

		history.addFirst(state);

		int direction = Tools.sign(state.data.lateralVelocity);

		orbitDirection = direction;

		if(Math.abs(enemyVelocity) == 0 && lastEnemyVelocity > 2) {

			//Hit the wall!
			lastEnemyEnergy -= Rules.getWallHitDamage(lastEnemyVelocity);
		}

		/*
		 * Fire new imaginary wave if we can, heat waves
		 * are always 2 ticks previous to a real one 'possibly'
		 * being fired
		 */
		if(!haveFiredImaginaryWave) {
			if(enemyGunHeat <= 2.0*peer.getGunCoolingRate()) {
				haveFiredImaginaryWave = true;

				double power = 0;
				for(Double d : enemyBulletPower)
					power += d;
				power /= enemyBulletPower.size();

				/* Create the Wave */
				Wave<Data> wave = createMovementWave(power,direction);
				wave.set(simulatedEnemyPosition);
				wave.imaginary = true;

				/*
				 * Only +1 because real waves are -1 in time,
				 * meaning that by the time we detect them, it has
				 * already been 1 turn since it was fired, meaning
				 * heat waves +2 makes it only +1 here. :-)
				 */
				wave.fireTime = time + 1;
				wave.directAngle = wave.angleTo(state.myPosition);

				wave.data = state.data;

				enemyWaves.add(wave);
			}
		}

		/* Check for new enemy wave */
		double enemyEnergy = e.getEnergy();
		double energyDelta = lastEnemyEnergy - enemyEnergy;
		if(energyDelta > 0 && energyDelta <= 3.0) {

			enemyGunHeat += Rules.getGunHeat(energyDelta);
			haveFiredImaginaryWave = false;

			State old = history.get(2);
			direction = Tools.sign(old.data.lateralVelocity);

			/* Create the Wave */
			Wave<Data> wave = createMovementWave(energyDelta,direction);
			wave.set(lastEnemyPosition);

			wave.fireTime = time - 1;
			wave.directAngle = wave.angleTo(old.myPosition);

			wave.data = old.data;

			enemyBulletPower.addLast(wave.power);
			enemyWaves.add(wave);
		}

		lastEnemyPosition = state.enemyPosition;
		lastEnemyVelocity = enemyVelocity;
		lastEnemyEnergy = enemyEnergy;

	}

	private Wave<Data> createMovementWave(double power, int direction) {
		Wave<Data> wave = new Wave<Data>();
		wave.power = power;
		wave.speed = Rules.getBulletSpeed(wave.power);
		wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
		return wave;
	}

	@Override
	public void onTurnEnded(Event e) {
		super.onTurnEnded(e);

		/* Don't move if we are scanning, this is mostly start
		 * of round fast radar finding protection
		 */
		if(initialTick != 0)
			return;

		/* We track the gun heat because it is important
		 * for our heat/imaginary waves
		 */
		enemyGunHeat -= coolingRate;
		if(enemyGunHeat <= 0)
			enemyGunHeat = 0;

		/* Take are of all waves that cannot possibly even conceivably hit us anymore */
		checkWaves();

		/* Run our 'actual' movement */
		doMovement();
	}

	private void checkWaves() {
		Iterator<Wave<Data>> waveIterator = enemyWaves.iterator();
		while(waveIterator.hasNext()) {
			Wave<Data> wave = waveIterator.next();

			double radius = wave.getRadius(time);

			/* /////////////////////// */
			/* DRAW WAVE */
			g.setColor(new Color(128,128,128));
			double escape = Tools.abs(wave.escapeAngle);
			g.drawArc(
					(int)(wave.x - radius), (int)(wave.y - radius),
					(int)(radius*2), (int)(radius*2),
					(int)Math.toDegrees(wave.directAngle-escape),
					(int)Math.toDegrees(escape*2));
			/* END DRAW WAVE */
			/* /////////////////////// */

			if(wave.imaginary) {
				/*
				 * if the wave is imaginary (a heat wave)
				 * we should remove it automatically after
				 * 2 ticks which is when we will detect their
				 * actual wave, if they fire
				 */
				if(time - wave.fireTime > 2) {
					waveIterator.remove();
				}
			} else
				/* Clean up waves that can no longer hit us */
				if(wave.didIntersect(myPosition, time)) {
					waveIterator.remove();
				}
		}
	}

	private Wave<Data> findBestSurfableWave() {
		double halfBotWidth = 18 + Math.sin(lastEnemyPosition.angleTo(myPosition))*7.4558441;
		//find the wave soonest to hit us
		Wave<Data> wave = null;
		double bestETA = Double.POSITIVE_INFINITY;
		for(Wave<Data> check : enemyWaves) {
			double distance = myPosition.distance(check) - check.getRadius(time);
			double ETA = distance / check.speed;

			if(distance < 0 && Math.abs(distance) + check.speed > halfBotWidth)
				continue;

			if(ETA < bestETA) {
				//If we are already surfing a strong wave
				//that will hit at almost the same time
				//do not switch to this wave, more powerful
				//waves are slower and thus will always be
				//shot first for this to occur, which is why
				//we don't need an else on the ETA < bestETA

				//This is mostly just a hack for some score
				if(wave != null && ETA > bestETA - 3) {
					if(check.power > wave.power) {
						wave = check;
						bestETA = ETA;
					}
				} else {
					wave = check;
					bestETA = ETA;
				}
			}
		}

		return wave;
	}


	private void checkMove(Simulate sim, Wave<Data> wave, int orbitDirection) {
		Move move = predictMove(sim.position,wave,
				sim.heading,sim.velocity,orbitDirection);

		sim.angleToTurn = move.angleToTurn;
		sim.direction = move.direction;
		sim.maxVelocity = Tools.min(sim.maxVelocity, move.maxVelocity);
	}

	/**
	 * Calculate the driving
	 */
	protected Move predictMove(Vector myPosition, Vector orbitCenter,
			double myHeading, double myVelocity, int orbitDirection) {
		Move data = new Move();

		/* Better safe then very very sorry */
		if(orbitDirection == 0)
			orbitDirection = 1;

		double angleToRobot = orbitCenter.angleTo(myPosition);

		/*
		 * if the orbit direction is clockwise/counter, we want to try and
		 * point our robot in that direction, which is why we multiply by it
		 */
		double travelAngle = angleToRobot + (Math.PI/2.0) * orbitDirection;

		/* DONE add distancing to drive method */
		/* TODO add a better distancing method */
		double distance = myPosition.distance(orbitCenter);
		final double best = 500.0;
		double distancing = ((distance-best)/best);

		travelAngle += distancing*orbitDirection;

		/* DONE add a wall smoothing method */
		/* TODO add a better wall smoothing method */
		while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
			travelAngle += 0.08*orbitDirection;
		}

		data.angleToTurn = Utils.normalRelativeAngle(travelAngle - myHeading);
		data.direction = 1;

		/*
		 * If our backend is closer to direction, use that instead, and
		 * inform the caller that we are going to be going in reverse instead
		 */
		if(Tools.abs(data.angleToTurn) > Math.PI/2.0) {
			data.angleToTurn = Utils.normalRelativeAngle(data.angleToTurn - Math.PI);
			data.direction = -1;
		}

		/*
		 * Slow down so we do not ram head long into the walls and can instead turn to avoid them
		 * FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
		 */
		if(!field.contains(myPosition.projectNew(myHeading, myVelocity*3.25)))
			data.maxVelocity = 0;

		if(!field.contains(myPosition.projectNew(myHeading, myVelocity*5)))
			data.maxVelocity = 4;

		return data;
	}

	private void doMovement() {
		Wave<Data> wave = findBestSurfableWave();
		if(wave == null) {
			doWavelessMovement();
			return;
		}

		g.setColor(Color.WHITE);
		g.draw(field.toRectangle2D());

		doSurfingMovement(wave);
	}

	private void doSurfingMovement(Wave<Data> wave) {
		/* Do the actual surfing and such */
		g.setColor(Color.ORANGE);

		peer.setMaxVelocity(Rules.MAX_VELOCITY);

		Risk[] directions = new Risk[] {
				checkWaveRisk(wave,orbitDirection,0),
				checkWaveRisk(wave,orbitDirection,Rules.MAX_VELOCITY),
				checkWaveRisk(wave,-orbitDirection,Rules.MAX_VELOCITY),
		};

		int bestIndex = 0;
		double minRisk = Double.POSITIVE_INFINITY;
		for(int i = 0; i < directions.length; ++i) {
			if(directions[i].risk < minRisk) {
				bestIndex = i;
				minRisk = directions[i].risk;
			}
		}

		Move move = predictMove(myPosition,lastEnemyPosition,
				status.getHeadingRadians(),status.getVelocity(),
				directions[bestIndex].orbitDirection );

		peer.setMaxVelocity(Tools.min(directions[bestIndex].maxVelocity,move.maxVelocity));
		peer.setTurnBody(move.angleToTurn);
		peer.setMove(1000*move.direction);
		peer.setCall();
	}

	private Risk checkWaveRisk(Wave<Data> wave, int orbitDirection, double maxVelocity) {
		/*
		 * Simplify the output to our direction chooser
		 */
		Risk waveRisk = new Risk();
		waveRisk.orbitDirection = orbitDirection;
		waveRisk.maxVelocity = maxVelocity;

		/*
		 * Create a simulator for our movement
		 */
		Simulate sim = createSimulator();
		sim.maxVelocity = maxVelocity;
		sim.direction = orbitDirection;

		/*
		 * Used for our distance danger
		 */
		double currentDistance = wave.distance(sim.position);
		double predictedDistance = 0;
		int intersected = 0;

		long timeOffset = 0;

		/*
		 * Reset the factors, so we do not get cross risk check contamination
		 */
		wave.resetFactors();

		/*
		 * Since our radar is limited to 1200 and the slowest bullet moves at
		 * 11 per second, calculating past 110 is pointlessly impossible, so
		 * it is a safe number to use as our break point.
		 */
		while(timeOffset < 110) {
			//DRAW DANGER MOVEMENT
			g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6);

			if(wave.doesIntersect(sim.position, time+timeOffset)) {
				++intersected;
				predictedDistance += wave.distance(sim.position);
			} else if(intersected > 0) {
				predictedDistance /= intersected;

				waveRisk.risk += checkIntersectionRisk(wave,predictedDistance);
				break;
			}

			checkMove(sim,wave,orbitDirection);
			sim.step();
			sim.maxVelocity = maxVelocity;

			++timeOffset;
		}

		if(intersected > 0) {
			double distanceRisk = currentDistance / predictedDistance;
			distanceRisk *= distanceRisk;

			//distanceRisk = (distanceRisk + 1.0) / 2.0;
			//System.out.println(distanceRisk);

			waveRisk.risk *= distanceRisk;
		}


		return waveRisk;
	}

	private double checkIntersectionRisk(Wave<Data> wave, double predicted) {
		double risk = 0;
		double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0;

		double factorRange = Tools.abs(wave.maxFactor - wave.minFactor);
		double bandwidth = factorRange / Tools.abs(wave.escapeAngle);

		List<Entry<Data>> list = tree.nearestNeighbor(wave.data.getWeightedDataArray(),
				NearestNeighborsToUseInSurfing, false);

		for(Entry<Data> e : list) {
			Data data = e.value;

			double entryRisk = 0.1/(1.0+Tools.abs(data.guessfactor-factorCenter));

			if(wave.minFactor < data.guessfactor && wave.maxFactor > data.guessfactor) {
				entryRisk += 0.9;
			}

			double timeWeight = 1.0+AverageSingleRoundTime/(double)(globalTime-data.globalTime);

			//kernel calculation neh?

			double density = 0;

			for(Entry<Data> ex : list) {
				double ux = (e.value.guessfactor-data.guessfactor) / bandwidth;
				double sw = ex.value.weight / (1.0+ex.distance);
				density += Math.exp(-0.5*ux*ux)*sw;
			}

			//System.out.println(density);

			double weight = (data.weight / (1.0+e.distance)) * timeWeight * density;

			//System.out.println(weight);
			//System.out.println(entryRisk);

			risk += 1.0-(1.0/(1.0+weight*entryRisk));
		}

		return risk/NearestNeighborsToUseInSurfing;
	}

	private void doWavelessMovement() {
		final int initialTurns = (int)Math.ceil(3.0/coolingRate+4.0);
		double safeTurns = status.getGunHeat()/coolingRate;

		if(time < initialTurns) {
			/* Do we have enough time to move around before they can start firing? */
			if(safeTurns > TimeBeforeFirstWaveToStartTurning) {
				doMinimalRiskMovement();
			} else {
				Move data = predictMove(myPosition,lastEnemyPosition,
						status.getHeadingRadians(),status.getVelocity(),orbitDirection);
				peer.setTurnBody(data.angleToTurn);
				peer.setMaxVelocity(0);

				peer.setMove(0);
				//peer.setTurnBody(0);
				peer.setMaxVelocity(0);
			}
		} else {
			/*
			 * Things are no longer safe, set our movement to `sane`
			 * 
			 * TODO: RAM if enemy is disabled
			 */
			if(status.getOthers() == 0) {
				/*
				 * No bullets in the air, enemy dead, we can do our victory DANCE (YAY!)
				 */
				doVictoryDance();
			} else {
				//Some other thing to do when nothing is in the air??
				doMinimalRiskMovement();
			}
		}
	}

	protected void doMinimalRiskMovement() {
		double heading = status.getHeadingRadians();
		double velocity = status.getVelocity();

		Rectangle escapeField = new Rectangle(30,30,fieldWidth-60,fieldHeight-60);
		g.setColor(Color.WHITE);
		g.draw(escapeField.toRectangle2D());

		//Do minimal risk movement
		Vector target = myPosition.copy();
		Vector bestTarget = myPosition;
		double angle = 0;
		double bestRisk = checkWavelessRisk(bestTarget);
		double enemyDistance = myPosition.distance(lastEnemyPosition)+18;
		while(angle < Math.PI*2) {
			double targetDistance = Tools.min(200,enemyDistance);

			target.setProject(myPosition, angle, targetDistance);
			if(escapeField.contains(target)) {
				double risk = checkWavelessRisk(target);
				if(risk < bestRisk) {
					bestRisk = risk;
					bestTarget = target.copy();
				}
				g.setColor(Color.BLUE);
				g.drawRect((int)target.x-2, (int)target.y-2, 4, 4);

			}
			angle += Math.PI/32.0;
		}
		g.setColor(bodyColor);
		g.drawRect((int)bestTarget.x-2, (int)bestTarget.y-2, 4, 4);

		double travelAngle = myPosition.angleTo(bestTarget);

		double forward = myPosition.distance(bestTarget);

		double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians());
		int direction = 1;

		if(Tools.abs(angleToTurn) > Math.PI/2.0) {
			angleToTurn = Utils.normalRelativeAngle(angleToTurn - Math.PI);
			direction = -1;
		}

		//Slow down so we do not ram head long into the walls and can instead turn to avoid them
		double maxVelocity = Rules.MAX_VELOCITY;

		if(!field.contains(myPosition.projectNew(heading, velocity*3.25)))
			maxVelocity = 0;

		if(!field.contains(myPosition.projectNew(heading, velocity*5)))
			maxVelocity = 4;

		if(angleToTurn > 1.0) {
			maxVelocity = 0;
		}

		peer.setMaxVelocity(maxVelocity);
		peer.setTurnBody(angleToTurn);
		peer.setMove(forward*direction);
	}

	private double checkWavelessRisk(Vector pos) {
		double risk = lastEnemyEnergy/pos.distanceSq(lastEnemyPosition);

		for(Line edge : field.getLines()) {
			risk += 5.0/(1.0+edge.ptSegDistSq(pos));
		}

		g.setColor(Color.RED);
		/*
		 * get points between enemy location and corner and add risk!!!!
		 * these are bad places to be! Our hitbox is larger here if nothing else!
		 */
		for(Vector corner : field.getCorners()) {
			corner.add(lastEnemyPosition);
			corner.scale(0.5);
			if(corner.distanceSq(lastEnemyPosition) < 22500 /* 150 */) {
				g.drawRect((int)corner.x-2, (int)corner.y-2, 4, 4);
			}
			risk += 5.0/(1.0+corner.distanceSq(pos));
		}

		return risk;
	}

	/**
	 * Account for our bullets hitting the enemy
	 */
	@Override
	public void onBulletHit(BulletHitEvent e) {
		super.onBulletHit(e);

		/* Get the power of the bullet of ours that hit */
		double bulletPower = e.getBullet().getPower();

		/* Determine how much damage our bullet does to the enemy
		 * and adjust our stored copy to reflect the amount lost
		 */
		lastEnemyEnergy -= Rules.getBulletDamage(bulletPower);
	}

	/**
	 * Account for one of our bullets hitting an enemy bullet.
	 */

	@Override
	public void onBulletHitBullet(BulletHitBulletEvent e) {
		super.onBulletHitBullet(e);

		handleBullet(e.getHitBullet());
	}

	/**
	 * Account for one of the enemies bullets hitting us.
	 */
	@Override
	public void onHitByBullet(HitByBulletEvent e) {
		super.onHitByBullet(e);

		/*
		 * Increase the enemy energy based on how powerful the bullet that
		 * hit us was this is so we can get reliable energy drop detection
		 */
		lastEnemyEnergy += Rules.getBulletHitBonus(e.getPower());

		handleBullet(e.getBullet());
	}

	/**
	 * Handle an actual enemy bullet
	 */
	private void handleBullet(Bullet b) {
		Vector bulletPosition = new Vector(b.getX(),b.getY());

		/* Find the matching wave */
		Iterator<Wave<Data>> waveIterator = enemyWaves.iterator();
		while(waveIterator.hasNext()) {
			Wave<Data> wave = waveIterator.next();

			/* the current distance of the wave */
			double radius = wave.speed*(time - wave.fireTime);

			/* check if the power is close enough to be our wave. This
			 * margin is small, only to allow room for rounding errors
			 */
			if(Tools.abs(b.getPower()-wave.power) < 0.001) {

				/* check if the bullets distance from the center of
				 * the wave is close to our waves radius. This margin
				 * is small, only to allow room for rounding errors
				 */
				if(Tools.abs(wave.distanceSq(bulletPosition)-radius*radius) < 0.1) {
					/* FIXME extensively test this detection method */

					/* Alternate method, but why recalculate something? Could be used as an extra check. */
					/* updateTree(wave,wave.angleTo(bulletPosition)); */
					updateTree(wave,b.getHeadingRadians());

					waveIterator.remove();

					return;
				}
			}
		}

		/* If not found say so, cause someone (me) SCREWED UP!!! */
		println("Error: Unknown Bullet Collision");
		println("\tBullet:["+b.getX()+","+b.getY()+"] @ h"+b.getHeadingRadians()+" @ p" + b.getPower());
	}

	private void updateTree(Wave<Data> wave, double angleToBullet) {
		double angleOffset = Utils.normalRelativeAngle(angleToBullet - wave.directAngle);

		wave.data.guessfactor = angleOffset / wave.escapeAngle;

		//TODO
		//tree.addPoint(wave.data.getDataArray(), wave.data);
		tree.addPoint(wave.data.getWeightedDataArray(), wave.data);
	}
}


/**
 * Data gained from movement prediction
 */
class Move {
	double angleToTurn = 0;
	double maxVelocity = Rules.MAX_VELOCITY;
	int direction = 1;
}

class Risk {
	double risk;
	double maxVelocity;
	int orbitDirection;
}

public class State {
	Vector myPosition;
	Vector enemyPosition;
	Data data = new Data();
}

public class Data {
	long globalTime;

	double lateralVelocity;
	double advancingVelcity;
	double guessfactor;

	double weight = 1.0;

	public double[] getDataArray() {
		return new double[] {
				Math.abs(lateralVelocity)/8.0,
				(advancingVelcity+8.0)/16.0,
		};
	}

	public double[] getDataWeights() {
		return new double[] {
				1,
				1,
		};
	}

	public double[] getWeightedDataArray() {
		double[] data = getDataArray();
		double[] weights = getDataWeights();
		for(int i=0;i<data.length;++i) {
			data[i] *= weights[i];
		}
		return data;
	}
}

public class Wave<T> extends Vector {
	public long fireTime;
	public double escapeAngle;
	public double directAngle;
	public double speed;
	public double power;
	public boolean imaginary = false;
	
	public T data;
	
	public double getRadius(long time) {
		return speed*(time - fireTime);
	}
	
	
	public int intersected = 0;
	/**
	 * Used to determine if we can safely remove this wave
	 */
	public final boolean didIntersect(Vector target, long time) {
		hitbox.set(target, 36, 36);
		
		double radius = getRadius(time);
		double nextRadius = getRadius(time+1);
		Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
		Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
		
		if(current.length != 0 || next.length != 0) {
			++intersected;
		} else {
			if(intersected > 0) {
				return true;
			}
		}
		
		return false;
	}
	
	public static Rectangle hitbox = new Rectangle();
	/**
	 * Used for calculating where a bullet will intersect a target
	 */
	public final boolean doesIntersect(Vector target, long time) {
		hitbox.set(target, 36, 36);
		
		double radius = getRadius(time);
		double nextRadius = getRadius(time+1);
		Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
		Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
		
		if(current.length != 0 || next.length != 0) {
			for(Vector v : current)
				expandFactors(v);
			
			for(Vector v : next)
				expandFactors(v);
			
			Vector[] corners = hitbox.getCorners();
			for(Vector v : corners) {
				double distance = distanceSq(v); 
				if(distance < nextRadius*nextRadius
				&& distance > radius*radius) {
					expandFactors(v);
				}
			}
			
			return true;
		}
		
		return false;
	}
	
	public double minFactor = Double.POSITIVE_INFINITY;
	public double maxFactor = Double.NEGATIVE_INFINITY;
	
	/**
	 * Expand the guessfactors based on target location
	 */
	private void expandFactors(Vector pos) {
		double angle = Utils.normalRelativeAngle(angleTo(pos) - directAngle) / escapeAngle;
		if(angle < minFactor) minFactor = angle;
		if(angle > maxFactor) maxFactor = angle;
	}
	
	/**
	 * Reset our factors for calculating position (important!)
	 */
	public void resetFactors() {
		minFactor = Double.POSITIVE_INFINITY;
		maxFactor = Double.NEGATIVE_INFINITY;
	}
}