Difference between revisions of "Basilisk"

From Robowiki
Jump to navigation Jump to search
m (updated Basilisk's page)
Line 8: Line 8:
 
| author          = [[User:Slugzilla]]
 
| author          = [[User:Slugzilla]]
 
| extends        = [[AdvancedRobot]]
 
| extends        = [[AdvancedRobot]]
| targeting      = Guessfactor Targeting
+
| targeting      = [[Guessfactor Targeting]]
| movement        = Stop and go and Random Movement
+
| movement        = [[Stop And Go]], [[Random Movement]]
| current_version = 2.19
+
| current_version = 2.35
 
| released        = 2019
 
| released        = 2019
 
| license        = [[RWPCL]]
 
| license        = [[RWPCL]]
| download_link  = [https://drive.google.com/uc?export=download&id=1GZokhKyIAj0DUDi7MaH4XlJzsfva6fJ9 Basilisk]
+
| download_link  = [https://drive.google.com/uc?export=download&id=16-Cts6GGsFpUqmavKflMbMd5dr6SGTvw Basilisk 2.35]
 
| isOpenSource    = true
 
| isOpenSource    = true
 
| isMelee        = false
 
| isMelee        = false
Line 28: Line 28:
 
'''How competitive is it?'''
 
'''How competitive is it?'''
  
Currently #15 in the MiniRumble and #118 in the RoboRumble.
+
Currently #14 in the MiniRumble and #99 in the RoboRumble.
  
 
== Strategy ==
 
== Strategy ==
Line 34: Line 34:
 
'''How does it move?'''
 
'''How does it move?'''
  
It starts with stop and go, but if it gets hit too much, it will switch to random movement.
+
Basilisk starts out with [[Stop And Go]], but if it gets hit often, it will switch over to [[Random  Movement]].
  
 
'''How does it fire?'''
 
'''How does it fire?'''
  
It uses guessfactor targeting.
+
Basilisk uses [[Guessfactor Targeting]].  It's based off of [[GFTargetingBot]], but has [[Energy Management]], [[Rolling Averages]], and [[Lateral Velocity]] segments, plus a change in velocity segment.
  
 
'''How does it dodge bullets?'''
 
'''How does it dodge bullets?'''
  
Stop and go dodges the simple targeters, random movement dodges the rest =)
+
Stop and go dodges [[HOT]] and [[Linear Targeting]], and the [[Random Movement]] dodges the rest =)
  
 
'''How does the melee strategy differ from one-on-one strategy?'''
 
'''How does the melee strategy differ from one-on-one strategy?'''
Line 56: Line 56:
 
'''Can I use your code?'''
 
'''Can I use your code?'''
  
Sure!  See code below or decompile the .jar file It's licensed under the [[RWPCL]].
+
Sure!  See code below or decompile the .jar file.  
 
 
 
'''What's next for your robot?'''
 
'''What's next for your robot?'''
  
Line 70: Line 69:
 
Credits:
 
Credits:
  
Based off of RandomMovementBot and the GFTargetingBot by PEZ
+
Basilisk is based off of many ideas and concepts from the RoboWiki.  Specifically, I used code/drew inspiration from
 
 
Uses the Turn Multiplier Lock from the RoboWiki
 
 
 
Dive protection is from by Tityus
 
  
The favorite distance idea was inspired by Raiko
+
[[RandomMovementBot]], [[GFTargetingBot]], [[Aristocles]], [[RaikoNano]], [[Tityus]], [[Raiko]], [[Cotillion]], [[BlackWidow]], [[HedgehogGF]], and [[EpeeistMicro]].
  
Uses Raiko's reverse direction formula
+
Special thanks to Dsekercioglu for help identifying bugs and beating bullet shielders =)
 
 
Stop and go at the beginning and then swapping to random movement was inspired by Cotillion and BlackWidow
 
 
 
Improved mode swaps from EpeeistMicro and Cotillion
 
 
 
Uses Cotillions stop and go move amount formula
 
 
 
