package chase.pm;
import robocode.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import static java.lang.Math.*;
import static robocode.util.Utils.*;
/**
* A simple demonstration of a one on one
* pattern matching robot.
*
* @author Chase
*/
public class PatternBot extends AdvancedRobot {
//the deepest backward trace to determine the best match
private static final int MAX_MATCH_LENGTH = 50;
//Limit the size of the pattern, so that it doesn't get to slow
private static final int MAX_PATTERN_LENGTH = 10000;
//the maximum angle difference between heading delta's to be considered a match
private static final double MAX_ANGLE_DIFF = PI/64; //== 2.8125 degrees
//the maximum velocity difference between velocity delta's to be considered a match
private static final double MAX_VELOCITY_DIFF = 1;
private static int patternLength = 0;
private static Rectangle2D.Double battlefield;
private static State head, tail;
private double lastHeading, lastVelocity;
private Point2D.Double nextPredicted;
private ArrayList<Point2D.Double> bestList;
public void run() {
//adjust for all the turns, by way of hierarchy
//this adjusts the radar for robot turn aswell
setAdjustGunForRobotTurn(true);
setAdjustRadarForGunTurn(true);
//some nice flamy red colors
setColors(Color.red, Color.red, Color.red);
//Add a break to the pattern, so that we don't
//track patterns that never happened
addBreak();
lastVelocity = 100;
//setup the battlefield for later
if(battlefield == null) {
battlefield = new Rectangle2D.Double(18,18,
getBattleFieldWidth()-36,
getBattleFieldHeight()-36);
}
//spin the radar
do {
turnRadarRightRadians(Double.POSITIVE_INFINITY);
} while(true);
}
public void onScannedRobot(ScannedRobotEvent e) {
//This is the direct angle to the enemy
double eAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
//since this is one on one robot, reset the radar
//we do this first as the lag may cause the robot
//to skip a turn, this way we don't have broken scans
//this is an advanced no-slip one on one radar
double radar = normalRelativeAngle(eAbsoluteBearing - getRadarHeadingRadians());
double arcToScan = atan(36.0 / e.getDistance());
radar += (radar < 0) ? -arcToScan : arcToScan;
setTurnRadarRightRadians(radar);
//Initialize some more variable
double eHeading = e.getHeadingRadians();
double eVelocity = e.getVelocity();
double eDistance = e.getDistance();
Point2D.Double myLocation = new Point2D.Double(getX(), getY());
Point2D.Double eLocation = project(myLocation,eAbsoluteBearing,eDistance);
if(lastVelocity == 100) {
lastHeading = eHeading;
lastVelocity = eVelocity;
return;
}
//record the latest data into the pattern
addState(eHeading, eVelocity);
//check and eliminate extra states over the limit
while(patternLength > MAX_PATTERN_LENGTH) pruneTail();
//update the last heading and velocity
lastHeading = eHeading;
lastVelocity = eVelocity;
//check to see if we need to fire this tick
if (getGunHeat()/getGunCoolingRate() < 5) {
//determine the bullet power and speed
double bulletPower = 3;
double bulletSpeed = 20-3*bulletPower;
//now for the meat of the gun, we scan from the head
State state = head.reverse.reverse; //atleast 2 back
int deepestDepth = 2; //to save time
Point2D.Double predictedLocation = null;
while(state.reverse != null) {
if(!state.isBreak && matchState(state, head)) {
State state2 = state.reverse;
State recent = head.reverse;
int depth = 1; //could be 0 aswell
//check to see if the state is a match for the current state
while(matchState(state2,recent) && depth <= MAX_MATCH_LENGTH) {
depth++;
state2 = state2.reverse;
recent = recent.reverse;
}
//now play it forward to determine where to aim, but
//only if the depth was deeper then the deepest
if(depth > deepestDepth) {
state2 = state.forward;
Point2D.Double predict = eLocation;
double bulletDistance = eDistance;
double heading = eHeading;
double velocity = eVelocity;
ArrayList<Point2D.Double> list = new ArrayList<Point2D.Double>();
while(battlefield.contains(predict) && bulletDistance > 0
&& state2 != null && !state2.isBreak) {
bulletDistance -= bulletSpeed;
heading = normalRelativeAngle(heading + state2.headingDelta);
velocity = max(-8, min(8,velocity + state2.velocityDelta));
predict = project(predict,heading,velocity);
list.add(predict);
state2 = state2.forward;
}
if(battlefield.contains(predict) && bulletDistance < 1) {
bestList = list;
deepestDepth = depth;
predictedLocation = predict;
}
}
}
state = state.reverse;
}
nextPredicted = predictedLocation;
double aimAngle = eAbsoluteBearing;
if(predictedLocation != null)
aimAngle = absoluteAngle(myLocation, predictedLocation);
double angle = normalRelativeAngle(aimAngle-getGunHeadingRadians());
setTurnGunRightRadians(angle);
setFire(bulletPower);
}
}
public void onPaint(Graphics2D g) {
g.setColor(Color.white);
if(bestList != null) {
Point2D.Double last = null;
for(int i=0;i<bestList.size();i++) {
Point2D.Double c = bestList.get(i);
if(last == null) {
g.setColor(Color.red);
g.fillOval((int)c.x-4, (int)c.y-4, 8, 8);
g.setColor(Color.white);
} else {
g.drawLine((int)last.x, (int)last.y, (int)c.x, (int)c.y);
}
last = c;
}
}
if(nextPredicted != null) {
g.drawString("Next Point: ("+(float)nextPredicted.x
+","+(float)nextPredicted.y+")", 0, 5);
g.setColor(Color.GREEN);
g.fillOval((int)nextPredicted.x-4,
(int)nextPredicted.y-4, 8, 8);
} else {
g.drawString("Next Point: null", 0, 5);
}
g.setColor(Color.white);
g.drawString("Pattern Length: " + patternLength, 0, 20);
}
public boolean matchState(State s1, State s2) {
if(s1 != null && s2 != null &&
abs(s1.headingDelta - s2.headingDelta) <= MAX_ANGLE_DIFF
&& abs(s1.velocityDelta - s2.velocityDelta) <= MAX_VELOCITY_DIFF) {
return true;
}
return false;
}
public void pruneTail() {
State temp = tail;
tail = tail.forward;
tail.reverse = null;
temp.forward = null;
patternLength--;
}
public void addState(double heading, double velocity) {
patternLength++;
State s = new State();
s.headingDelta = normalRelativeAngle(heading - lastHeading);
s.velocityDelta = velocity - lastVelocity;
_addState(s);
}
public void addBreak() {
patternLength++;
State s = new State();
s.isBreak = true;
if(head == null) {
head = s;
} else {
_addState(s);
}
}
private void _addState(State s) {
State tmp = head;
head = s;
head.reverse = tmp;
tmp.forward = head;
if(tail == null) {
tail = tmp;
}
}
public static final Point2D.Double project(Point2D.Double l, double a, double d){
return new Point2D.Double(l.x + d*Math.sin(a), l.y + d*Math.cos(a));
}
public static final double absoluteAngle(Point2D source, Point2D target) {
return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY());
}
}
//only in the same file for completeness
class State {
double headingDelta, velocityDelta;
boolean isBreak;
State reverse, forward;
public State() {
isBreak = false;
}
}
I'll post some challenge scores for it in a bit. --Chase-san
Also if someone can tell me while it sometimes misses SpinBot when it aims at a left or right edge (relatively) of SpinBots? circle, that the bullets just misses as SpinBot drives away (dispite a hour of debugging I couldn't come up with a solution). --Chase-san
It sounds like it might be a 1-off problem. But to me your movement-rebuilding code seems horribly, and unnecessarily complex. Take a look in Waylander for a REALLY simple way to rebuild the movement. -- Skilgannon
I just took a thorough look through your rebuilding code, and I can see the problem. Rather than seeing whether the bullet would have traveled as far as the enemy in that amount of time, you presume that the enemy will be at the same distance as they are currently, and then subtract from that. What this doesn't factor in is if the enemy is moving away or towards you, that this will change the bullet flight time. So rather than subtracting from bulletDistance, initialize it to zero, and add to it each time. When the distance from your current location to the predicted enemy location is smaller than the bulletDistance, then break the loop. Another point: it would probably be quicker to find the longest match, and only then rebuild the movement. Otherwise you will rebuild a movement every single time you find a longer match than you had before. -- Skilgannon