User:Chase-san/NewTech/KakeruCode
< User:Chase-san | NewTech
Jump to navigation
Jump to search
Code for version MC25
package cs;
/*
* Dear Future Chase,
*
* I cannot even begin to express how deeply sorry I am.
* But I assure you, at the time, I (probably) knew what I was doing.
*
* -Chase
*
* P.S. I am trying to leave comments,
* but you know how bad I am at those.
*/
import java.awt.Color;
import java.util.Iterator;
import java.util.LinkedList;
import robocode.Bullet;
import robocode.BulletHitBulletEvent;
import robocode.BulletHitEvent;
import robocode.Event;
import robocode.HitByBulletEvent;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.util.Utils;
import cs.geom.*;
import cs.util.Simulate;
import cs.util.Wave;
/**
* Movement
* <br/><br/>
* In Japanese, Kakeru means "Dash" (in some cases)
* @author Chase
*/
public abstract class Kakeru extends Chikaku {
private static class TurnEntry {
public Vector myPosition;
public Vector enemyPosition;
public double myLateralVelocity;
}
private static class DriveData {
double angleToTurn = 0;
double maxVelocity = Rules.MAX_VELOCITY;
int direction = 1;
}
private static class DangerData {
double danger;
double maxVelocity;
int orbitDirection;
}
/**
* Time needed to turn a quarter circle if we are not moving, the actual movement
* should be against the current velocity for this, but since we stop moving when
* we goto turn its only 4 turns to stop moving and we have some time usually before
* the first bullet gets to us
*/
private static final double turnsNeededToTurnQuarter = (Math.PI/2.0)/Rules.MAX_TURN_RATE_RADIANS;
public LinkedList<TurnEntry> history = new LinkedList<TurnEntry>();
public LinkedList<Wave> enemyWaves = new LinkedList<Wave>();
public static LinkedList<Double> hitFactors = new LinkedList<Double>();
public static LinkedList<Double> enemyBulletPower = new LinkedList<Double>();
public double enemyGunHeat = 0;
/**
* <pre>
* 1 = clockwise
* -1 = counter-clockwise
* </pre>
*/
public int orbitDirection = 1;
public Vector lastEnemyPosition = new Vector();
public double lastEnemyEnergy = 100;
public void onBattleStarted(Event e) {
hitFactors.addLast((Double)0.0);
enemyBulletPower.addLast((Double)3.0);
}
public void onRoundStarted(Event e) {
super.onRoundStarted(e);
println("hitFactors: " + hitFactors.size());
firstScan = true;
}
private boolean imaginaryFired = false;
private boolean firstScan = false;
public void onScannedRobot(ScannedRobotEvent e) {
super.onScannedRobot(e);
enemyGunHeat -= peer.getGunCoolingRate();
if(enemyGunHeat <= 0)
enemyGunHeat = 0;
if(firstScan) {
enemyGunHeat = status.getGunHeat();
firstScan = false;
}
/*
* Setup current history
*/
double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians();
TurnEntry entry = new TurnEntry();
entry.myPosition = myPosition;
entry.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance());
entry.myLateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians());
orbitDirection = Tools.sign(entry.myLateralVelocity);
history.addFirst(entry);
/*
* Check for new enemy wave
*/
double enemyEnergy = e.getEnergy();
double energyDelta = lastEnemyEnergy - enemyEnergy;
if(energyDelta > 0 && energyDelta <= 3.0) {
enemyGunHeat += Rules.getGunHeat(energyDelta);
imaginaryFired = false;
Wave wave = new Wave();
wave.set(lastEnemyPosition);
wave.fireTime = time - 1;
wave.power = energyDelta;
wave.speed = Rules.getBulletSpeed(energyDelta);
TurnEntry stat = history.get(2);
wave.directAngle = stat.enemyPosition.angleTo(stat.myPosition);
int direction = Tools.sign(stat.myLateralVelocity);
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
enemyBulletPower.addLast(energyDelta);
if(enemyBulletPower.size() == 5) {
enemyBulletPower.removeFirst();
}
enemyWaves.add(wave);
}
/*
* Setup data for next round.
*/
lastEnemyPosition.set(entry.enemyPosition);
lastEnemyEnergy = enemyEnergy;
g.setColor(Color.YELLOW);
g.drawLine((int)myPosition.x, (int)myPosition.y, (int)lastEnemyPosition.x, (int)lastEnemyPosition.y);
g.setColor(Color.RED);
Vector v = myPosition.copy();
v.add(lastEnemyPosition);
v.scale(0.5);
g.drawString(""+e.getDistance(), (int)v.x, (int)v.y);
/**
* Calculate imaginary wave
*/
if(!imaginaryFired && enemyGunHeat <= peer.getGunCoolingRate()*2.0) {
imaginaryFired = true;
//add up average bullet power
double power = 0;
for(Double p : enemyBulletPower) {
power += p;
}
power /= enemyBulletPower.size();
//Add virtual wave :D
Wave wave = new Wave();
//lastEnemyPosition
wave.set(simulatedEnemyPosition);
wave.fireTime = time + 1;
wave.power = power;
wave.speed = Rules.getBulletSpeed(power);
wave.directAngle = entry.enemyPosition.angleTo(entry.myPosition);
int direction = Tools.sign(entry.myLateralVelocity);
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
wave.imaginary = true;
enemyWaves.add(wave);
}
}
public void onTurnEnded(Event e) {
super.onTurnEnded(e);
/*
* Don't move if we are scanning, this is mostly start
* of round fast radar finding protection
*/
if(initialTick != 0)
return;
checkWaves();
doMovement();
}
/**
* Run our actual movement
*/
public void doMovement() {
//find the wave soonest to hit us
Wave wave = null;
double bestETA = Double.POSITIVE_INFINITY;
for(Wave check : enemyWaves) {
double distance = myPosition.distance(check) - check.getRadius(time);
double ETA = distance / check.speed;
if(ETA < bestETA) {
//If we are already surfing a strong wave
//that will hit at almost the same time
//do not switch to this wave, more powerful
//waves are slower and thus will always be
//shot first for this to occur, which is why
//we don't need an else on the ETA < bestETA
//This is mostly just a hack for some score
if(wave != null && ETA > bestETA - 3) {
if(check.power > wave.power) {
wave = check;
bestETA = ETA;
}
} else {
wave = check;
bestETA = ETA;
}
}
}
double heading = status.getHeadingRadians();
double velocity = status.getVelocity();
//There is no wave for us to surf!
if(wave == null) {
double safeTurns = status.getGunHeat()/peer.getGunCoolingRate();
//Do we have enough time to move around before they can start firing?
if(safeTurns > turnsNeededToTurnQuarter) {
//No waves movement
//Here we drive away from the enemy as best as we can
double travelAngle = lastEnemyPosition.angleTo(myPosition);
//TODO better wall smoothing
while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
travelAngle += 0.08*orbitDirection;
}
double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians());
int direction = 1;
if(Math.abs(angleToTurn) > Math.PI/2.0) {
angleToTurn = Utils.normalRelativeAngle(angleToTurn - Math.PI);
direction = -1;
}
//Slow down so we do not ram head long into the walls and can instead turn to avoid them
//FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
double maxVelocity = Rules.MAX_VELOCITY;
if(!field.contains(myPosition.projectNew(heading, velocity*3.25)))
maxVelocity = 0;
if(!field.contains(myPosition.projectNew(heading, velocity*5)))
maxVelocity = 4;
peer.setMaxVelocity(maxVelocity);
peer.setTurnBody(angleToTurn);
peer.setMove(100*direction);
} else {
//turn to face perpendicular to the enemy while we have time to do so!
DriveData data = doDrive(myPosition,lastEnemyPosition,
status.getHeadingRadians(),status.getVelocity(),orbitDirection);
peer.setTurnBody(data.angleToTurn);
peer.setMaxVelocity(0);
}
return;
}
//Do the actual surfing and such
g.setColor(Color.ORANGE);
peer.setMaxVelocity(Rules.MAX_VELOCITY);
//DONE make the direction data a class instead
DangerData[] directions = new DangerData[] {
calculateDanger(wave,orbitDirection,0),
calculateDanger(wave,orbitDirection,Rules.MAX_VELOCITY),
calculateDanger(wave,-orbitDirection,Rules.MAX_VELOCITY),
};
int bestIndex = 0;
double leastDanger = Double.POSITIVE_INFINITY;
for(int i = 0; i < directions.length; ++i) {
if(directions[i].danger < leastDanger) {
bestIndex = i;
leastDanger = directions[i].danger;
}
}
DriveData driver = doDrive(myPosition,lastEnemyPosition,
status.getHeadingRadians(),status.getVelocity(),
directions[bestIndex].orbitDirection );
double forward = 100;
peer.setMaxVelocity(Math.min(directions[bestIndex].maxVelocity,driver.maxVelocity));
peer.setTurnBody(driver.angleToTurn);
peer.setMove(forward*driver.direction);
peer.setCall();
}
private DangerData calculateDanger(Wave wave, int orbitDirection, double maxVelocity) {
//Here we just propagate the stuff we got from the input
//so that we do not have to write it again in the array somehow
DangerData data = new DangerData();
data.orbitDirection = orbitDirection;
data.maxVelocity = maxVelocity;
//Simulate ahead a single step, no need to calculate for a position
// (that is our current one) that we can do nothing about
Simulate sim = createSimulator();
sim.maxVelocity = maxVelocity;
sim.direction = orbitDirection;
doDrive(sim,wave,orbitDirection);
sim.step();
sim.maxVelocity = maxVelocity;
//Originally I divided by this, at the moment I do not anymore
int intersected = 0;
long timeOffset = 1;
double danger = 0;
//Reset the factors for the wave we are surfing, don't need old
// data messing up our calculations, now do we?
wave.resetFactors();
while(true) {
//DRAW DANGER MOVEMENT
g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6);
//Every time we intersect iterate
if(wave.doesIntersect(sim.position, time+timeOffset)) {
++intersected;
} else if(intersected > 0) {
//After the intersection is complete calculate the danger
//for the entire span we intersected
double intersectDanger = 0;
double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0;
for(Double d : hitFactors) {
intersectDanger += 0.1/(1.0+Math.abs(d-factorCenter));
if(wave.minFactor < d && wave.maxFactor > d) {
intersectDanger += 1.0;
}
}
danger += intersectDanger;
intersected = -1;
break;
}
//Use our driving method to determine where we will go next
doDrive(sim,wave,orbitDirection);
sim.step();
sim.maxVelocity = maxVelocity;
if(++timeOffset > 24)
break;
}
data.danger = danger;
return data;
}
private void doDrive(Simulate sim, Wave wave, int orbitDirection) {
DriveData data = doDrive(sim.position,wave,
sim.heading,sim.velocity,orbitDirection);
sim.angleToTurn = data.angleToTurn;
sim.direction = data.direction;
sim.maxVelocity = Math.min(sim.maxVelocity, data.maxVelocity);
}
/**
* Calculate the driving
*/
private DriveData doDrive(Vector myPosition, Vector orbitCenter,
double myHeading, double myVelocity, int orbitDirection) {
DriveData data = new DriveData();
double angleToRobot = orbitCenter.angleTo(myPosition);
//if the orbit direction is clockwise/counter, we want to try and point
//our robot in that direction, which is why we multiply by it
double travelAngle = angleToRobot + (Math.PI/2.0) * orbitDirection;
//DONE add distancing to drive method
//TODO add a better distancing method
double distance = myPosition.distance(orbitCenter);
final double best = 500.0;
double distancing = ((distance-best)/best);
travelAngle += distancing*orbitDirection;
//DONE add a wall smoothing method
//TODO add a better wall smoothing method
while(!field.contains(myPosition.projectNew(travelAngle, 140))) {
travelAngle += 0.08*orbitDirection;
}
data.angleToTurn = Utils.normalRelativeAngle(travelAngle - myHeading);
data.direction = 1;
//If our backend is closer to direction, use that instead, and inform
//the caller that we are going to be going in reverse instead
if(Math.abs(data.angleToTurn) > Math.PI/2.0) {
data.angleToTurn = Utils.normalRelativeAngle(data.angleToTurn - Math.PI);
data.direction = -1;
}
//Slow down so we do not ram head long into the walls and can instead turn to avoid them
//FIXME We only have to do this because of the wall smoothing, fix that to get rid of this!
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*3.25)))
data.maxVelocity = 0;
if(!field.contains(myPosition.projectNew(myHeading, myVelocity*5)))
data.maxVelocity = 4;
return data;
}
/**
* Account for our bullets hitting the enemy
*/
public void onBulletHit(BulletHitEvent e) {
super.onBulletHit(e);
//get the power of the bullet of ours that hit
double bulletPower = e.getBullet().getPower();
//determine how much damage our bullet does to the enemy
//and adjust our stored copy to reflect the amount lost
lastEnemyEnergy -= Rules.getBulletDamage(bulletPower);
}
/**
* Account for one of our bullets hitting an enemy bullet.
*/
public void onBulletHitBullet(BulletHitBulletEvent e) {
super.onBulletHitBullet(e);
handleBullet(e.getHitBullet());
}
/**
* Account for one of the enemies bullets hitting us.
*/
public void onHitByBullet(HitByBulletEvent e) {
super.onHitByBullet(e);
//increase the enemy energy based on how powerful the bullet that hit us was
//this is so we can get reliable energy drop detection
lastEnemyEnergy += Rules.getBulletHitBonus(e.getPower());
handleBullet(e.getBullet());
}
/**
* Handle an actual enemy bullet
*/
private void handleBullet(Bullet b) {
Vector bulletPosition = new Vector(b.getX(),b.getY());
//Find the matching wave
Iterator<Wave> waveIterator = enemyWaves.iterator();
while(waveIterator.hasNext()) {
Wave wave = waveIterator.next();
//the current distance of the wave
double radius = wave.speed*(time - wave.fireTime);
//check if the power is close enough to be our wave
//this margin is small, only to allow room for rounding errors
if(Math.abs(b.getPower()-wave.power) < 0.001) {
//check if the bullets distance from the center of the wave is
//close to our waves radius.
//this margin is small, only to allow room for rounding errors
if(Math.abs(wave.distanceSq(bulletPosition)-radius*radius) < 0.1) {
//FIXME extensively test this detection method
//Alternate method, but why recalculate something? Could be used as an extra check.
//double angleToBullet = wave.angleTo(bulletPosition);
double angleToBullet = b.getHeadingRadians();
double angleOffsetFromBase =
Utils.normalRelativeAngle(angleToBullet - wave.directAngle);
double bulletGuessfactor = angleOffsetFromBase / wave.escapeAngle;
debug("Hit @ GF " + bulletGuessfactor);
hitFactors.addLast(bulletGuessfactor);
//FIXME Temporary, only allow 17 factors to be in the buffer at a time
if(hitFactors.size() == 17) {
hitFactors.removeFirst();
}
waveIterator.remove();
break;
}
}
}
}
/**
* Check waves to be removed after they pass
*/
private void checkWaves() {
Iterator<Wave> waveIterator = enemyWaves.iterator();
while(waveIterator.hasNext()) {
Wave wave = waveIterator.next();
double radius = wave.getRadius(time);
///////////////////////
//DRAW WAVE
if(wave.imaginary) {
g.setColor(new Color(32,32,32));
} else {
g.setColor(new Color(128,128,128));
}
double escape = Math.abs(wave.escapeAngle);
g.drawArc(
(int)(wave.x - radius), (int)(wave.y - radius),
(int)(radius*2), (int)(radius*2),
(int)Math.toDegrees(wave.directAngle-escape),
(int)Math.toDegrees(escape*2));
//END DRAW WAVE
///////////////////////
if(wave.imaginary) {
//if the wave is imaginary (a heat wave)
//we should remove it automatically after
//2 ticks which is when we will detect their
//actual wave, if they fire
if(time - wave.fireTime > 2) {
waveIterator.remove();
}
} else
/*
* Clean up waves that can no longer hit us
*/
if(wave.didIntersect(myPosition, time)) {
waveIterator.remove();
}
}
}
}