Special thanks to Dsekercioglu for giving me ideas for beating the bullet shielders, and help identifying bugs
 
 
 
And of course, a huge thank you to all the contributors on the RoboWiki for your awesome tutorials and open source robots!
 
  
 
== Code ==
 
== Code ==
  
'''Version 2.15'''
+
'''Version 2.35'''
  
 
<syntaxhighlight>
 
<syntaxhighlight>
Line 101: Line 86:
 
import java.awt.*;
 
import java.awt.*;
 
import static robocode.util.Utils.normalRelativeAngleDegrees;
 
import static robocode.util.Utils.normalRelativeAngleDegrees;
 +
import robocode.BulletHitEvent;
 +
import robocode.HitByBulletEvent;
  
 
public class Basilisk extends AdvancedRobot {
 
public class Basilisk extends AdvancedRobot {
Line 106: Line 93:
 
private static double lateralDirection;
 
private static double lateralDirection;
 
private static double lastEnemyVelocity;
 
private static double lastEnemyVelocity;
static final double MAX_VELOCITY = 8;
 
    static final double WALL_MARGIN = 25;
 
 
static double enemyFirePower;
 
static double enemyFirePower;
 
static double enemyBulletVelocity;
 
static double enemyBulletVelocity;
    Point2D robotLocation;
 
    Point2D enemyLocation;
 
 
     double enemyDistance;
 
     double enemyDistance;
 
     double enemyAbsoluteBearing;
 
     double enemyAbsoluteBearing;
 
double enemyVelocity;
 
double enemyVelocity;
    double movementLateralAngle;
+
static double direction = 1;
double direction = 1;
+
static int movementMode;
static int bullets;
 
 
static double hits;
 
static double hits;
static double enemyEnergy;
+
private static double enemyEnergy;
 
static double deltaT;
 
static double deltaT;
 
static double lastDeltaT;
 
static double lastDeltaT;
 
static double lastReverseTime;
 
static double lastReverseTime;
+
static double firePower;
 
