// (C) 2007 Kim, Taegyoon // StatisticLogTargeting // version 1.0: 20071023. matches only on fire times // version 1.0b: 20071024. matches always package stelo; import robocode.*; import robocode.util.Utils; import java.awt.geom.*; // for Point2D's import java.util.*; import java.awt.*; public class PastFuture extends AdvancedRobot { public static int BINS = 47; private static int DISTANCE_INDEXES = 5; private static int VELOCITY_INDEXES = 5; // public static double _surfStats[] = new double[BINS]; // we'll use 47 bins private static double _surfStats1[][] = new double[VELOCITY_INDEXES][BINS]; private static double _surfStats2[][][] = new double[VELOCITY_INDEXES][DISTANCE_INDEXES][BINS]; public static Point2D.Double _myLocation; // our bot's location public static Point2D.Double _enemyLocation; // enemy bot's location public ArrayList _enemyWaves; public ArrayList _surfDirections; public ArrayList _surfAbsBearings; private static double lateralDirection; private static double lastEnemyVelocity; // This is a rectangle that represents an 800x600 battle field, // used for a simple, iterative WallSmoothing method (by Kawigi). // If you're not familiar with WallSmoothing, the wall stick indicates // the amount of space we try to always have on either end of the tank // (extending straight out the front or back) before touching a wall. public static final Rectangle2D.Double _fieldRect = new java.awt.geom.Rectangle2D.Double(18, 18, 764, 564); private static double lastEnemyEnergy; private static double enemyBulletPower = 0.1; private static double lastMyVelocity; private static Vector velocities = new Vector(10000); private static Vector headingChanges = new Vector(10000); private static final int SLT_VELOCITY_INDEXES = 8 + 8 + 1; private static final int SLT_VELOCITY_INDEXES2 = 8 + 1; // different segmentations private static Vector[][] fireTimes2 = new Vector[SLT_VELOCITY_INDEXES][SLT_VELOCITY_INDEXES2]; private static Vector[] fireTimes1 = new Vector[SLT_VELOCITY_INDEXES]; private static Vector fireTimes0 = new Vector(); private static double MAX_ESCAPE_ANGLE = 0.9; private static double lastEnemyHeading; private static double enemyDistance; private static double absBearing; private static double lastBearingOffset; private static int vIndex1, vIndex2; private static int hitRobotCount; public void run() { setColors(Color.GREEN, Color.YELLOW, Color.RED); lateralDirection = 1; lastEnemyVelocity = 0; _enemyWaves = new ArrayList(); _surfDirections = new ArrayList(); _surfAbsBearings = new ArrayList(); if (getRoundNum() == 0) { for (int i = 0; i < SLT_VELOCITY_INDEXES; i++) { fireTimes1[i] = new Vector(); for (int j = 0; j < SLT_VELOCITY_INDEXES2; j++) { fireTimes2[i][j] = new Vector(); } } } setAdjustGunForRobotTurn(true); setAdjustRadarForGunTurn(true); do { // basic mini-radar code turnRadarRightRadians(Double.POSITIVE_INFINITY); } while (true); } public void onScannedRobot(ScannedRobotEvent e) { _myLocation = new Point2D.Double(getX(), getY()); double lateralVelocity = getVelocity()*Math.sin(e.getBearingRadians()); absBearing = e.getBearingRadians() + getHeadingRadians(); enemyDistance = e.getDistance(); double enemyVelocity = e.getVelocity(); double myVelocity = getVelocity(); setTurnRadarRightRadians(Utils.normalRelativeAngle(absBearing - getRadarHeadingRadians()) * 2); { velocities.add(new Double(e.getVelocity())); headingChanges.add(new Double(Utils.normalRelativeAngle(e.getHeadingRadians() - lastEnemyHeading))); } _surfDirections.add(0, new Integer((lateralVelocity >= 0) ? 1 : -1)); _surfAbsBearings.add(0, new Double(absBearing + Math.PI)); // check energyDrop double energyDrop = lastEnemyEnergy - e.getEnergy(); if (energyDrop < 3.01 && energyDrop > 0.09 && _surfDirections.size() > 2) { enemyBulletPower = energyDrop; // EnemyWave ew = new EnemyWave(); EnemyWave ew = new EnemyWave(this.getVelocity(), enemyDistance); ew.fireTime = getTime() - 1; ew.bulletVelocity = bulletVelocity(enemyBulletPower); ew.distanceTraveled = bulletVelocity(enemyBulletPower); ew.direction = ((Integer)_surfDirections.get(2)).intValue(); ew.directAngle = ((Double)_surfAbsBearings.get(2)).doubleValue(); ew.fireLocation = (Point2D.Double)_enemyLocation.clone(); // last tick _enemyWaves.add(ew); if (Math.random() < 0.5) direction = -direction; } updateWaves(); doSurfing(); // update after EnemyWave detection, because that needs the previous // enemy location as the source of the wave _enemyLocation = (Point2D.Double) project(_myLocation, absBearing, e.getDistance()); // System.out.println(_enemyLocation.getX() + " " + _enemyLocation.getY()); double bulletPower = 1.9; if (e.getDistance() < 55) bulletPower = 3.0; bulletPower = Math.min(getEnergy(), Math.min(bulletPower, e.getEnergy() / 5.0)); bulletPower = limit(0.1, bulletPower, 3.0); if (getGunHeat() < getGunCoolingRate() * 4.0) { lastBearingOffset = bestBearingOffset(e, bulletPower, absBearing, vIndex1, vIndex2); Bullet b = setFireBullet(bulletPower); // if (b != null) { vIndex1 = (int) (enemyVelocity + 8.0); vIndex2 = (int) Math.abs(lastEnemyVelocity); fireTimes2[vIndex1][vIndex2].add(new Integer(velocities.size() - 1)); fireTimes1[vIndex1].add(new Integer(velocities.size() - 1)); fireTimes0.add(new Integer(velocities.size() - 1)); // } } setTurnGunRightRadians(Utils.normalRelativeAngle(absBearing - getGunHeadingRadians() + lastBearingOffset)); setTurnRadarRightRadians(Utils.normalRelativeAngle(absBearing - getRadarHeadingRadians()) * 2); lastEnemyHeading = e.getHeadingRadians(); lastEnemyVelocity = enemyVelocity; lastEnemyEnergy = e.getEnergy(); lastMyVelocity = getVelocity(); } public void onPaint(java.awt.Graphics2D g) { g.draw(new Rectangle((int) getX() - 18, (int) getY() - 18, 36, 36)); EnemyWave surfWave = null; for (int x = 0; _enemyWaves != null && x < _enemyWaves.size(); x++) { EnemyWave ew = (EnemyWave)_enemyWaves.get(x); double eX = ew.fireLocation.getX() + ew.distanceTraveled * Math.sin(ew.directAngle); double eY = ew.fireLocation.getY() + ew.distanceTraveled * Math.cos(ew.directAngle); g.draw(new Rectangle((int) eX - 8, (int) eY - 8, 16, 16)); int maxBin = (BINS - 1) / 2; for (int i = 0; i < BINS; i++) { if (ew.buffer[EnemyWave.BUFFERS - 1][i] > ew.buffer[EnemyWave.BUFFERS - 1][maxBin]) { maxBin = i; } } double angle = ew.directAngle + maxEscapeAngle(ew.bulletVelocity) * ew.direction * (double) ((maxBin - (BINS - 1)) / 2) / ((BINS - 1) / 2); eX = ew.fireLocation.getX() + ew.distanceTraveled * Math.sin(angle); eY = ew.fireLocation.getY() + ew.distanceTraveled * Math.cos(angle); g.draw(new Rectangle((int) eX - 4, (int) eY - 4, 8, 8)); } } public void onHitRobot(HitRobotEvent e) { hitRobotCount++; considerRamming(); } private void considerRamming() { if (enemyDistance < 55 && hitRobotCount > (getRoundNum() + 1) * 3) { setFire(3.0); goTo(_enemyLocation); } } private static double direction = 1.0; private void randomMove() { Point2D robotDestination; double angle = Math.PI / 2.0; do { robotDestination = project(_myLocation, absBearing + direction * angle, 160.0); angle -= 0.05; } while (direction != 0.0 && !_fieldRect.contains(robotDestination)); goTo(robotDestination); considerRamming(); } private void goTo(Point2D destination) { double angle = Utils.normalRelativeAngle(absoluteBearing(_myLocation, destination) - getHeadingRadians()); double turnAngle = Math.atan(Math.tan(angle)); setTurnRightRadians(turnAngle); setAhead(_myLocation.distance(destination) * (angle == turnAngle ? 1 : -1)); // Hit the brake pedal hard if we need to turn sharply // setMaxVelocity(Math.abs(getTurnRemaining()) > 30 ? 0 : 8); //setMaxVelocity(8.0 * (1.0 - Math.abs(getTurnRemaining()) / 30.0)); setMaxVelocity((Math.PI / 18 - Math.min(Math.abs(getTurnRemainingRadians()), Math.PI / 18)) / (Math.PI / 240)); } private Rectangle2D fieldRectangle(double margin) { return new Rectangle2D.Double(margin, margin, getBattleFieldWidth() - margin * 2, getBattleFieldHeight() - margin * 2); } private static final Rectangle2D.Double field = new java.awt.geom.Rectangle2D.Double(0, 0, 800, 600); private static final int MAX_SAMPLES = 500; // statistical movement reconstructor private double bestBearingOffset(ScannedRobotEvent e, double bulletPower, double absBearing, int vIndex1, int vIndex2) { double angleThreshold = 36.0 / e.getDistance(); // final double MAX_ESCAPE_ANGLE = maxEscapeAngle(bulletVelocity(bulletPower)); int binSize = (int) (MAX_ESCAPE_ANGLE * 2.0 / angleThreshold) + 1; // final int CLOSEST_SIZE = binSize * 2 + 1; final double bulletSpeed = (20.0 - 3.0 * bulletPower); final int bulletTravelTime = (int) (enemyDistance / bulletSpeed); Vector ft = fireTimes2[vIndex1][vIndex2]; if (ft.size() == 0) ft = fireTimes1[vIndex1]; if (ft.size() == 0) ft = fireTimes0; int[] statBin = new int[binSize]; double[] metaAngle = new double[binSize]; System.out.println("binSize: " + binSize + " choices: " + ft.size()); int maxIndex = 0; int lastIndex = velocities.size() - 1; int count = 0; final double heading = e.getHeadingRadians(); final double eV = e.getVelocity(); for (int fi = ft.size() - 1; fi >= 0; fi--) { count++; if (count > MAX_SAMPLES) break; // limit to recent samples int i = ((Integer) ft.get(fi)).intValue(); double initialEX = enemyDistance * Math.sin(absBearing); double initialEY = enemyDistance * Math.cos(absBearing); // reconstruct enemy movement and find the most popular angle double eX = initialEX; double eY = initialEY; double ww = heading; double v = eV; double db = 0; int index = i; boolean inField = true; do { db += bulletSpeed; eX += v * Math.sin(ww); eY += v * Math.cos(ww); if (!field.contains(new Point2D.Double(eX + _myLocation.getX(), eY + _myLocation.getY()))) { inField = false; break; } v = ((Double) velocities.get(index)).doubleValue(); ww += ((Double) headingChanges.get(index)).doubleValue(); if (index + 1 < lastIndex) { index++; } } while (db < Point2D.distance(0, 0, eX, eY)); if (inField) { double angle = Utils.normalRelativeAngle(Math.atan2(eX, eY) - absBearing); int binIndex = (int) ((angle + MAX_ESCAPE_ANGLE) / angleThreshold); metaAngle[binIndex] = angle; statBin[binIndex]++; if (statBin[binIndex] > statBin[maxIndex]) { maxIndex = binIndex; } } } return metaAngle[maxIndex]; } public void updateWaves() { for (int x = 0; x < _enemyWaves.size(); x++) { EnemyWave ew = (EnemyWave)_enemyWaves.get(x); ew.distanceTraveled = (getTime() - ew.fireTime) * ew.bulletVelocity; if (ew.distanceTraveled > _myLocation.distance(ew.fireLocation) + 50) { // logHit(ew, _myLocation); // flatten _enemyWaves.remove(x); x--; } } } public EnemyWave getClosestSurfableWave() { double closestDistance = 50000; // I juse use some very big number here EnemyWave surfWave = null; for (int x = 0; x < _enemyWaves.size(); x++) { EnemyWave ew = (EnemyWave)_enemyWaves.get(x); double distance = _myLocation.distance(ew.fireLocation) - ew.distanceTraveled; if (distance > ew.bulletVelocity && distance < closestDistance) { surfWave = ew; closestDistance = distance; } } return surfWave; } // Given the EnemyWave that the bullet was on, and the point where we // were hit, calculate the index into our stat array for that factor. public static int getFactorIndex(EnemyWave ew, Point2D.Double targetLocation) { double offsetAngle = (absoluteBearing(ew.fireLocation, targetLocation) - ew.directAngle); double factor = Utils.normalRelativeAngle(offsetAngle) / maxEscapeAngle(ew.bulletVelocity) * ew.direction; return (int)limit(0, (factor * ((BINS - 1) / 2)) + ((BINS - 1) / 2), BINS - 1); } // Given the EnemyWave that the bullet was on, and the point where we // were hit, update our stat array to reflect the danger in that area. public void logHit(EnemyWave ew, Point2D.Double targetLocation) { int index = getFactorIndex(ew, targetLocation); for (int x = 0; x < BINS; x++) { // for the spot bin that we were hit on, add 1; // for the bins next to it, add 1 / 2; // the next one, add 1 / 5; and so on... double increment = 1.0 / (Math.pow(index - x, 2) + 1); for (int b = 0; b < EnemyWave.BUFFERS; b++) { ew.buffer[b][x] += increment; } } } public void onHitByBullet(HitByBulletEvent e) { // If the _enemyWaves collection is empty, we must have missed the // detection of this wave somehow. if (!_enemyWaves.isEmpty()) { Point2D.Double hitBulletLocation = new Point2D.Double( e.getBullet().getX(), e.getBullet().getY()); EnemyWave hitWave = null; // look through the EnemyWaves, and find one that could've hit us. for (int x = 0; x < _enemyWaves.size(); x++) { EnemyWave ew = (EnemyWave)_enemyWaves.get(x); if (Math.abs(ew.distanceTraveled - _myLocation.distance(ew.fireLocation)) < 50 && Math.round(bulletVelocity(e.getBullet().getPower()) * 10) == Math.round(ew.bulletVelocity * 10)) { hitWave = ew; break; } } if (hitWave != null) { logHit(hitWave, hitBulletLocation); // We can remove this wave now, of course. _enemyWaves.remove(_enemyWaves.lastIndexOf(hitWave)); } } } public void onBulletHitBullet(BulletHitBulletEvent e) { // If the _enemyWaves collection is empty, we must have missed the // detection of this wave somehow. Bullet bullet = e.getHitBullet(); if (!_enemyWaves.isEmpty()) { Point2D.Double hitBulletLocation = new Point2D.Double( bullet.getX(), bullet.getY()); EnemyWave hitWave = null; // look through the EnemyWaves, and find one that could've hit us. for (int x = 0; x < _enemyWaves.size(); x++) { EnemyWave ew = (EnemyWave)_enemyWaves.get(x); if (Math.abs(ew.distanceTraveled - _myLocation.distance(ew.fireLocation)) < 50 && Math.round(bulletVelocity(bullet.getPower()) * 10) == Math.round(ew.bulletVelocity * 10)) { hitWave = ew; break; } } if (hitWave != null) { logHit(hitWave, hitBulletLocation); // We can remove this wave now, of course. _enemyWaves.remove(_enemyWaves.lastIndexOf(hitWave)); } } } // CREDIT: mini sized predictor from Apollon, by rozu // http://robowiki.net?Apollon public Point2D.Double predictPosition(EnemyWave surfWave, int direction) { Point2D.Double predictedPosition = (Point2D.Double)_myLocation.clone(); double predictedVelocity = getVelocity(); double predictedHeading = getHeadingRadians(); double maxTurning, moveAngle, moveDir; int counter = 0; // number of ticks in the future boolean intercepted = false; if (direction == 0) { // brake while (predictedVelocity != 0.0) { double nextVelocity = predictedVelocity - Math.signum(predictedVelocity) * 2.0; if (nextVelocity * predictedVelocity < 0.0) nextVelocity = 0.0; predictedVelocity = nextVelocity; // calculate the new predicted position predictedPosition = (Point2D.Double) project(predictedPosition, predictedHeading, predictedVelocity); } return predictedPosition; } do { moveAngle = wallSmoothing(predictedPosition, absoluteBearing(surfWave.fireLocation, predictedPosition) + (direction * (FAR_HALF_PI)), direction) - predictedHeading; moveDir = 1; if(Math.cos(moveAngle) < 0) { moveAngle += Math.PI; moveDir = -1; } moveAngle = Utils.normalRelativeAngle(moveAngle); // maxTurning is built in like this, you can't turn more then this in one tick maxTurning = Math.PI/720d*(40d - 3d*Math.abs(predictedVelocity)); predictedHeading = Utils.normalRelativeAngle(predictedHeading + limit(-maxTurning, moveAngle, maxTurning)); // this one is nice ;). if predictedVelocity and moveDir have // different signs you want to breack down // otherwise you want to accelerate (look at the factor "2") predictedVelocity += (predictedVelocity * moveDir < 0 ? 2*moveDir : moveDir); predictedVelocity = limit(-8, predictedVelocity, 8); // calculate the new predicted position predictedPosition = (Point2D.Double) project(predictedPosition, predictedHeading, predictedVelocity); counter++; if (predictedPosition.distance(surfWave.fireLocation) < surfWave.distanceTraveled + (counter * surfWave.bulletVelocity) + surfWave.bulletVelocity) { intercepted = true; } } while(!intercepted && counter < 500); return predictedPosition; } public double checkDanger(EnemyWave surfWave, int direction) { int index = getFactorIndex(surfWave, predictPosition(surfWave, direction)); int b = EnemyWave.BUFFERS - 1; for (; b >= 0; b--) { double sum = 0.0; for (int i = 0; i < BINS; i++) { sum += surfWave.buffer[b][i]; } if (sum > 0.0) break; } if (b < 0) b = 0; return surfWave.buffer[b][index]; } private static final double FAR_HALF_PI = 1.3; public void doSurfing() { EnemyWave surfWave = getClosestSurfableWave(); setMaxVelocity(8.0); if (surfWave == null) { randomMove(); return; } double leastDanger = Double.POSITIVE_INFINITY; double dangerLeft = checkDanger(surfWave, -1); double dangerRight = checkDanger(surfWave, 1); double dangerMiddle = checkDanger(surfWave, 0); double goAngle = absoluteBearing(surfWave.fireLocation, _myLocation); if (dangerLeft < dangerRight) { goAngle = wallSmoothing(_myLocation, goAngle - (FAR_HALF_PI), -1); leastDanger = dangerLeft; } else { goAngle = wallSmoothing(_myLocation, goAngle + (FAR_HALF_PI), 1); leastDanger = dangerRight; } setBackAsFront(this, goAngle); if (dangerMiddle < leastDanger) { setAhead(0.0); } considerRamming(); } // This can be defined as an inner class if you want. static class EnemyWave { Point2D.Double fireLocation; long fireTime; double bulletVelocity, directAngle, distanceTraveled; int direction; static final int BUFFERS = 3; double[][] buffer = new double[BUFFERS][]; static double[] fastBuffer = new double[BINS]; private static final double MAX_DISTANCE = 1000.0; public EnemyWave(double velocity, double distance) { int velocityIndex = (int) Math.abs(velocity / 2); // int lastVelocityIndex = (int) Math.abs(lastVelocity / 2); int distanceIndex = (int)(distance / (MAX_DISTANCE / DISTANCE_INDEXES)); buffer[2] = _surfStats2[velocityIndex][distanceIndex]; buffer[1] = _surfStats1[velocityIndex]; buffer[0] = fastBuffer; } } // CREDIT: Iterative WallSmoothing by Kawigi // - return absolute angle to move at after account for WallSmoothing // robowiki.net?WallSmoothing public double wallSmoothing(Point2D.Double botLocation, double angle, int orientation) { while (!_fieldRect.contains(project(botLocation, angle, 160))) { angle += orientation*0.05; } return angle; } public static double limit(double min, double value, double max) { return Math.max(min, Math.min(value, max)); } public static double bulletVelocity(double power) { return (20D - (3D*power)); } public static double maxEscapeAngle(double velocity) { return Math.asin(8.0/velocity); } /* public static void setBackAsFront(AdvancedRobot robot, double goAngle) { double angle = Utils.normalRelativeAngle(goAngle - robot.getHeadingRadians()); if (Math.abs(angle) > (Math.PI/2)) { if (angle < 0) { robot.setTurnRightRadians(Math.PI + angle); } else { robot.setTurnLeftRadians(Math.PI - angle); } robot.setBack(100); } else { if (angle < 0) { robot.setTurnLeftRadians(-1*angle); } else { robot.setTurnRightRadians(angle); } robot.setAhead(100); } } */ public static void setBackAsFront(AdvancedRobot robot, double angle) { angle = Utils.normalRelativeAngle(angle - robot.getHeadingRadians()); double turnAngle = Math.atan(Math.tan(angle)); robot.setTurnRightRadians(turnAngle); robot.setAhead(angle == turnAngle ? 100 : -100); } static Point2D project(Point2D sourceLocation, double angle, double length) { return new Point2D.Double(sourceLocation.getX() + Math.sin(angle) * length, sourceLocation.getY() + Math.cos(angle) * length); } static double absoluteBearing(Point2D source, Point2D target) { return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY()); } }