PastFuture/Code
Jump to navigation
Jump to search
// (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());
}
}