     public void run() {
 
     public void run() {
 
         setAdjustRadarForGunTurn(true);
 
         setAdjustRadarForGunTurn(true);
Line 140: Line 122:
 
//radar ↓
 
//radar ↓
 
double radarTurn = getHeadingRadians() + e.getBearingRadians() - getRadarHeadingRadians();
 
double radarTurn = getHeadingRadians() + e.getBearingRadians() - getRadarHeadingRadians();
 +
    setTurnRadarRightRadians(Utils.normalRelativeAngle(radarTurn));
 +
//movement ↓
 +
     
 +
double v2;
 +
double offset;
 +
double theta = 0.5952*(20D - 3D*enemyFirePower)/enemyDistance;
 +
 +
if (movementMode >= 1) {
 +
offset = 2 + 100/e.getDistance();
 +
} else {
 +
//offset = 1.5 + 100/e.getDistance();
 +
offset = 2 + (int)(100/e.getDistance());
 +
}
 
 
//movement ↓
 
        robotLocation = new Point2D.Double(getX(), getY());
 
 
         enemyAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
 
         enemyAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
 +
double v1 = enemyAbsoluteBearing;
 
         enemyDistance = e.getDistance();
 
         enemyDistance = e.getDistance();
 
enemyVelocity = e.getVelocity();
 
enemyVelocity = e.getVelocity();
        enemyLocation = vectorToLocation(enemyAbsoluteBearing, enemyDistance, robotLocation);
+
enemyBulletVelocity = 20 - 3 * enemyFirePower;
enemyBulletVelocity = 20 - 3*enemyFirePower;
+
//we move far away when close, but stabilize and move perpendicular when far ↓
 
movementLateralAngle = Math.max(enemyDistance/2000, 0.075);
 
 
if (enemyEnergy > e.getEnergy() && enemyEnergy - 3 <= e.getEnergy()) {
 
if (enemyEnergy > e.getEnergy() && enemyEnergy - 3 <= e.getEnergy()) {
 
enemyFirePower = enemyEnergy - e.getEnergy();
 
enemyFirePower = enemyEnergy - e.getEnergy();
 
}
 
}
if (bullets >= 1 && enemyDistance > 100) {
+
while(!new Rectangle2D.Double(18, 18, 764, 564).
considerChangingDirection();
+
contains(getX() + 160 * Math.sin(v2 = v1 + direction * (offset -= .02)), getY() + 160 * Math.cos(v2)));
}  
+
 
 +
if (offset < 0.75) {
 +
direction = -direction;
 +
lastDeltaT = deltaT;
 +
lastReverseTime = getTime();
 +
}
 +
setTurnRightRadians(Math.tan(v2 -= getHeadingRadians()));
 +
deltaT = getTime() - lastReverseTime;
 
 
for (int i = 0; i < 2; i++) {
+
if(movementMode >= 1) {
Point2D robotDestination = null;
+
setAhead(1000 * Math.cos(v2));
double tries = 0;
+
if (Math.random() > Math.pow(theta, theta) && enemyDistance > 100) {
do {
+
if (deltaT >= lastDeltaT +2 || deltaT <= lastDeltaT -2) {
robotDestination = vectorToLocation(absoluteBearing(enemyLocation, robotLocation) + movementLateralAngle*direction,
+
direction = -direction;
enemyDistance * (1.1 - tries / 100.0), enemyLocation);
+
lastDeltaT = deltaT;
tries++;
+
lastReverseTime = getTime();
 
} while (tries < 100 && !fieldRectangle(WALL_MARGIN).contains(robotDestination));
 
double angle = Utils.normalRelativeAngle(absoluteBearing(robotLocation, robotDestination) - getHeadingRadians());
 
double turnAngle = Math.atan(Math.tan(angle));
 
        setTurnRightRadians(turnAngle);
 
//random movement ↓
 
if (bullets >= 1) {
 
setAhead(Double.POSITIVE_INFINITY * (angle == turnAngle ? 1 : -1));
 
//stop and go ↓
 
} else {
 
if (enemyEnergy > e.getEnergy() && getDistanceRemaining() == 0) {
 
setAhead(((3 + (int)(enemyFirePower * 1.999999)) << 3 ) * (angle == turnAngle ? 1 : -1));
 
}
 
 
}
 
}
// Hit the brake pedal hard if we need to turn sharply
+
}
setMaxVelocity(Math.abs(getTurnRemaining()) > 33 ? 2 : MAX_VELOCITY);
+
} else {
+
double energyDrop = enemyEnergy - e.getEnergy();
if (tries < 30 + i * 5) {
+
if ((int)(100/enemyDistance) + energyDrop > 0) {
break;
+
setAhead(((3 + (int)(energyDrop * 1.999999)) << 3 ) * Math.signum(Math.cos(v2)));
}
+
}
// dive protection ↓
+
}
direction *= -1;
 
lastDeltaT = deltaT;
 
lastReverseTime = getTime();
 
}
 
 
enemyEnergy = e.getEnergy();
 
enemyEnergy = e.getEnergy();
 
//gun ↓
 
//gun ↓
+
double enemyLateralVelocity = e.getVelocity() * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing);
 
if (enemyVelocity != 0) {
 
if (enemyVelocity != 0) {
 
lateralDirection = GFTUtils.sign(enemyVelocity * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
 
lateralDirection = GFTUtils.sign(enemyVelocity * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
Line 197: Line 179:
 
GFTWave wave = new GFTWave(this);
 
GFTWave wave = new GFTWave(this);
 
 
wave.bulletPower = Math.min(Math.min(e.getEnergy()/4, getEnergy()/8), Math.min(0.05 * Math.round((1.5+200/enemyDistance)/0.05), 2.95));
+
wave.bulletPower = Math.min(Math.min(e.getEnergy()/4, getEnergy()/8), Math.min(0.05 * Math.round((1.8+100/enemyDistance)/0.05), 3));
 +
firePower =  wave.bulletPower;
 
wave.MAX_ESCAPE_ANGLE = (Math.asin(8/(20 - 3 * wave.bulletPower)));
 
wave.MAX_ESCAPE_ANGLE = (Math.asin(8/(20 - 3 * wave.bulletPower)));
 
wave.BIN_WIDTH = wave.MAX_ESCAPE_ANGLE / wave.MIDDLE_BIN;
 
wave.BIN_WIDTH = wave.MAX_ESCAPE_ANGLE / wave.MIDDLE_BIN;
Line 203: Line 186:
 
GFTWave.targetLocation = GFTUtils.project(wave.gunLocation, enemyAbsoluteBearing, enemyDistance);
 
GFTWave.targetLocation = GFTUtils.project(wave.gunLocation, enemyAbsoluteBearing, enemyDistance);
 
wave.lateralDirection = lateralDirection;
 
wave.lateralDirection = lateralDirection;
wave.setSegmentations(enemyDistance, enemyVelocity, lastEnemyVelocity);
+
wave.setSegmentations(enemyDistance, enemyVelocity, lastEnemyVelocity, enemyLateralVelocity);
 
lastEnemyVelocity = enemyVelocity;
 
lastEnemyVelocity = enemyVelocity;
 
wave.bearing = enemyAbsoluteBearing;
 
wave.bearing = enemyAbsoluteBearing;
Line 212: Line 195:
 
setFire(wave.bulletPower);
 
setFire(wave.bulletPower);
 
}
 
}
 +
 
if (getEnergy() >= wave.bulletPower) {
 
if (getEnergy() >= wave.bulletPower) {
 
addCustomEvent(wave);
 
addCustomEvent(wave);
 
}
 
}
//radar ↓
+
    setTurnRadarRightRadians(Utils.normalRelativeAngle(radarTurn));
 
 
     }
 
     }
 
//movement ↓
 
//movement ↓
//random movement  ↓
+
public void onHitByBullet(HitByBulletEvent e) {
    void considerChangingDirection() {
+
//enemyEnergy += 3 * enemyFirePower;
deltaT = getTime() - lastReverseTime;
+
enemyEnergy -= Rules.getBulletDamage(e.getBullet().getPower());
double theta;
+
if ((hits += (4.25 / enemyBulletVelocity)) > getRoundNum() + 1) {
theta = 0.5952*(20D - 3D*enemyFirePower)/enemyDistance;
+
movementMode++;
if (Math.random() > Math.pow(theta, theta)) {
 
if (deltaT >= lastDeltaT +2 || deltaT <= lastDeltaT -2) {
 
direction *= -1;
 
lastDeltaT = deltaT;
 
lastReverseTime = getTime();
 
}
 
}
 
    }
 
    RoundRectangle2D fieldRectangle(double margin) {
 
        return new RoundRectangle2D.Double(margin, margin,
 
    getBattleFieldWidth() - margin * 2, getBattleFieldHeight() - margin * 2, 75, 75);
 
    }
 
 
    static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation) {
 
return vectorToLocation(angle, length, sourceLocation, new Point2D.Double());
 
    }
 
 
    static Point2D vectorToLocation(double angle, double length, Point2D sourceLocation, Point2D targetLocation) {
 
        targetLocation.setLocation(sourceLocation.getX() + Math.sin(angle) * length,
 
            sourceLocation.getY() + Math.cos(angle) * length);
 
return targetLocation;
 
    }
 
 
    static double absoluteBearing(Point2D source, Point2D target) {
 
        return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY());
 
    }
 
 
public void onHitByBullet(HitByBulletEvent e) {
 
//enemyEnergy += 20 - enemyBulletVelocity;
 
enemyEnergy += 2 * enemyFirePower;
 
if ((hits += (5 / enemyBulletVelocity)) > getRoundNum() + 2) {
 
bullets++;
 
 
}
 
}
 
     }
 
     }
 
 
public void onBulletHit(BulletHitEvent e) {
 
public void onBulletHit(BulletHitEvent e) {
enemyEnergy -= 10;
+
//enemyEnergy -= 4 * firePower + firePower > 1 ? 2 * firePower - 1  : 0;
 +
enemyEnergy -= Rules.getBulletDamage(e.getBullet().getPower());
 
}
 
}
 
}
 
}
Line 275: Line 226:
 
