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