/*
* Created on Aug 4, 2003
*
*/
package jekl.mini;
import java.awt.geom.*;
import java.io.*;
import java.util.zip.*;
import robocode.*;
import robocode.util.Utils;
/**
* BlackPearl is derived from Jekyl in many ways. It also serves as a proving ground for new concepts
* that may one day find thier way into Jekyl. It is a pretty complete robot. If you use any of the code
* contained in this bot I only ask that you let me see what it is you changed to make it better. Seeing
* your whole bot would be nice but is not a requirement. I would also appreciate it if you would help me
* to learn from your experiences in return as well. If you use this please take the time to send me an
* email or an IM. -- jim
*/
public class BlackPearl extends AdvancedRobot {
private static final double MAX_STAND_OFF_DISTANCE = 500D;
private static final double MIN_STAND_OFF_DISTANCE = 400D;
private static final double MOVEMENT_DISTANCE = 21D;
private static final double GUESS_FACTORS = 25D;
private static final double MAX_SHOT_POWER = 3D;
private static final double FIRE_POWER_NUMERATOR = 1000D;
private static final double WALL_CURVE_DISTANCE = 117D; //pixels
private static final double MIN_GRAZE_ANGLE = 0.52359; //30
private static final double MAX_GRAZE_ANGLE = 2.61799; //150
//private static final double MIN_GRAZE_ANGLE = 1.0479D; //60 degrees in radians
//private static final double MAX_GRAZE_ANGLE = 2.0943D; //120 degrees in radians
private static final double BEND_ANGLE = 0.0698D; //4 degrees in radians
private static final int CLOSE_IN = -1;
private static final int BACK_OFF = 1;
private static Rectangle2D.Double field;
private static int statBuffer[][][][];
private static String targetName;
private static boolean save;
private static int hits;
private int direction = 1;
private int eDirection;
private double eEnergy, eLastEnergy = 100D;
private double eX, eY, eDelta, eAbsBearing, eVelocity, eDistance, eBearing, eLastVelocity;
private boolean flatten = (hits > 2);
private double eLastShot = 3;
private long lastReverseTime;
public void run() {
//setColors(java.awt.Color.MAGENTA, java.awt.Color.LIGHT_GRAY, java.awt.Color.RED);
field = new Rectangle2D.Double(18, 18, getBattleFieldWidth() - 36, getBattleFieldHeight() - 36);
setAdjustGunForRobotTurn(true);
setAdjustRadarForGunTurn(true);
save = (getOthers() == 1); //Only save data if we are in a 1-v-1 engagement, otherwise the data will be polluted
do {
turnRadarRightRadians(Double.POSITIVE_INFINITY);
} while(true);
}
public void onScannedRobot(ScannedRobotEvent e) {
//Gather target information
targetName = e.getName();
double latVel = (eVelocity = e.getVelocity()) * Math.sin(e.getHeadingRadians() - (eAbsBearing = Utils.normalRelativeAngle(getHeadingRadians() + (eBearing = e.getBearingRadians()))));
eX = getX() + Math.sin(eAbsBearing) * (eDistance = e.getDistance());
eY = getY() + Math.cos(eAbsBearing) * eDistance;
double accelDiff = Math.round(Math.abs(eVelocity) - Math.abs(eLastVelocity));
eDirection = (latVel >= 0 ? 1 : -1);
//Set the firePower
double firePower = Math.max(Math.min(Math.min(Math.min(getEnergy() * .2, (eEnergy = e.getEnergy()) * .25), FIRE_POWER_NUMERATOR/eDistance), MAX_SHOT_POWER), .1);
//Set the indicies for shooting
int latVelIndex = (int) (Math.abs(latVel) / 3);
int accellIndex = (accelDiff == 0 ? 0 : (accelDiff > 0 ? 2 : 1));
int distanceIndex = (int)(Math.min(11, (eDistance / 100D)));
//Movement
double turnTo = getTurnAngle();
setTurnRightRadians(Utils.normalRelativeAngle(turnTo));
//Reverse chance
if ((eDelta = eLastEnergy - eEnergy) > 0 && eDelta <= 3) {
eLastShot = eDelta;
}
//This is an adaptation from Raiko. If you can't beat 'em, join 'em
double theta = .64 * bulletV(eLastShot)/eDistance;
if ((Math.random() > Math.pow(theta, theta) && flatten)) {
reverse();
} else if (out(MOVEMENT_DISTANCE * direction, turnTo)) {
reverse();
}
//Finally move
setAhead(Math.cos(turnTo)*100*direction);
//setAhead(MOVEMENT_DISTANCE * direction);
//Radar
setTurnRadarRightRadians(Math.sin(eAbsBearing - getRadarHeadingRadians()));
//Gun
//Initialize the statbuffer
if (statBuffer == null) {
statBuffer = (int[][][][]) readObject(targetName);
}
//Retrieve the current stat segment to use
int stats[] = statBuffer[accellIndex][latVelIndex][distanceIndex];
//Get the current best Guess Factor to shoot at
int bestIndex = (int)((GUESS_FACTORS -1) / 2);
for (int i = 0; i < stats.length ; i++) {
if (stats[i] > stats[bestIndex])
bestIndex = i;
}
//Aim the gun
//Original
setTurnGunRightRadians(robocode.util.Utils.normalRelativeAngle(eAbsBearing - getGunHeadingRadians() + (getGuessAngle(bestIndex, firePower))));
//If the gun is cool, fire and shoot a Wave
if (getGunHeat() == 0 && firePower > 0) {
setFire(firePower);
Wave w = new Wave();
w.guessFactors = stats;
w.wDirection = eDirection;
w.eventTime = getTime();
w.shotOrigin = new Point2D.Double(getX(), getY());
w.shotPower = firePower;
w.startingAbsTargetBearing = eAbsBearing;
w.maxAnglePossible = Math.asin(8D / bulletV(firePower));
addCustomEvent(w);
}
//Housekeeping
eLastVelocity = eVelocity;
eLastEnergy = eEnergy;
}
// public void onHitByBullet(HitByBulletEvent e) {
// if (!flatten)
// hits++;
// flatten = true;
// }
// Check if we are hit with full lead aim
// Adapted from Axe's Musashi: http://robowiki.net/?Musashi
public void onHitByBullet(HitByBulletEvent e) {
if (lastReverseTime > (eDistance + 50) / e.getVelocity() && !flatten) {
flatten = (++hits > 1);
}
}
//Method to determine if I should wall smooth or not.
private double getTurnAngle() {
double turnTo = eBearing + (Math.PI / 2) + (.2618 * direction * (eDistance > MAX_STAND_OFF_DISTANCE ? CLOSE_IN : (eDistance < MIN_STAND_OFF_DISTANCE ? BACK_OFF : 0)));
if (out(WALL_CURVE_DISTANCE * direction, turnTo) && eDistance > 200) { //Start smoothing
//int cDirection = ((getVelocity() * Math.sin(getHeadingRadians() - Math.atan2(getX() - getBattleFieldWidth() / 2, getY() - getBattleFieldHeight() / 2))) > 0 ? 1 : -1);
double test;
//No "diving" in
if (Math.abs(Math.sin(eBearing)) < .500 && flatten) { //.500 == sin 30 degrees
reverse();
//Hug the wall.
} else if (!out(WALL_CURVE_DISTANCE * direction, getHeadingRadians())) {
return 0; //Moving along the wall
//Smooth into it
} else {
return (BEND_ANGLE * -direction);
}
} //End smoothing
return turnTo;
}
//Reverse my direction
public void reverse() {
lastReverseTime = getTime();
direction = -direction;
}
// Save the data if I win the round
public void onWin(WinEvent e) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new RobocodeFileOutputStream(getDataFile(targetName))));
oos.writeObject(statBuffer);
oos.close();
} catch (IOException ex) {
//Should do something here but alas it is too big
//e.printStackTrace();
}
}
// Figures the Guess Angle Radians to offset the gun by
private double getGuessAngle(int guessIndex, double firePower) {
if (eEnergy == 0.0) //If the enemy bot is disabled, fire directly at it.
return 0.0;
return (guessIndex - ((GUESS_FACTORS - 1) / 2)) / ((GUESS_FACTORS - 1) / 2) * Math.asin(8.0 / bulletV(firePower)) * eDirection;
}
// Method to read data from disc. Taken from SandboxMini
Object readObject(String fileName) {
try {
ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(new FileInputStream(getDataFile(fileName))));
Object o = ois.readObject();
ois.close();
return o;
} catch (Exception e) {
return new int[3][3][11][(int)GUESS_FACTORS];
//Should do something here but alas it is too big
//e.printStackTrace();
}
}
//Retrieve the bullets velocity
private double bulletV (double shotPower) {
return (20 - (3 * shotPower));
}
//Stolen from kawigi and modified to make it smaller
public boolean out(double c, double angle) {
//double angle;
return !(field.contains(Math.sin(angle = getHeadingRadians())*c+getX(), Math.cos(angle)*c+getY()));
}
//Stolen from kawigi
//private double distanceFromCorner() {
//private boolean cornered() {
// double x, y;
// return Point2D.distance(0, 0, Math.min((x = getX()), getBattleFieldWidth()-x), Math.min((y = getY()), getBattleFieldHeight()-y)) < 180;
//}
//Test for closeness to wall
//public double wallDist(double x, double y) {
// return Math.min(Math.min(x - 18D, getBattleFieldWidth() - 18D - x), Math.min(y - 18D, getBattleFieldHeight() -18D - y));
//}
// Jekyl's implementation of a Wave (mostly)
public class Wave extends Condition {
public Point2D.Double shotOrigin;
public double shotPower;
public double startingAbsTargetBearing;
public double maxAnglePossible;
public long eventTime;
public int[] guessFactors;
public int wDirection;
public boolean test() {
if (((getTime() - eventTime) * bulletV(shotPower)) >= shotOrigin.distance(eX,eY)) {
double currentAbsBearingFromShotOrigin = Math.atan2(eX - shotOrigin.getX(), eY - shotOrigin.getY());
guessFactors[(int)Math.max(0, Math.min((GUESS_FACTORS - 1),(int) Math.round(((((Utils.normalRelativeAngle(currentAbsBearingFromShotOrigin - startingAbsTargetBearing) * wDirection) / maxAnglePossible) * ((GUESS_FACTORS - 1) / 2)) + ((GUESS_FACTORS - 1) / 2)))))]++;
removeCustomEvent(this);
}
return false;
}
} //End Wave
}
As you can see there is quite a bit of code commented out and you can see some of the variables I am monkeying around with. As always, questions, comments, and feedback are always appreciated.