private static final int DISTANCE_INDEXES = 5;
 
private static final int DISTANCE_INDEXES = 5;
 
private static final int VELOCITY_INDEXES = 5;
 
private static final int VELOCITY_INDEXES = 5;
 +
private static final int ACCELERATION_INDEXES = 3;
 +
private static final int LATERAL_VELOCITY_INDEXES = 5;
 +
 
static final int BINS = 25;
 
static final int BINS = 25;
 
static final int MIDDLE_BIN = (BINS - 1) / 2;
 
static final int MIDDLE_BIN = (BINS - 1) / 2;
 
static double MAX_ESCAPE_ANGLE;
 
static double MAX_ESCAPE_ANGLE;
 
static double BIN_WIDTH;
 
static double BIN_WIDTH;
 +
private static double[][][][][][] statBuffers = new double[DISTANCE_INDEXES][VELOCITY_INDEXES][VELOCITY_INDEXES][ACCELERATION_INDEXES][LATERAL_VELOCITY_INDEXES][BINS];
 
   
 
   
private static int[][][][] statBuffers = new int[DISTANCE_INDEXES][VELOCITY_INDEXES][VELOCITY_INDEXES][BINS];
+
private double[] buffer;
 
private int[] buffer;
 
 
private AdvancedRobot robot;
 
