BasicGTSurfer/Code
Jump to navigation
Jump to search
- BasicGTSurfer Sub-pages:
- BasicGTSurfer - Code
package wiki.tutorial;
import robocode.*;
import robocode.util.Utils;
import java.awt.geom.*; // for Point2D's
import java.lang.*; // for Double and Integer objects
import java.util.ArrayList; // for collection of waves
import java.awt.*;
public class BasicGTSurfer extends AdvancedRobot {
public static int BINS = 47;
public static double _surfStats[] = new double[BINS]; // we'll use 47 bins
public Point2D.Double _myLocation; // our bot's location
public Point2D.Double _enemyLocation; // enemy bot's location
public Point2D.Double _lastGoToPoint;
public double direction = 1;
public ArrayList _enemyWaves;
public ArrayList _surfDirections;
public ArrayList _surfAbsBearings;
// We must keep track of the enemy's energy level to detect EnergyDrop,
// indicating a bullet is fired
public static double _oppEnergy = 100.0;
// 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 Rectangle2D.Double _fieldRect
= new java.awt.geom.Rectangle2D.Double(18, 18, 764, 564);
public static double WALL_STICK = 160;
public void run() {
_enemyWaves = new ArrayList();
_surfDirections = new ArrayList();
_surfAbsBearings = new ArrayList();
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());
double absBearing = e.getBearingRadians() + getHeadingRadians();
setTurnRadarRightRadians(Utils.normalRelativeAngle(absBearing - getRadarHeadingRadians()) * 2);
_surfDirections.add(0,
new Integer((lateralVelocity >= 0) ? 1 : -1));
_surfAbsBearings.add(0, new Double(absBearing + Math.PI));
double bulletPower = _oppEnergy - e.getEnergy();
if (bulletPower < 3.01 && bulletPower > 0.09
&& _surfDirections.size() > 2) {
EnemyWave ew = new EnemyWave();
ew.fireTime = getTime() - 1;
ew.bulletVelocity = bulletVelocity(bulletPower);
ew.distanceTraveled = bulletVelocity(bulletPower);
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);
}
_oppEnergy = e.getEnergy();
// update after EnemyWave detection, because that needs the previous
// enemy location as the source of the wave
_enemyLocation = project(_myLocation, absBearing, e.getDistance());
updateWaves();
doSurfing();
// gun code would go here...
}
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) {
_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...
_surfStats[x] += 1.0 / (Math.pow(index - x, 2) + 1);
}
}
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.abs(bulletVelocity(e.getBullet().getPower())
- ew.bulletVelocity) < 0.001) {
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 ArrayList predictPositions(EnemyWave surfWave, int direction) {
Point2D.Double predictedPosition = (Point2D.Double)_myLocation.clone();
double predictedVelocity = getVelocity();
double predictedHeading = getHeadingRadians();
double maxTurning, moveAngle, moveDir;
ArrayList traveledPoints = new ArrayList();
int counter = 0; // number of ticks in the future
boolean intercepted = false;
do {
double distance = predictedPosition.distance(surfWave.fireLocation);
double offset = Math.PI/2 - 1 + distance/400;
moveAngle =
wallSmoothing(predictedPosition, absoluteBearing(surfWave.fireLocation,
predictedPosition) + (direction * (offset)), 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 = project(predictedPosition, predictedHeading, predictedVelocity);
//add this point the our prediction
traveledPoints.add(predictedPosition);
counter++;
if (predictedPosition.distance(surfWave.fireLocation) - 20 <
surfWave.distanceTraveled + (counter * surfWave.bulletVelocity)
// + surfWave.bulletVelocity
) {
intercepted = true;
}
} while(!intercepted && counter < 500);
//we can't get the the last point, because we need to slow down
if(traveledPoints.size() > 1)
traveledPoints.remove(traveledPoints.size() - 1);
return traveledPoints;
}
public double checkDanger(EnemyWave surfWave, Point2D.Double position) {
int index = getFactorIndex(surfWave, position);
double distance = position.distance(surfWave.fireLocation);
return _surfStats[index]/distance;
}
public Point2D.Double getBestPoint(EnemyWave surfWave){
if(surfWave.safePoints == null){
ArrayList forwardPoints = predictPositions(surfWave, 1);
ArrayList reversePoints = predictPositions(surfWave, -1);
int FminDangerIndex = 0;
int RminDangerIndex = 0;
double FminDanger = Double.POSITIVE_INFINITY;
double RminDanger = Double.POSITIVE_INFINITY;
for(int i = 0, k = forwardPoints.size(); i < k; i++){
double thisDanger = checkDanger(surfWave, (Point2D.Double)(forwardPoints.get(i)));
if(thisDanger <= FminDanger){
FminDangerIndex = i;
FminDanger = thisDanger;
}
}
for(int i = 0, k = reversePoints.size(); i < k; i++){
double thisDanger = checkDanger(surfWave, (Point2D.Double)(reversePoints.get(i)));
if(thisDanger <= RminDanger){
RminDangerIndex = i;
RminDanger = thisDanger;
}
}
ArrayList bestPoints;
int minDangerIndex;
if(FminDanger < RminDanger ){
bestPoints = forwardPoints;
minDangerIndex = FminDangerIndex;
}
else {
bestPoints = reversePoints;
minDangerIndex = RminDangerIndex;
}
Point2D.Double bestPoint = (Point2D.Double)bestPoints.get(minDangerIndex);
while(bestPoints.indexOf(bestPoint) != -1)
bestPoints.remove(bestPoints.size() - 1);
bestPoints.add(bestPoint);
surfWave.safePoints = bestPoints;
//debugging - so that we should always be on top of the last point
bestPoints.add(0,new Point2D.Double(getX(), getY()));
}
else
if(surfWave.safePoints.size() > 1)
surfWave.safePoints.remove(0);
if(surfWave.safePoints.size() >= 1){
for(int i = 0,k=surfWave.safePoints.size(); i < k; i++){
Point2D.Double goToPoint = (Point2D.Double)surfWave.safePoints.get(i);
if(goToPoint.distanceSq(_myLocation) > 20*20*1.1)
//if it's not 20 units away we won't reach max velocity
return goToPoint;
}
//if we don't find a point 20 units away, return the end point
return (Point2D.Double)surfWave.safePoints.get(surfWave.safePoints.size() - 1);
}
return null;
}
public void doSurfing() {
EnemyWave surfWave = getClosestSurfableWave();
double distance = _enemyLocation.distance(_myLocation);
if (surfWave == null || distance < 50) {
//do 'away' movement best distance of 400 - modified from RaikoNano
double absBearing = absoluteBearing(_myLocation, _enemyLocation);
double headingRadians = getHeadingRadians();
double stick = 160;//Math.min(160,distance);
double v2, offset = Math.PI/2 + 1 - distance/400;
while(!_fieldRect.
contains(project(_myLocation,v2 = absBearing + direction*(offset -= 0.02), stick)
// getX() + stick * Math.sin(v2 = absBearing + direction * (offset -= .02)), getY() + stick * Math.cos(v2)
));
if( offset < Math.PI/3 )
direction = -direction;
setAhead(50*Math.cos(v2 - headingRadians));
setTurnRightRadians(Math.tan(v2 - headingRadians));
}
else
goTo(getBestPoint(surfWave));
}
private void goTo(Point2D.Double destination) {
if(destination == null){
if(_lastGoToPoint != null)
destination = _lastGoToPoint;
else
return;
}
_lastGoToPoint = destination;
Point2D.Double location = new Point2D.Double(getX(), getY());
double distance = location.distance(destination);
double angle = Utils.normalRelativeAngle(absoluteBearing(location, destination) - getHeadingRadians());
if (Math.abs(angle) > Math.PI/2) {
distance = -distance;
if (angle > 0) {
angle -= Math.PI;
}
else {
angle += Math.PI;
}
}
//this is hacked so that the bot doesn't turn once we get to our destination
setTurnRightRadians(angle*Math.signum(Math.abs((int)distance)));
setAhead(distance);
}
// This can be defined as an inner class if you want.
class EnemyWave {
Point2D.Double fireLocation;
long fireTime;
double bulletVelocity, directAngle, distanceTraveled;
int direction;
ArrayList safePoints;
public EnemyWave() { }
}
// 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;
}
// CREDIT: from CassiusClay, by PEZ
// - returns point length away from sourceLocation, at angle
// robowiki.net?CassiusClay
public static Point2D.Double project(Point2D.Double sourceLocation, double angle, double length) {
return new Point2D.Double(sourceLocation.x + Math.sin(angle) * length,
sourceLocation.y + Math.cos(angle) * length);
}
// got this from RaikoMicro, by Jamougha, but I think it's used by many authors
// - returns the absolute angle (in radians) from source to target points
public static double absoluteBearing(Point2D.Double source, Point2D.Double target) {
return Math.atan2(target.x - source.x, target.y - source.y);
}
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 void onPaint(java.awt.Graphics2D g) {
g.setColor(Color.red);
for(int i = 0; i < _enemyWaves.size(); i++){
EnemyWave w = (EnemyWave)(_enemyWaves.get(i));
Point2D.Double center = w.fireLocation;
//int radius = (int)(w.distanceTraveled + w.bulletVelocity);
//hack to make waves line up visually, due to execution sequence in robocode engine
//use this only if you advance waves in the event handlers (eg. in onScannedRobot())
//NB! above hack is now only necessary for robocode versions before 1.4.2
//otherwise use:
int radius = (int)w.distanceTraveled;
if(radius - 40 < center.distance(_myLocation))
g.drawOval((int)(center.x - radius ), (int)(center.y - radius), radius*2, radius*2);
}
}
}