/**
*
*/
package rampancy.standard;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.*;
import rampancy.*;
import rampancy.util.*;
import rampancy.util.movement.MovSim;
import rampancy.util.movement.MovSimStat;
import rampancy.util.movement.RMoveChoice;
import rampancy.util.wave.REnemyWave;
import rampancy.util.wave.REnemyWaveWithStats;
import robocode.util.Utils;
/**
* @author Matthew Chun-Lum
* Movement strategy:
* --------------------------------
* Identify the nearest wave
* Simulate movement in either direction
* Select the best location
*
*/
public class RDefaultMovementManager implements RMovementManager {
public static final double MIN_DISTANCE = 73.875;
public static final double MIN_WAVE_ORBIT_DISTANCE = 230;
public static final double MAX_ORBIT_DISTANCE = 475;
public static final Stroke ABSOLUTE_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 10 }, 0);
public static final Stroke MIN_ORBIT_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 20 }, 0);
public static final Stroke MAX_ORBIT_STROKE = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 20 }, 0);
public static final Color ABSOLUTE_STROKE_COLOR = Color.red;
public static final Color MIN_ORBIT_STROKE_COLOR = Color.yellow;
public static final Color MAX_ORBIT_STROKE_COLOR = new Color(0xA65CB5);
public static final Color CW_COLOR = new Color(0, 0, 255);
public static final Color CCW_COLOR = new Color(0, 200, 200);
public static final int CW = 1;
public static final int CCW = -1;
private ArrayList<RMoveChoice> examinedMovementChoices;
private MoveStruct lastMoveStruct;
private RampantRobot reference;
private int direction;
private REnemyWave lastWave;
protected Ellipse2D.Double absoluteDangerZone;
protected Ellipse2D.Double minOrbitZone;
protected Ellipse2D.Double maxOrbitZone;
/**
* Constructor
*/
public RDefaultMovementManager(RampantRobot reference) {
examinedMovementChoices = new ArrayList<RMoveChoice>();
this.reference = reference;
direction = 1;
}
public void updateReference(RampantRobot reference) {
this.reference = reference;
}
/* (non-Javadoc)
* @see rampancy_v2.RMovementManager#computeBestMove()
*/
public RMoveChoice computeBestMove(REnemyRobot enemy) {
REnemyWave enemyWave = RampantRobot.getWaveManager().getClosestEnemyWave(reference.getLocation());
REnemyWave secondWave = null;
if(RampantRobot.getWaveManager() instanceof RDefaultWaveManager) {
secondWave = ((RDefaultWaveManager) RampantRobot.getWaveManager()).getNthClosestEnemyWave(reference.getLocation(), 2);
}
if(enemyWave == null) {
enemyWave = new REnemyWaveWithStats(enemy);
}
examinedMovementChoices.clear();
lastWave = enemyWave;
MoveStruct moveStruct = new MoveStruct(reference, enemy, enemyWave, secondWave);
simulateMove(moveStruct);
if(examinedMovementChoices.isEmpty()) {
return null;
}
lastMoveStruct = moveStruct;
computeDangerForMoveChoices(moveStruct);
sortMoveChoices();
setDangerColors();
direction = examinedMovementChoices.get(0).direction;
examinedMovementChoices.get(0).color = Color.pink;
return examinedMovementChoices.get(0);
}
/* (non-Javadoc)
* @see rampancy.RMovementManager#draw(java.awt.Graphics2D)
*/
public void draw(Graphics2D g) {
if(examinedMovementChoices == null || examinedMovementChoices.isEmpty())
return;
for(RMoveChoice moveChoice : examinedMovementChoices)
moveChoice.draw(g);
RPoint enemyLocation = lastMoveStruct.enemy.getCurrentState().location;
absoluteDangerZone = new Ellipse2D.Double(enemyLocation.x - MIN_DISTANCE,
enemyLocation.y - MIN_DISTANCE,
MIN_DISTANCE * 2,
MIN_DISTANCE * 2);
minOrbitZone = new Ellipse2D.Double(enemyLocation.x - MIN_WAVE_ORBIT_DISTANCE,
enemyLocation.y - MIN_WAVE_ORBIT_DISTANCE,
MIN_WAVE_ORBIT_DISTANCE * 2,
MIN_WAVE_ORBIT_DISTANCE * 2);
maxOrbitZone = new Ellipse2D.Double(enemyLocation.x - MAX_ORBIT_DISTANCE,
enemyLocation.y - MAX_ORBIT_DISTANCE,
MAX_ORBIT_DISTANCE * 2,
MAX_ORBIT_DISTANCE * 2);
Stroke stroke = g.getStroke();
g.setColor(ABSOLUTE_STROKE_COLOR);
g.setStroke(ABSOLUTE_STROKE);
g.draw(absoluteDangerZone);
g.setColor(MIN_ORBIT_STROKE_COLOR);
g.setStroke(MIN_ORBIT_STROKE);
g.draw(minOrbitZone);
g.setColor(MAX_ORBIT_STROKE_COLOR);
g.setStroke(MAX_ORBIT_STROKE);
g.draw(maxOrbitZone);
g.setStroke(stroke);
double distance = lastMoveStruct.enemy.getCurrentState().distance;
RPoint midpoint = enemyLocation.projectTo(enemyLocation.computeAbsoluteBearingTo(reference.getLocation()), distance / 2);
// draw distance line
if(distance < MIN_WAVE_ORBIT_DISTANCE) {
g.setColor(Color.yellow);
} else if(distance < MAX_ORBIT_DISTANCE) {
g.setColor(Color.DARK_GRAY);
} else {
g.setColor(MAX_ORBIT_STROKE_COLOR);
}
g.draw(new Line2D.Double(reference.getLocation(), enemyLocation));
g.setColor(Color.white);
g.drawString(RUtil.roundToPrecision(distance, 1) + "", (int) midpoint.x, (int) midpoint.y);
}
/**
* Simulate the complete movement for the given wave
* @param moveStruct
*/
private void simulateMove(MoveStruct moveStruct) {
MoveProfile startProfileCW = new MoveProfile(reference.getCopyOfLocation(), -5000, reference.getHeadingRadians(),
reference.getHeadingRadians(), reference.getVelocity(), 8.0, CW, 0);
startProfileCW.initialOrbitAngle = computeOrbitAngle(startProfileCW, moveStruct);
MoveProfile startProfileCCW = startProfileCW.getCopy();
startProfileCCW.direction = CCW;
startProfileCCW.initialOrbitAngle = computeOrbitAngle(startProfileCCW, moveStruct);
recursiveSimulateMove(CW, startProfileCW, moveStruct);
recursiveSimulateMove(CCW, startProfileCCW, moveStruct);
}
/**
* Recursively simulate movement in the given direction
* @param direction
* @param currentProfile
* @param moveStruct
*/
private void recursiveSimulateMove(int direction, MoveProfile currentProfile, MoveStruct moveStruct) {
if(!moveStruct.wave.intercepted(currentProfile.fromLocation, currentProfile.futureTicks)) {
//simulateStop(currentProfile, moveStruct);
simulateMoveDirection(direction, currentProfile, moveStruct);
}
}
private void simulateMoveDirection(int direction, MoveProfile currentProfile, MoveStruct moveStruct) {
MoveProfile moveProfile = currentProfile.getCopy();
moveProfile.desiredHeading = computeOrbitAngle(currentProfile, moveStruct);
MoveProfile predictedProfile = predictPosition(moveProfile);
logLocation(predictedProfile, moveStruct);
recursiveSimulateMove(direction, predictedProfile, moveStruct);
}
private void logLocation(MoveProfile currentProfile, MoveStruct moveStruct) {
double distance = currentProfile.fromLocation.distance(moveStruct.wave.getOrigin());
double danger = 0;
double guessFactor = RUtil.getFactorIndex(moveStruct.wave, currentProfile.fromLocation, RDefaultSurfingSegmentArray.NUM_GUESS_FACTORS);
RMoveChoice moveChoice = new RMoveChoice(currentProfile.fromLocation, currentProfile.initialOrbitAngle, distance, currentProfile.maxVelocity,
danger, guessFactor, currentProfile.futureTicks, currentProfile.direction);
examinedMovementChoices.add(moveChoice);
}
private double computeOrbitAngle(MoveProfile currentProfile, MoveStruct moveStruct) {
RPoint orbitLocation = moveStruct.wave.getOrigin();
double distanceToCenterOfOrbit = orbitLocation.distance(currentProfile.fromLocation);
double absB = RUtil.computeAbsoluteBearing(orbitLocation, currentProfile.fromLocation);
double orbitAngle = absB + Math.PI/2 * currentProfile.direction;
double attackAngle = computeAttackAngle(currentProfile, moveStruct) + orbitAngle;
double wallSmoothedAngle = RUtil.wallSmoothing(currentProfile.fromLocation,
attackAngle, currentProfile.direction, distanceToCenterOfOrbit);
return Utils.normalAbsoluteAngle(wallSmoothedAngle);
}
private double computeAttackAngle(MoveProfile currentProfile, MoveStruct moveStruct) {
double distanceToEnemy = currentProfile.fromLocation.distance(moveStruct.enemy.getCurrentState().location);
double distanceToWaveOrigin = currentProfile.fromLocation.distance(moveStruct.wave.getOrigin());
if(distanceToEnemy < MIN_DISTANCE) {
return -(Math.PI / 4) * currentProfile.direction;
} else if(distanceToEnemy < MAX_ORBIT_DISTANCE) {
if(distanceToEnemy < moveStruct.enemyDistance && moveStruct.enemy.getCurrentState().directionTraveling != currentProfile.direction) {
// we're on a collision course with the enemy
// adjust our oribt to prevent the collsion
return -(Math.PI / 10) * currentProfile.direction;
}
return 0;
} else {
return (Math.PI / 8) * currentProfile.direction;
}
}
private void computeDangerForMoveChoices(MoveStruct moveStruct) {
REnemyWaveWithStats statWave = null;
REnemyWaveWithStats secondStatWave = null;
if(moveStruct.wave instanceof REnemyWaveWithStats) {
statWave = (REnemyWaveWithStats) moveStruct.wave;
}
// if(moveStruct.secondWave != null && moveStruct.secondWave instanceof REnemyWaveWithStats) {
// secondStatWave = (REnemyWaveWithStats) moveStruct.secondWave;
// }
for(RMoveChoice moveChoice : examinedMovementChoices) {
double danger = statWave.getDangerForLocation(moveChoice.destination, RDefaultSurfingSegmentTree.NUM_BINS);
if(secondStatWave != null) {
danger += secondStatWave.getDangerForLocation(moveChoice.destination, RDefaultSurfingSegmentTree.NUM_BINS);
}
double dist = moveChoice.destination.distance(moveStruct.enemy.getCurrentState().location);
if(dist < 200) {
danger += (1.0 - dist/200.0) * 100;
}
moveChoice.danger += danger;
// for(RMoveChoice subMove : examinedMovementChoices) {
// if(!moveChoice.equals(subMove) && subMove.destination.getBotRect().contains(moveChoice.destination)) {
// subMove.danger += danger;
// }
// }
}
}
private void sortMoveChoices() {
Collections.sort(examinedMovementChoices, new Comparator<RMoveChoice>() {
public int compare(RMoveChoice o1, RMoveChoice o2) {
return RUtil.sign(o1.danger - o2.danger);
}
});
}
private void setDangerColors() {
double largest = examinedMovementChoices.get(examinedMovementChoices.size() - 1).danger;
for(RMoveChoice moveChoice : examinedMovementChoices) {
Color dangerColor = new Color(0, 0, 200);
if(moveChoice.danger > 0.01) {
dangerColor = new Color((int) (155 * (moveChoice.danger / largest)) + 100, 0, 0);
}
moveChoice.color = dangerColor;
}
}
/*
* Accurately predicts the future position
* Credit goes to Albert for the MovSim part
*/
private MoveProfile predictPosition(MoveProfile currentProfile) {
double angleToTurn = currentProfile.desiredHeading - currentProfile.currentHeading;
int moveDirection = 1;
if(Math.cos(angleToTurn) < 0) {
angleToTurn += Math.PI;
moveDirection = -1;
}
RPoint fromLocation = currentProfile.fromLocation;
angleToTurn = Utils.normalRelativeAngle(angleToTurn);
RBattlefield bf = RampantRobot.getGlobalBattlefield();
MovSimStat[] nextTick = MovSim.futurePos(1, fromLocation.x, fromLocation.y,
currentProfile.currentVelocity, currentProfile.maxVelocity,
currentProfile.currentHeading, 1000 * moveDirection, angleToTurn,
MovSim.defaultMaxTurnRate, bf.width, bf.height);
MovSimStat sim = nextTick[0];
return new MoveProfile(new RPoint(sim.x, sim.y), currentProfile.initialOrbitAngle, sim.h, sim.h, sim.v,
currentProfile.maxVelocity, currentProfile.direction, currentProfile.futureTicks + 1);
}
}
class MoveStruct {
public MoveStruct(RampantRobot reference, REnemyRobot enemy,
REnemyWave wave, REnemyWave secondWave) {
this.reference = reference;
this.enemy = enemy;
this.wave = wave;
this.secondWave = secondWave;
this.enemyDistance = reference.getLocation().distance(enemy.getCurrentState().location);
this.waveDistance = reference.getLocation().distance(wave.getOrigin());
}
public RampantRobot reference;
public REnemyRobot enemy;
public REnemyWave wave;
public REnemyWave secondWave;
public double enemyDistance;
public double waveDistance;
}
class MoveProfile {
public MoveProfile(RPoint fromLocation, double initialOrbitAngle, double currentHeading,
double desiredHeading, double currentVelocity,
double maxVelocity, int direction, int futureTicks) {
this.fromLocation = fromLocation;
this.initialOrbitAngle = initialOrbitAngle;
this.currentHeading = currentHeading;
this.desiredHeading = desiredHeading;
this.currentVelocity = currentVelocity;
this.maxVelocity = maxVelocity;
this.direction = direction;
this.futureTicks = futureTicks;
}
public MoveProfile getCopy() {
return new MoveProfile(fromLocation.getCopy(), initialOrbitAngle, currentHeading,
desiredHeading, currentVelocity, maxVelocity, direction, futureTicks);
}
public RPoint fromLocation;
public double initialOrbitAngle;
public double currentHeading;
public double desiredHeading;
public double currentVelocity;
public double maxVelocity;
public int direction;
public int futureTicks;
}