private AdvancedRobot robot;
 
private double distanceTraveled;
 
private double distanceTraveled;
Line 293: Line 246:
 
//remove passed waves
 
//remove passed waves
 
if (hasArrived()) {
 
if (hasArrived()) {
buffer[currentBin()]++;
+
//buffer[currentBin()]++;
 +
// value = buffer[currentBin()]
 +
// newValue = .9*value +1
 +
// buffer[currentBin()] = newValue
 +
 +
for (int i = 0; i < BINS; i++) {
 +
buffer[i] = 0.95 * buffer[i];
 +
}
 +
buffer[currentBin()] += 1;
 
robot.removeCustomEvent(this);
 
robot.removeCustomEvent(this);
 
}
 
}
Line 301: Line 262:
 
return (lateralDirection * BIN_WIDTH) * (mostVisitedBin() - MIDDLE_BIN);
 
return (lateralDirection * BIN_WIDTH) * (mostVisitedBin() - MIDDLE_BIN);
 
}
 
}
void setSegmentations(double distance, double velocity, double lastVelocity) {
+
void setSegmentations(double distance, double velocity, double lastVelocity, double lateralVelocity) {
 
int distanceIndex = (int)(distance / (MAX_DISTANCE / DISTANCE_INDEXES));
 
int distanceIndex = (int)(distance / (MAX_DISTANCE / DISTANCE_INDEXES));
 
int velocityIndex = (int)Math.abs(velocity / 2);
 
int velocityIndex = (int)Math.abs(velocity / 2);
 
int lastVelocityIndex = (int)Math.abs(lastVelocity / 2);
 
int lastVelocityIndex = (int)Math.abs(lastVelocity / 2);
 +
int deltaTime;
 +
if (velocityIndex < lastVelocityIndex) {
 +
deltaTime = 0;
 +
} else if (velocityIndex > lastVelocityIndex) {
 +
deltaTime = 1;
 +
} else {
 +
deltaTime = 2;
 +
}
 +
int accelerationIndex = deltaTime;
 +
int lateralVelocityIndex  = (int)Math.abs(lateralVelocity / 2);
 
 
buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex];
+
buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex][accelerationIndex][lateralVelocityIndex];
 
}
 
}
 
private void advance() {
 
private void advance() {
Line 315: Line 286:
 
}
 
}
 
private int currentBin() {
 
private int currentBin() {
int bin = (int)Math.round(((Utils.normalRelativeAngle(GFTUtils.absoluteBearing(gunLocation, targetLocation) - bearing)) /
+
int bin = (int)Math.round((
(lateralDirection * BIN_WIDTH)) + MIDDLE_BIN);
+
(Utils.normalRelativeAngle(GFTUtils.absoluteBearing(gunLocation, targetLocation) - bearing))  
 +
/ (lateralDirection * BIN_WIDTH)
 +
) + MIDDLE_BIN);
 
return GFTUtils.minMax(bin, 0, BINS - 1);
 
return GFTUtils.minMax(bin, 0, BINS - 1);
 
}
 
}

Revision as of 16:37, 8 June 2019

Basilisk Sub-pages:
Version History
Basilisk
Author(s) User:Slugzilla
Extends AdvancedRobot
Targeting Guessfactor Targeting
Movement Stop And Go, Random Movement
Released 2019
Current Version 2.35
Code License RWPCL
[Basilisk 2.35 Download]

Background Information

What's special about it?

It's my first robot =)

How competitive is it?

Currently #14 in the MiniRumble and #99 in the RoboRumble.

Strategy

How does it move?

Basilisk starts out with Stop And Go, but if it gets hit often, it will switch over to Random Movement.

How does it fire?

Basilisk uses Guessfactor Targeting. It's based off of GFTargetingBot, but has Energy Management, Rolling Averages, and Lateral Velocity segments, plus a change in velocity segment.

How does it dodge bullets?

Stop and go dodges HOT and Linear Targeting, and the Random Movement dodges the rest =)

How does the melee strategy differ from one-on-one strategy?

It doesn't have a melee strategy.

Additional Information

Where did you get the name?

I wanted a robot that was mean, lean, and green. Basilisks are green, and I think they're pretty mean, at least according to the legends =)

Can I use your code?

Sure! See code below or decompile the .jar file. What's next for your robot?

Top 10!!!

Does it have any White Whales?

Partial is it's arch-nemesis!!!

What other robot(s) is it based on?

Credits:

Basilisk is based off of many ideas and concepts from the RoboWiki. Specifically, I used code/drew inspiration from

RandomMovementBot, GFTargetingBot, Aristocles, RaikoNano, Tityus, Raiko, Cotillion, BlackWidow, HedgehogGF, and EpeeistMicro.

Special thanks to Dsekercioglu for help identifying bugs and beating bullet shielders =)

Code

Version 2.35

package slugzilla;
import robocode.*;
import java.awt.geom.*;
import robocode.util.*;
import java.awt.*;
import static robocode.util.Utils.normalRelativeAngleDegrees;
import robocode.BulletHitEvent;
import robocode.HitByBulletEvent;

public class Basilisk extends AdvancedRobot {

	private static double lateralDirection;
	private static double lastEnemyVelocity;
	static double enemyFirePower;
	static double enemyBulletVelocity;
    double enemyDistance;
    double enemyAbsoluteBearing;
	double enemyVelocity;
	static double direction = 1;
	static int movementMode;
	static double hits;
	private static double enemyEnergy;
	static double deltaT;
	static double lastDeltaT;
	static double lastReverseTime;
	static double firePower;
    public void run() {
        setAdjustRadarForGunTurn(true);
 		setAdjustGunForRobotTurn(true);
		setBodyColor(new Color(40, 100, 100));
		setGunColor(new Color(34, 50, 50));
		setRadarColor(new Color(0, 255, 0));
		setScanColor(new Color(0, 255, 0));
	 	turnRadarRightRadians(Double.POSITIVE_INFINITY);
        do {
            scan();
        } while (true);
    }
 
    public void onScannedRobot(ScannedRobotEvent e) {
		//radar ↓
		double radarTurn = getHeadingRadians() + e.getBearingRadians() - getRadarHeadingRadians();
    	setTurnRadarRightRadians(Utils.normalRelativeAngle(radarTurn));
		//movement ↓
       	
		double v2; 
		double offset;
		double theta = 0.5952*(20D - 3D*enemyFirePower)/enemyDistance;

		if (movementMode >= 1) {
			offset = 2 + 100/e.getDistance();
		} else {
			//offset = 1.5 + 100/e.getDistance();
			offset = 2 + (int)(100/e.getDistance());
		}
		
        enemyAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
		double v1 = enemyAbsoluteBearing;
        enemyDistance = e.getDistance();
		enemyVelocity = e.getVelocity();
		enemyBulletVelocity = 20 - 3 * enemyFirePower;
		
		if (enemyEnergy > e.getEnergy() && enemyEnergy - 3 <= e.getEnergy()) {
			enemyFirePower = enemyEnergy - e.getEnergy();
		}
		while(!new Rectangle2D.Double(18, 18, 764, 564).
			contains(getX() + 160 * Math.sin(v2 = v1 + direction * (offset -= .02)), getY() + 160 * Math.cos(v2)));

		if (offset < 0.75) {
			direction = -direction;
			lastDeltaT = deltaT;
			lastReverseTime = getTime();
		}
		setTurnRightRadians(Math.tan(v2 -= getHeadingRadians()));
		deltaT = getTime() - lastReverseTime;
		
		if(movementMode >= 1) {
			setAhead(1000 * Math.cos(v2));
			if (Math.random() > Math.pow(theta, theta) && enemyDistance > 100) {
				if (deltaT >= lastDeltaT +2 || deltaT <= lastDeltaT -2) {
					direction = -direction;
					lastDeltaT = deltaT;
					lastReverseTime = getTime();
				}
			}
		} else {
			double energyDrop = enemyEnergy - e.getEnergy();
			if ((int)(100/enemyDistance) + energyDrop > 0) {
				setAhead(((3 + (int)(energyDrop * 1.999999)) << 3 ) * Math.signum(Math.cos(v2)));
			}		
		}
		enemyEnergy = e.getEnergy();
		//gun ↓
		double enemyLateralVelocity = e.getVelocity() * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing);
		if (enemyVelocity != 0) {
			lateralDirection = GFTUtils.sign(enemyVelocity * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
		}
		GFTWave wave = new GFTWave(this);
		
		wave.bulletPower = Math.min(Math.min(e.getEnergy()/4, getEnergy()/8), Math.min(0.05 * Math.round((1.8+100/enemyDistance)/0.05), 3));
		firePower =  wave.bulletPower;
		wave.MAX_ESCAPE_ANGLE = (Math.asin(8/(20 - 3 * wave.bulletPower)));
		wave.BIN_WIDTH = wave.MAX_ESCAPE_ANGLE / wave.MIDDLE_BIN;
		wave.gunLocation = new Point2D.Double(getX(), getY());
		GFTWave.targetLocation = GFTUtils.project(wave.gunLocation, enemyAbsoluteBearing, enemyDistance);
		wave.lateralDirection = lateralDirection;
		wave.setSegmentations(enemyDistance, enemyVelocity, lastEnemyVelocity, enemyLateralVelocity);
		lastEnemyVelocity = enemyVelocity;
		wave.bearing = enemyAbsoluteBearing;
		
		setTurnGunRightRadians(Utils.normalRelativeAngle(enemyAbsoluteBearing - getGunHeadingRadians() + wave.mostVisitedBearingOffset()) + ((Math.random()/500)-0.002));
		if (getEnergy() > 0.4) {
		//fire at will! ↓
			setFire(wave.bulletPower);
		}
		
		if (getEnergy() >= wave.bulletPower) {
			addCustomEvent(wave);
		}
	
    }
	//movement ↓
	public void onHitByBullet(HitByBulletEvent e) {	
		//enemyEnergy += 3 * enemyFirePower;
		enemyEnergy -= Rules.getBulletDamage(e.getBullet().getPower());
		if ((hits += (4.25 / enemyBulletVelocity)) > getRoundNum() + 1) {
			movementMode++;
		}
    }
	public void onBulletHit(BulletHitEvent e) {
		//enemyEnergy -= 4 * firePower + firePower > 1 ? 2 * firePower - 1  : 0;
		enemyEnergy -= Rules.getBulletDamage(e.getBullet().getPower());
	}
}
//gun ↓
class GFTWave extends Condition {
	static Point2D targetLocation;
	double bulletPower;
	Point2D gunLocation;
	double bearing;
	double lateralDirection;
	// 1000 is the maximum space away in a 800 by 600 battlefield
	private static final double MAX_DISTANCE = 1000;
	//segment into 5 sections ↓
	private static final int DISTANCE_INDEXES = 5;
	private static final int VELOCITY_INDEXES = 5;
	private static final int ACCELERATION_INDEXES = 3;
	private static final int LATERAL_VELOCITY_INDEXES = 5;

	static final int BINS = 25;
	static final int MIDDLE_BIN = (BINS - 1) / 2;
	static double MAX_ESCAPE_ANGLE;
	static double BIN_WIDTH;
	private static double[][][][][][] statBuffers = new double[DISTANCE_INDEXES][VELOCITY_INDEXES][VELOCITY_INDEXES][ACCELERATION_INDEXES][LATERAL_VELOCITY_INDEXES][BINS];
 
	private double[] buffer;
	private AdvancedRobot robot;
	private double distanceTraveled;
 
	GFTWave(AdvancedRobot _robot) {
		this.robot = _robot;
	}
	public boolean test() {
		advance();
		//remove passed waves
		if (hasArrived()) {
			//buffer[currentBin()]++;
		//	value = buffer[currentBin()]
		//	newValue = .9*value +1
		//	buffer[currentBin()] = newValue
			
			for (int i = 0; i < BINS; i++) {
				buffer[i] = 0.95 * buffer[i];
			}
			buffer[currentBin()] += 1;
			robot.removeCustomEvent(this);
		}
		return false;
	}
	double mostVisitedBearingOffset() {
		return (lateralDirection * BIN_WIDTH) * (mostVisitedBin() - MIDDLE_BIN);
	}
	void setSegmentations(double distance, double velocity, double lastVelocity, double lateralVelocity) {
		int distanceIndex = (int)(distance / (MAX_DISTANCE / DISTANCE_INDEXES));
		int velocityIndex = (int)Math.abs(velocity / 2);
		int lastVelocityIndex = (int)Math.abs(lastVelocity / 2);
		int deltaTime;
		if (velocityIndex < lastVelocityIndex) {
			deltaTime = 0;
		} else if (velocityIndex > lastVelocityIndex) {
			deltaTime = 1;
		} else {
			deltaTime = 2;
		}
		int accelerationIndex = deltaTime; 
		int lateralVelocityIndex  = (int)Math.abs(lateralVelocity / 2);
	
		buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex][accelerationIndex][lateralVelocityIndex];
	}
	private void advance() {
		distanceTraveled += GFTUtils.bulletVelocity(bulletPower);
	}
	private boolean hasArrived() {
		return distanceTraveled > gunLocation.distance(targetLocation) - 18;
	}
	private int currentBin() {
		int bin = (int)Math.round((
									(Utils.normalRelativeAngle(GFTUtils.absoluteBearing(gunLocation, targetLocation) - bearing)) 
									/ (lateralDirection * BIN_WIDTH)
								) + MIDDLE_BIN);
		return GFTUtils.minMax(bin, 0, BINS - 1);
	}
	private int mostVisitedBin() {
		int mostVisited = MIDDLE_BIN;
		for (int i = 0; i < BINS; i++) {
			if (buffer[i] > buffer[mostVisited]) {
				mostVisited = i;
			}
		}
		return mostVisited;
	}	
}
//helpful utilities ↓
class GFTUtils {
	static double bulletVelocity(double power) {
		return 20 - 3 * power;
	}
	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());
	}
	static int sign(double v) {
		return v < 0 ? -1 : 1;
	}
	static int minMax(int v, int min, int max) {
		return Math.max(min, Math.min(max, v));
	}
}