Difference between revisions of "User:Chase-san/NewTech/KakeruCode"
< User:Chase-san | NewTech
Jump to navigation
Jump to search
m |
m (update) |
||
Line 2: | Line 2: | ||
<syntaxhighlight> | <syntaxhighlight> | ||
− | + | /** | |
− | + | * Copyright (c) 2011 Chase | |
+ | * | ||
+ | * This software is provided 'as-is', without any express or implied | ||
+ | * warranty. In no event will the authors be held liable for any damages | ||
+ | * arising from the use of this software. | ||
+ | * | ||
+ | * Permission is granted to anyone to use this software for any purpose, | ||
+ | * including commercial applications, and to alter it and redistribute it | ||
+ | * freely, subject to the following restrictions: | ||
+ | * | ||
+ | * 1. The origin of this software must not be misrepresented; you must not | ||
+ | * claim that you wrote the original software. If you use this software | ||
+ | * in a product, an acknowledgment in the product documentation would be | ||
+ | * appreciated but is not required. | ||
+ | * | ||
+ | * 2. Altered source versions must be plainly marked as such, and must not be | ||
+ | * misrepresented as being the original software. | ||
+ | * | ||
+ | * 3. This notice may not be removed or altered from any source | ||
+ | * distribution. | ||
+ | */ | ||
/* | /* | ||
* Dear Future Chase, | * Dear Future Chase, | ||
Line 15: | Line 35: | ||
* but you know how bad I am at those. | * but you know how bad I am at those. | ||
*/ | */ | ||
+ | |||
+ | package cs.move; | ||
import java.awt.Color; | import java.awt.Color; | ||
import java.util.Iterator; | import java.util.Iterator; | ||
import java.util.LinkedList; | import java.util.LinkedList; | ||
+ | import java.util.List; | ||
+ | |||
+ | import cs.Chikaku; | ||
+ | import cs.geom.*; | ||
+ | import cs.utils.*; | ||
− | import | + | import ags.utils.KdTree; |
− | + | import ags.utils.KdTree.Entry; | |
− | import | + | import robocode.*; |
− | |||
− | |||
− | |||
− | import robocode. | ||
import robocode.util.Utils; | import robocode.util.Utils; | ||
− | |||
− | |||
− | |||
− | |||
/** | /** | ||
Line 40: | Line 59: | ||
*/ | */ | ||
public abstract class Kakeru extends Chikaku { | public abstract class Kakeru extends Chikaku { | ||
− | + | private static final int MaxTurnHistoryLength = 20; | |
− | private static | + | private static final int MaxHeatWaveQueueLength = 4; |
− | + | private static final int AverageSingleRoundTime = 1200; | |
− | + | private static final int NearestNeighborsToUseInSurfing = 16; | |
− | + | ||
− | |||
− | |||
− | private static | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | private static | ||
− | |||
− | |||
− | |||
− | |||
− | |||
/** | /** | ||
− | * | + | * Keep in mind with heat waves we will have waves 2 rounds earlier |
− | * | + | * the the gun heat would otherwise indicate |
− | |||
− | |||
*/ | */ | ||
− | private static final | + | private static final int TimeBeforeFirstWaveToStartTurning = 4; |
− | + | ||
− | + | private static final MaximumDeque<Double> enemyBulletPower; | |
− | + | private static final KdTree<Data> tree; | |
− | + | ||
− | + | static { | |
− | + | enemyBulletPower = new MaximumDeque<Double>(MaxHeatWaveQueueLength); | |
− | + | enemyBulletPower.addLast(3.0); | |
− | + | ||
− | + | int dimensions = new Data().getDataArray().length; | |
− | + | tree = new KdTree.SqrEuclid<Data>(dimensions, 0); | |
− | + | ||
− | + | Data data = new Data(); | |
− | + | data.weight = 0.1; | |
− | + | tree.addPoint(data.getDataArray(), data); | |
− | + | } | |
− | + | ||
− | + | private final LinkedList<Wave<Data>> enemyWaves; | |
− | + | private final MaximumDeque<State> history; | |
− | public | + | private Vector lastEnemyPosition; |
− | + | private double lastEnemyEnergy; | |
− | + | private double lastEnemyVelocity; | |
+ | private double enemyGunHeat; | ||
+ | |||
+ | private int orbitDirection = 1; | ||
+ | private boolean haveFiredImaginaryWave = false; | ||
+ | |||
+ | public Kakeru() { | ||
+ | enemyWaves = new LinkedList<Wave<Data>>(); | ||
+ | history = new MaximumDeque<State>(MaxTurnHistoryLength); | ||
+ | lastEnemyPosition = new Vector(-1,-1); | ||
+ | lastEnemyEnergy = 100; | ||
} | } | ||
− | + | ||
+ | @Override | ||
public void onRoundStarted(Event e) { | public void onRoundStarted(Event e) { | ||
super.onRoundStarted(e); | super.onRoundStarted(e); | ||
− | + | ||
− | + | /* Set it to be the same as our gun heat on round start! */ | |
− | + | enemyGunHeat = status.getGunHeat(); | |
− | |||
} | } | ||
− | + | ||
− | + | @Override | |
− | |||
public void onScannedRobot(ScannedRobotEvent e) { | public void onScannedRobot(ScannedRobotEvent e) { | ||
super.onScannedRobot(e); | super.onScannedRobot(e); | ||
− | + | ||
− | + | double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians(); | |
− | + | double enemyVelocity = e.getVelocity(); | |
− | + | ||
− | + | State state = new State(); | |
− | + | ||
− | + | state.myPosition = myPosition; | |
− | + | state.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance()); | |
+ | state.data.lateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians()); | ||
+ | state.data.advancingVelcity = status.getVelocity()*Math.cos(e.getBearingRadians()); | ||
+ | state.data.globalTime = globalTime; | ||
+ | |||
+ | history.addFirst(state); | ||
+ | |||
+ | int direction = Tools.sign(state.data.lateralVelocity); | ||
+ | |||
+ | orbitDirection = direction; | ||
+ | |||
+ | if(Math.abs(enemyVelocity) == 0 && lastEnemyVelocity > 2) { | ||
+ | |||
+ | //Hit the wall! | ||
+ | lastEnemyEnergy -= Rules.getWallHitDamage(lastEnemyVelocity); | ||
} | } | ||
− | + | ||
/* | /* | ||
− | * | + | * Fire new imaginary wave if we can, heat waves |
+ | * are always 2 ticks previous to a real one 'possibly' | ||
+ | * being fired | ||
*/ | */ | ||
− | + | if(!haveFiredImaginaryWave) { | |
− | + | if(enemyGunHeat <= 2.0*peer.getGunCoolingRate()) { | |
− | + | haveFiredImaginaryWave = true; | |
− | + | ||
− | + | double power = 0; | |
− | + | for(Double d : enemyBulletPower) | |
− | + | power += d; | |
− | + | power /= enemyBulletPower.size(); | |
− | + | ||
− | + | /* Create the Wave */ | |
− | + | Wave<Data> wave = createMovementWave(power,direction); | |
+ | wave.set(simulatedEnemyPosition); | ||
+ | wave.imaginary = true; | ||
+ | |||
+ | /* | ||
+ | * Only +1 because real waves are -1 in time, | ||
+ | * meaning that by the time we detect them, it has | ||
+ | * already been 1 turn since it was fired, meaning | ||
+ | * heat waves +2 makes it only +1 here. :-) | ||
+ | */ | ||
+ | wave.fireTime = time + 1; | ||
+ | wave.directAngle = wave.angleTo(state.myPosition); | ||
+ | wave.data = state.data; | ||
− | / | + | enemyWaves.add(wave); |
− | + | } | |
− | + | } | |
+ | |||
+ | /* Check for new enemy wave */ | ||
double enemyEnergy = e.getEnergy(); | double enemyEnergy = e.getEnergy(); | ||
double energyDelta = lastEnemyEnergy - enemyEnergy; | double energyDelta = lastEnemyEnergy - enemyEnergy; | ||
if(energyDelta > 0 && energyDelta <= 3.0) { | if(energyDelta > 0 && energyDelta <= 3.0) { | ||
+ | |||
enemyGunHeat += Rules.getGunHeat(energyDelta); | enemyGunHeat += Rules.getGunHeat(energyDelta); | ||
− | + | haveFiredImaginaryWave = false; | |
− | + | ||
− | Wave wave = | + | State old = history.get(2); |
+ | direction = Tools.sign(old.data.lateralVelocity); | ||
+ | |||
+ | /* Create the Wave */ | ||
+ | Wave<Data> wave = createMovementWave(energyDelta,direction); | ||
wave.set(lastEnemyPosition); | wave.set(lastEnemyPosition); | ||
+ | |||
wave.fireTime = time - 1; | wave.fireTime = time - 1; | ||
− | wave. | + | wave.directAngle = wave.angleTo(old.myPosition); |
− | + | ||
− | + | wave.data = old.data; | |
− | + | ||
− | + | enemyBulletPower.addLast(wave.power); | |
− | |||
− | |||
− | wave. | ||
− | |||
− | enemyBulletPower.addLast( | ||
− | |||
− | |||
− | |||
− | |||
enemyWaves.add(wave); | enemyWaves.add(wave); | ||
} | } | ||
− | + | ||
− | + | lastEnemyPosition = state.enemyPosition; | |
− | + | lastEnemyVelocity = enemyVelocity; | |
− | |||
− | |||
lastEnemyEnergy = enemyEnergy; | lastEnemyEnergy = enemyEnergy; | ||
− | + | ||
− | + | } | |
− | + | ||
− | + | private Wave<Data> createMovementWave(double power, int direction) { | |
− | + | Wave<Data> wave = new Wave<Data>(); | |
− | + | wave.power = power; | |
− | + | wave.speed = Rules.getBulletSpeed(wave.power); | |
− | + | wave.escapeAngle = Math.asin(8.0/wave.speed) * direction; | |
− | + | return wave; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | + | ||
+ | @Override | ||
public void onTurnEnded(Event e) { | public void onTurnEnded(Event e) { | ||
super.onTurnEnded(e); | super.onTurnEnded(e); | ||
− | + | ||
− | / | + | /* Don't move if we are scanning, this is mostly start |
− | |||
* of round fast radar finding protection | * of round fast radar finding protection | ||
*/ | */ | ||
if(initialTick != 0) | if(initialTick != 0) | ||
return; | return; | ||
− | + | ||
+ | /* We track the gun heat because it is important | ||
+ | * for our heat/imaginary waves | ||
+ | */ | ||
+ | enemyGunHeat -= coolingRate; | ||
+ | if(enemyGunHeat <= 0) | ||
+ | enemyGunHeat = 0; | ||
+ | |||
+ | /* Take are of all waves that cannot possibly even conceivably hit us anymore */ | ||
checkWaves(); | checkWaves(); | ||
− | + | ||
+ | /* Run our 'actual' movement */ | ||
doMovement(); | doMovement(); | ||
} | } | ||
− | + | ||
− | + | private void checkWaves() { | |
− | + | Iterator<Wave<Data>> waveIterator = enemyWaves.iterator(); | |
− | + | while(waveIterator.hasNext()) { | |
− | + | Wave<Data> wave = waveIterator.next(); | |
+ | |||
+ | double radius = wave.getRadius(time); | ||
+ | |||
+ | /* /////////////////////// */ | ||
+ | /* DRAW WAVE */ | ||
+ | g.setColor(new Color(128,128,128)); | ||
+ | double escape = Tools.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(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | private Wave<Data> findBestSurfableWave() { | ||
+ | double halfBotWidth = 18 + Math.sin(lastEnemyPosition.angleTo(myPosition))*7.4558441; | ||
//find the wave soonest to hit us | //find the wave soonest to hit us | ||
− | Wave wave = null; | + | Wave<Data> wave = null; |
double bestETA = Double.POSITIVE_INFINITY; | double bestETA = Double.POSITIVE_INFINITY; | ||
− | for(Wave check : enemyWaves) { | + | for(Wave<Data> check : enemyWaves) { |
double distance = myPosition.distance(check) - check.getRadius(time); | double distance = myPosition.distance(check) - check.getRadius(time); | ||
double ETA = distance / check.speed; | double ETA = distance / check.speed; | ||
− | + | ||
+ | if(distance < 0 && Math.abs(distance) + check.speed > halfBotWidth) | ||
+ | continue; | ||
+ | |||
if(ETA < bestETA) { | if(ETA < bestETA) { | ||
//If we are already surfing a strong wave | //If we are already surfing a strong wave | ||
Line 237: | Line 289: | ||
//shot first for this to occur, which is why | //shot first for this to occur, which is why | ||
//we don't need an else on the ETA < bestETA | //we don't need an else on the ETA < bestETA | ||
− | + | ||
//This is mostly just a hack for some score | //This is mostly just a hack for some score | ||
if(wave != null && ETA > bestETA - 3) { | if(wave != null && ETA > bestETA - 3) { | ||
Line 249: | Line 301: | ||
} | } | ||
} | } | ||
− | |||
} | } | ||
− | + | ||
− | double | + | return wave; |
− | double | + | } |
− | + | ||
− | // | + | |
+ | private void checkMove(Simulate sim, Wave<Data> wave, int orbitDirection) { | ||
+ | Move move = predictMove(sim.position,wave, | ||
+ | sim.heading,sim.velocity,orbitDirection); | ||
+ | |||
+ | sim.angleToTurn = move.angleToTurn; | ||
+ | sim.direction = move.direction; | ||
+ | sim.maxVelocity = Tools.min(sim.maxVelocity, move.maxVelocity); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Calculate the driving | ||
+ | */ | ||
+ | protected Move predictMove(Vector myPosition, Vector orbitCenter, | ||
+ | double myHeading, double myVelocity, int orbitDirection) { | ||
+ | Move data = new Move(); | ||
+ | |||
+ | /* Better safe then very very sorry */ | ||
+ | if(orbitDirection == 0) | ||
+ | orbitDirection = 1; | ||
+ | |||
+ | 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(Tools.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; | ||
+ | } | ||
+ | |||
+ | private void doMovement() { | ||
+ | Wave<Data> wave = findBestSurfableWave(); | ||
if(wave == null) { | if(wave == null) { | ||
− | + | doWavelessMovement(); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
return; | return; | ||
} | } | ||
− | + | ||
− | / | + | g.setColor(Color.WHITE); |
− | + | g.draw(field.toRectangle2D()); | |
+ | |||
+ | doSurfingMovement(wave); | ||
+ | } | ||
+ | |||
+ | private void doSurfingMovement(Wave<Data> wave) { | ||
+ | /* Do the actual surfing and such */ | ||
g.setColor(Color.ORANGE); | g.setColor(Color.ORANGE); | ||
− | + | ||
peer.setMaxVelocity(Rules.MAX_VELOCITY); | peer.setMaxVelocity(Rules.MAX_VELOCITY); | ||
− | + | ||
− | + | Risk[] directions = new Risk[] { | |
− | + | checkWaveRisk(wave,orbitDirection,0), | |
− | + | checkWaveRisk(wave,orbitDirection,Rules.MAX_VELOCITY), | |
− | + | checkWaveRisk(wave,-orbitDirection,Rules.MAX_VELOCITY), | |
− | |||
}; | }; | ||
− | + | ||
int bestIndex = 0; | int bestIndex = 0; | ||
− | double | + | double minRisk = Double.POSITIVE_INFINITY; |
− | for(int i = 0; i < directions.length; ++i) { | + | for(int i = 0; i < directions.length; ++i) { |
− | if(directions[i]. | + | if(directions[i].risk < minRisk) { |
bestIndex = i; | bestIndex = i; | ||
− | + | minRisk = directions[i].risk; | |
} | } | ||
} | } | ||
− | + | ||
− | + | Move move = predictMove(myPosition,lastEnemyPosition, | |
status.getHeadingRadians(),status.getVelocity(), | status.getHeadingRadians(),status.getVelocity(), | ||
directions[bestIndex].orbitDirection ); | directions[bestIndex].orbitDirection ); | ||
− | + | ||
− | + | peer.setMaxVelocity(Tools.min(directions[bestIndex].maxVelocity,move.maxVelocity)); | |
− | + | peer.setTurnBody(move.angleToTurn); | |
− | peer.setMaxVelocity( | + | peer.setMove(1000*move.direction); |
− | peer.setTurnBody( | ||
− | peer.setMove( | ||
peer.setCall(); | peer.setCall(); | ||
} | } | ||
− | + | ||
− | private | + | private Risk checkWaveRisk(Wave<Data> wave, int orbitDirection, double maxVelocity) { |
− | / | + | /* |
− | + | * Simplify the output to our direction chooser | |
− | + | */ | |
− | + | Risk waveRisk = new Risk(); | |
− | + | waveRisk.orbitDirection = orbitDirection; | |
− | + | waveRisk.maxVelocity = maxVelocity; | |
− | / | + | |
− | + | /* | |
+ | * Create a simulator for our movement | ||
+ | */ | ||
Simulate sim = createSimulator(); | Simulate sim = createSimulator(); | ||
sim.maxVelocity = maxVelocity; | sim.maxVelocity = maxVelocity; | ||
sim.direction = orbitDirection; | sim.direction = orbitDirection; | ||
− | + | ||
− | + | /* | |
− | sim. | + | * Used for our distance danger |
− | + | */ | |
− | + | double currentDistance = wave.distance(sim.position); | |
− | + | double predictedDistance = 0; | |
int intersected = 0; | int intersected = 0; | ||
− | + | ||
− | long timeOffset | + | long timeOffset = 0; |
− | + | ||
− | + | /* | |
− | / | + | * Reset the factors, so we do not get cross risk check contamination |
− | + | */ | |
wave.resetFactors(); | wave.resetFactors(); | ||
− | + | ||
− | while( | + | /* |
+ | * Since our radar is limited to 1200 and the slowest bullet moves at | ||
+ | * 11 per second, calculating past 110 is pointlessly impossible, so | ||
+ | * it is a safe number to use as our break point. | ||
+ | */ | ||
+ | while(timeOffset < 110) { | ||
//DRAW DANGER MOVEMENT | //DRAW DANGER MOVEMENT | ||
g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6); | g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6); | ||
− | + | ||
− | |||
if(wave.doesIntersect(sim.position, time+timeOffset)) { | if(wave.doesIntersect(sim.position, time+timeOffset)) { | ||
++intersected; | ++intersected; | ||
− | + | predictedDistance += wave.distance(sim.position); | |
} else if(intersected > 0) { | } else if(intersected > 0) { | ||
− | / | + | predictedDistance /= intersected; |
− | + | ||
− | + | waveRisk.risk += checkIntersectionRisk(wave,predictedDistance); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
break; | break; | ||
} | } | ||
− | + | ||
− | + | checkMove(sim,wave,orbitDirection); | |
− | |||
sim.step(); | sim.step(); | ||
sim.maxVelocity = maxVelocity; | sim.maxVelocity = maxVelocity; | ||
− | + | ||
− | if(++ | + | ++timeOffset; |
− | + | } | |
+ | |||
+ | if(intersected > 0) { | ||
+ | double distanceRisk = currentDistance / predictedDistance; | ||
+ | distanceRisk *= distanceRisk; | ||
+ | |||
+ | //distanceRisk = (distanceRisk + 1.0) / 2.0; | ||
+ | //System.out.println(distanceRisk); | ||
+ | |||
+ | waveRisk.risk *= distanceRisk; | ||
+ | } | ||
+ | |||
+ | |||
+ | return waveRisk; | ||
+ | } | ||
+ | |||
+ | private double checkIntersectionRisk(Wave<Data> wave, double predicted) { | ||
+ | double risk = 0; | ||
+ | double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0; | ||
+ | |||
+ | double factorRange = Tools.abs(wave.maxFactor - wave.minFactor); | ||
+ | double bandwidth = factorRange / Tools.abs(wave.escapeAngle); | ||
+ | |||
+ | List<Entry<Data>> list = tree.nearestNeighbor(wave.data.getWeightedDataArray(), | ||
+ | NearestNeighborsToUseInSurfing, false); | ||
+ | |||
+ | for(Entry<Data> e : list) { | ||
+ | Data data = e.value; | ||
+ | |||
+ | double entryRisk = 0.1/(1.0+Tools.abs(data.guessfactor-factorCenter)); | ||
+ | |||
+ | if(wave.minFactor < data.guessfactor && wave.maxFactor > data.guessfactor) { | ||
+ | entryRisk += 0.9; | ||
+ | } | ||
+ | |||
+ | double timeWeight = 1.0+AverageSingleRoundTime/(double)(globalTime-data.globalTime); | ||
+ | |||
+ | //kernel calculation neh? | ||
+ | |||
+ | double density = 0; | ||
+ | |||
+ | for(Entry<Data> ex : list) { | ||
+ | double ux = (e.value.guessfactor-data.guessfactor) / bandwidth; | ||
+ | double sw = ex.value.weight / (1.0+ex.distance); | ||
+ | density += Math.exp(-0.5*ux*ux)*sw; | ||
+ | } | ||
+ | |||
+ | //System.out.println(density); | ||
+ | |||
+ | double weight = (data.weight / (1.0+e.distance)) * timeWeight * density; | ||
+ | |||
+ | //System.out.println(weight); | ||
+ | //System.out.println(entryRisk); | ||
+ | |||
+ | risk += 1.0-(1.0/(1.0+weight*entryRisk)); | ||
} | } | ||
− | + | ||
− | + | return risk/NearestNeighborsToUseInSurfing; | |
− | return | ||
} | } | ||
− | private void | + | private void doWavelessMovement() { |
− | + | final int initialTurns = (int)Math.ceil(3.0/coolingRate+4.0); | |
− | + | double safeTurns = status.getGunHeat()/coolingRate; | |
− | + | ||
− | + | if(time < initialTurns) { | |
− | + | /* Do we have enough time to move around before they can start firing? */ | |
− | + | if(safeTurns > TimeBeforeFirstWaveToStartTurning) { | |
+ | doMinimalRiskMovement(); | ||
+ | } else { | ||
+ | Move data = predictMove(myPosition,lastEnemyPosition, | ||
+ | status.getHeadingRadians(),status.getVelocity(),orbitDirection); | ||
+ | peer.setTurnBody(data.angleToTurn); | ||
+ | peer.setMaxVelocity(0); | ||
+ | |||
+ | peer.setMove(0); | ||
+ | //peer.setTurnBody(0); | ||
+ | peer.setMaxVelocity(0); | ||
+ | } | ||
+ | } else { | ||
+ | /* | ||
+ | * Things are no longer safe, set our movement to `sane` | ||
+ | * | ||
+ | * TODO: RAM if enemy is disabled | ||
+ | */ | ||
+ | if(status.getOthers() == 0) { | ||
+ | /* | ||
+ | * No bullets in the air, enemy dead, we can do our victory DANCE (YAY!) | ||
+ | */ | ||
+ | doVictoryDance(); | ||
+ | } else { | ||
+ | //Some other thing to do when nothing is in the air?? | ||
+ | doMinimalRiskMovement(); | ||
+ | } | ||
+ | } | ||
} | } | ||
− | + | ||
− | + | protected void doMinimalRiskMovement() { | |
− | + | double heading = status.getHeadingRadians(); | |
− | + | double velocity = status.getVelocity(); | |
− | + | ||
− | + | Rectangle escapeField = new Rectangle(30,30,fieldWidth-60,fieldHeight-60); | |
− | + | g.setColor(Color.WHITE); | |
− | + | g.draw(escapeField.toRectangle2D()); | |
− | double | + | |
− | + | //Do minimal risk movement | |
− | + | Vector target = myPosition.copy(); | |
− | + | Vector bestTarget = myPosition; | |
− | + | double angle = 0; | |
− | + | double bestRisk = checkWavelessRisk(bestTarget); | |
− | + | double enemyDistance = myPosition.distance(lastEnemyPosition)+18; | |
− | // | + | while(angle < Math.PI*2) { |
− | + | double targetDistance = Tools.min(200,enemyDistance); | |
− | + | ||
− | double | + | target.setProject(myPosition, angle, targetDistance); |
− | + | if(escapeField.contains(target)) { | |
− | + | double risk = checkWavelessRisk(target); | |
− | + | if(risk < bestRisk) { | |
− | + | bestRisk = risk; | |
− | + | bestTarget = target.copy(); | |
− | + | } | |
− | + | g.setColor(Color.BLUE); | |
+ | g.drawRect((int)target.x-2, (int)target.y-2, 4, 4); | ||
+ | |||
+ | } | ||
+ | angle += Math.PI/32.0; | ||
} | } | ||
− | + | g.setColor(bodyColor); | |
− | + | g.drawRect((int)bestTarget.x-2, (int)bestTarget.y-2, 4, 4); | |
− | + | ||
− | + | double travelAngle = myPosition.angleTo(bestTarget); | |
− | + | ||
− | + | double forward = myPosition.distance(bestTarget); | |
− | if( | + | |
− | + | double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians()); | |
− | + | int direction = 1; | |
+ | |||
+ | if(Tools.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 | //Slow down so we do not ram head long into the walls and can instead turn to avoid them | ||
− | + | double maxVelocity = Rules.MAX_VELOCITY; | |
− | if(!field.contains(myPosition.projectNew( | + | |
− | + | if(!field.contains(myPosition.projectNew(heading, velocity*3.25))) | |
− | + | maxVelocity = 0; | |
− | if(!field.contains(myPosition.projectNew( | + | |
− | + | if(!field.contains(myPosition.projectNew(heading, velocity*5))) | |
− | + | maxVelocity = 4; | |
− | return | + | |
+ | if(angleToTurn > 1.0) { | ||
+ | maxVelocity = 0; | ||
+ | } | ||
+ | |||
+ | peer.setMaxVelocity(maxVelocity); | ||
+ | peer.setTurnBody(angleToTurn); | ||
+ | peer.setMove(forward*direction); | ||
+ | } | ||
+ | |||
+ | private double checkWavelessRisk(Vector pos) { | ||
+ | double risk = lastEnemyEnergy/pos.distanceSq(lastEnemyPosition); | ||
+ | |||
+ | for(Line edge : field.getLines()) { | ||
+ | risk += 5.0/(1.0+edge.ptSegDistSq(pos)); | ||
+ | } | ||
+ | |||
+ | g.setColor(Color.RED); | ||
+ | /* | ||
+ | * get points between enemy location and corner and add risk!!!! | ||
+ | * these are bad places to be! Our hitbox is larger here if nothing else! | ||
+ | */ | ||
+ | for(Vector corner : field.getCorners()) { | ||
+ | corner.add(lastEnemyPosition); | ||
+ | corner.scale(0.5); | ||
+ | if(corner.distanceSq(lastEnemyPosition) < 22500 /* 150 */) { | ||
+ | g.drawRect((int)corner.x-2, (int)corner.y-2, 4, 4); | ||
+ | } | ||
+ | risk += 5.0/(1.0+corner.distanceSq(pos)); | ||
+ | } | ||
+ | |||
+ | return risk; | ||
} | } | ||
− | + | ||
/** | /** | ||
* Account for our bullets hitting the enemy | * Account for our bullets hitting the enemy | ||
*/ | */ | ||
+ | @Override | ||
public void onBulletHit(BulletHitEvent e) { | public void onBulletHit(BulletHitEvent e) { | ||
super.onBulletHit(e); | super.onBulletHit(e); | ||
− | + | ||
− | / | + | /* Get the power of the bullet of ours that hit */ |
double bulletPower = e.getBullet().getPower(); | 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); | lastEnemyEnergy -= Rules.getBulletDamage(bulletPower); | ||
} | } | ||
− | + | ||
/** | /** | ||
* Account for one of our bullets hitting an enemy bullet. | * Account for one of our bullets hitting an enemy bullet. | ||
*/ | */ | ||
+ | |||
+ | @Override | ||
public void onBulletHitBullet(BulletHitBulletEvent e) { | public void onBulletHitBullet(BulletHitBulletEvent e) { | ||
super.onBulletHitBullet(e); | super.onBulletHitBullet(e); | ||
− | + | ||
handleBullet(e.getHitBullet()); | handleBullet(e.getHitBullet()); | ||
} | } | ||
− | + | ||
/** | /** | ||
* Account for one of the enemies bullets hitting us. | * Account for one of the enemies bullets hitting us. | ||
*/ | */ | ||
+ | @Override | ||
public void onHitByBullet(HitByBulletEvent e) { | public void onHitByBullet(HitByBulletEvent e) { | ||
super.onHitByBullet(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()); | lastEnemyEnergy += Rules.getBulletHitBonus(e.getPower()); | ||
− | + | ||
handleBullet(e.getBullet()); | handleBullet(e.getBullet()); | ||
} | } | ||
− | + | ||
/** | /** | ||
* Handle an actual enemy bullet | * Handle an actual enemy bullet | ||
Line 506: | Line 702: | ||
private void handleBullet(Bullet b) { | private void handleBullet(Bullet b) { | ||
Vector bulletPosition = new Vector(b.getX(),b.getY()); | Vector bulletPosition = new Vector(b.getX(),b.getY()); | ||
− | + | ||
− | / | + | /* Find the matching wave */ |
− | Iterator<Wave> waveIterator = enemyWaves.iterator(); | + | Iterator<Wave<Data>> waveIterator = enemyWaves.iterator(); |
while(waveIterator.hasNext()) { | while(waveIterator.hasNext()) { | ||
− | Wave wave = waveIterator.next(); | + | Wave<Data> wave = waveIterator.next(); |
− | + | ||
− | / | + | /* the current distance of the wave */ |
double radius = wave.speed*(time - wave.fireTime); | 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( | + | */ |
− | + | if(Tools.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( | + | */ |
− | / | + | if(Tools.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. */ |
− | + | /* updateTree(wave,wave.angleTo(bulletPosition)); */ | |
− | + | updateTree(wave,b.getHeadingRadians()); | |
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
waveIterator.remove(); | waveIterator.remove(); | ||
− | + | ||
− | + | return; | |
} | } | ||
} | } | ||
} | } | ||
+ | |||
+ | /* If not found say so, cause someone (me) SCREWED UP!!! */ | ||
+ | println("Error: Unknown Bullet Collision"); | ||
+ | println("\tBullet:["+b.getX()+","+b.getY()+"] @ h"+b.getHeadingRadians()+" @ p" + b.getPower()); | ||
+ | } | ||
+ | |||
+ | private void updateTree(Wave<Data> wave, double angleToBullet) { | ||
+ | double angleOffset = Utils.normalRelativeAngle(angleToBullet - wave.directAngle); | ||
+ | |||
+ | wave.data.guessfactor = angleOffset / wave.escapeAngle; | ||
+ | |||
+ | //TODO | ||
+ | //tree.addPoint(wave.data.getDataArray(), wave.data); | ||
+ | tree.addPoint(wave.data.getWeightedDataArray(), wave.data); | ||
} | } | ||
+ | } | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Data gained from movement prediction | ||
+ | */ | ||
+ | class Move { | ||
+ | double angleToTurn = 0; | ||
+ | double maxVelocity = Rules.MAX_VELOCITY; | ||
+ | int direction = 1; | ||
+ | } | ||
+ | |||
+ | class Risk { | ||
+ | double risk; | ||
+ | double maxVelocity; | ||
+ | int orbitDirection; | ||
+ | } | ||
+ | |||
+ | public class State { | ||
+ | Vector myPosition; | ||
+ | Vector enemyPosition; | ||
+ | Data data = new Data(); | ||
+ | } | ||
+ | |||
+ | public class Data { | ||
+ | long globalTime; | ||
+ | |||
+ | double lateralVelocity; | ||
+ | double advancingVelcity; | ||
+ | double guessfactor; | ||
+ | |||
+ | double weight = 1.0; | ||
+ | |||
+ | public double[] getDataArray() { | ||
+ | return new double[] { | ||
+ | Math.abs(lateralVelocity)/8.0, | ||
+ | (advancingVelcity+8.0)/16.0, | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | public double[] getDataWeights() { | ||
+ | return new double[] { | ||
+ | 1, | ||
+ | 1, | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | public double[] getWeightedDataArray() { | ||
+ | double[] data = getDataArray(); | ||
+ | double[] weights = getDataWeights(); | ||
+ | for(int i=0;i<data.length;++i) { | ||
+ | data[i] *= weights[i]; | ||
+ | } | ||
+ | return data; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public class Wave<T> extends Vector { | ||
+ | public long fireTime; | ||
+ | public double escapeAngle; | ||
+ | public double directAngle; | ||
+ | public double speed; | ||
+ | public double power; | ||
+ | public boolean imaginary = false; | ||
+ | |||
+ | public T data; | ||
+ | |||
+ | public double getRadius(long time) { | ||
+ | return speed*(time - fireTime); | ||
+ | } | ||
+ | |||
+ | public int intersected = 0; | ||
/** | /** | ||
− | * | + | * Used to determine if we can safely remove this wave |
*/ | */ | ||
− | + | public final boolean didIntersect(Vector target, long time) { | |
− | + | hitbox.set(target, 36, 36); | |
− | + | ||
− | + | double radius = getRadius(time); | |
+ | double nextRadius = getRadius(time+1); | ||
+ | Vector[] current = Tools.intersectRectCircle(hitbox, this, radius); | ||
+ | Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius); | ||
+ | |||
+ | if(current.length != 0 || next.length != 0) { | ||
+ | ++intersected; | ||
+ | } else { | ||
+ | if(intersected > 0) { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return false; | ||
+ | } | ||
+ | |||
+ | public static Rectangle hitbox = new Rectangle(); | ||
+ | /** | ||
+ | * Used for calculating where a bullet will intersect a target | ||
+ | */ | ||
+ | public final boolean doesIntersect(Vector target, long time) { | ||
+ | hitbox.set(target, 36, 36); | ||
+ | |||
+ | double radius = getRadius(time); | ||
+ | double nextRadius = getRadius(time+1); | ||
+ | Vector[] current = Tools.intersectRectCircle(hitbox, this, radius); | ||
+ | Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius); | ||
+ | |||
+ | if(current.length != 0 || next.length != 0) { | ||
+ | for(Vector v : current) | ||
+ | expandFactors(v); | ||
− | + | for(Vector v : next) | |
+ | expandFactors(v); | ||
− | + | Vector[] corners = hitbox.getCorners(); | |
− | + | for(Vector v : corners) { | |
− | + | double distance = distanceSq(v); | |
− | + | if(distance < nextRadius*nextRadius | |
− | + | && distance > radius*radius) { | |
− | + | expandFactors(v); | |
+ | } | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | return true; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
+ | |||
+ | return false; | ||
+ | } | ||
+ | |||
+ | public double minFactor = Double.POSITIVE_INFINITY; | ||
+ | public double maxFactor = Double.NEGATIVE_INFINITY; | ||
+ | |||
+ | /** | ||
+ | * Expand the guessfactors based on target location | ||
+ | */ | ||
+ | private void expandFactors(Vector pos) { | ||
+ | double angle = Utils.normalRelativeAngle(angleTo(pos) - directAngle) / escapeAngle; | ||
+ | if(angle < minFactor) minFactor = angle; | ||
+ | if(angle > maxFactor) maxFactor = angle; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Reset our factors for calculating position (important!) | ||
+ | */ | ||
+ | public void resetFactors() { | ||
+ | minFactor = Double.POSITIVE_INFINITY; | ||
+ | maxFactor = Double.NEGATIVE_INFINITY; | ||
} | } | ||
} | } | ||
+ | |||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 15:35, 5 August 2011
Code for version MC25
/**
* Copyright (c) 2011 Chase
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*/
/*
* 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.
*/
package cs.move;
import java.awt.Color;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import cs.Chikaku;
import cs.geom.*;
import cs.utils.*;
import ags.utils.KdTree;
import ags.utils.KdTree.Entry;
import robocode.*;
import robocode.util.Utils;
/**
* Movement
* <br/><br/>
* In Japanese, Kakeru means "Dash" (in some cases)
* @author Chase
*/
public abstract class Kakeru extends Chikaku {
private static final int MaxTurnHistoryLength = 20;
private static final int MaxHeatWaveQueueLength = 4;
private static final int AverageSingleRoundTime = 1200;
private static final int NearestNeighborsToUseInSurfing = 16;
/**
* Keep in mind with heat waves we will have waves 2 rounds earlier
* the the gun heat would otherwise indicate
*/
private static final int TimeBeforeFirstWaveToStartTurning = 4;
private static final MaximumDeque<Double> enemyBulletPower;
private static final KdTree<Data> tree;
static {
enemyBulletPower = new MaximumDeque<Double>(MaxHeatWaveQueueLength);
enemyBulletPower.addLast(3.0);
int dimensions = new Data().getDataArray().length;
tree = new KdTree.SqrEuclid<Data>(dimensions, 0);
Data data = new Data();
data.weight = 0.1;
tree.addPoint(data.getDataArray(), data);
}
private final LinkedList<Wave<Data>> enemyWaves;
private final MaximumDeque<State> history;
private Vector lastEnemyPosition;
private double lastEnemyEnergy;
private double lastEnemyVelocity;
private double enemyGunHeat;
private int orbitDirection = 1;
private boolean haveFiredImaginaryWave = false;
public Kakeru() {
enemyWaves = new LinkedList<Wave<Data>>();
history = new MaximumDeque<State>(MaxTurnHistoryLength);
lastEnemyPosition = new Vector(-1,-1);
lastEnemyEnergy = 100;
}
@Override
public void onRoundStarted(Event e) {
super.onRoundStarted(e);
/* Set it to be the same as our gun heat on round start! */
enemyGunHeat = status.getGunHeat();
}
@Override
public void onScannedRobot(ScannedRobotEvent e) {
super.onScannedRobot(e);
double angleToEnemy = e.getBearingRadians() + status.getHeadingRadians();
double enemyVelocity = e.getVelocity();
State state = new State();
state.myPosition = myPosition;
state.enemyPosition = myPosition.projectNew(angleToEnemy, e.getDistance());
state.data.lateralVelocity = status.getVelocity()*Math.sin(e.getBearingRadians());
state.data.advancingVelcity = status.getVelocity()*Math.cos(e.getBearingRadians());
state.data.globalTime = globalTime;
history.addFirst(state);
int direction = Tools.sign(state.data.lateralVelocity);
orbitDirection = direction;
if(Math.abs(enemyVelocity) == 0 && lastEnemyVelocity > 2) {
//Hit the wall!
lastEnemyEnergy -= Rules.getWallHitDamage(lastEnemyVelocity);
}
/*
* Fire new imaginary wave if we can, heat waves
* are always 2 ticks previous to a real one 'possibly'
* being fired
*/
if(!haveFiredImaginaryWave) {
if(enemyGunHeat <= 2.0*peer.getGunCoolingRate()) {
haveFiredImaginaryWave = true;
double power = 0;
for(Double d : enemyBulletPower)
power += d;
power /= enemyBulletPower.size();
/* Create the Wave */
Wave<Data> wave = createMovementWave(power,direction);
wave.set(simulatedEnemyPosition);
wave.imaginary = true;
/*
* Only +1 because real waves are -1 in time,
* meaning that by the time we detect them, it has
* already been 1 turn since it was fired, meaning
* heat waves +2 makes it only +1 here. :-)
*/
wave.fireTime = time + 1;
wave.directAngle = wave.angleTo(state.myPosition);
wave.data = state.data;
enemyWaves.add(wave);
}
}
/* Check for new enemy wave */
double enemyEnergy = e.getEnergy();
double energyDelta = lastEnemyEnergy - enemyEnergy;
if(energyDelta > 0 && energyDelta <= 3.0) {
enemyGunHeat += Rules.getGunHeat(energyDelta);
haveFiredImaginaryWave = false;
State old = history.get(2);
direction = Tools.sign(old.data.lateralVelocity);
/* Create the Wave */
Wave<Data> wave = createMovementWave(energyDelta,direction);
wave.set(lastEnemyPosition);
wave.fireTime = time - 1;
wave.directAngle = wave.angleTo(old.myPosition);
wave.data = old.data;
enemyBulletPower.addLast(wave.power);
enemyWaves.add(wave);
}
lastEnemyPosition = state.enemyPosition;
lastEnemyVelocity = enemyVelocity;
lastEnemyEnergy = enemyEnergy;
}
private Wave<Data> createMovementWave(double power, int direction) {
Wave<Data> wave = new Wave<Data>();
wave.power = power;
wave.speed = Rules.getBulletSpeed(wave.power);
wave.escapeAngle = Math.asin(8.0/wave.speed) * direction;
return wave;
}
@Override
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;
/* We track the gun heat because it is important
* for our heat/imaginary waves
*/
enemyGunHeat -= coolingRate;
if(enemyGunHeat <= 0)
enemyGunHeat = 0;
/* Take are of all waves that cannot possibly even conceivably hit us anymore */
checkWaves();
/* Run our 'actual' movement */
doMovement();
}
private void checkWaves() {
Iterator<Wave<Data>> waveIterator = enemyWaves.iterator();
while(waveIterator.hasNext()) {
Wave<Data> wave = waveIterator.next();
double radius = wave.getRadius(time);
/* /////////////////////// */
/* DRAW WAVE */
g.setColor(new Color(128,128,128));
double escape = Tools.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();
}
}
}
private Wave<Data> findBestSurfableWave() {
double halfBotWidth = 18 + Math.sin(lastEnemyPosition.angleTo(myPosition))*7.4558441;
//find the wave soonest to hit us
Wave<Data> wave = null;
double bestETA = Double.POSITIVE_INFINITY;
for(Wave<Data> check : enemyWaves) {
double distance = myPosition.distance(check) - check.getRadius(time);
double ETA = distance / check.speed;
if(distance < 0 && Math.abs(distance) + check.speed > halfBotWidth)
continue;
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;
}
}
}
return wave;
}
private void checkMove(Simulate sim, Wave<Data> wave, int orbitDirection) {
Move move = predictMove(sim.position,wave,
sim.heading,sim.velocity,orbitDirection);
sim.angleToTurn = move.angleToTurn;
sim.direction = move.direction;
sim.maxVelocity = Tools.min(sim.maxVelocity, move.maxVelocity);
}
/**
* Calculate the driving
*/
protected Move predictMove(Vector myPosition, Vector orbitCenter,
double myHeading, double myVelocity, int orbitDirection) {
Move data = new Move();
/* Better safe then very very sorry */
if(orbitDirection == 0)
orbitDirection = 1;
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(Tools.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;
}
private void doMovement() {
Wave<Data> wave = findBestSurfableWave();
if(wave == null) {
doWavelessMovement();
return;
}
g.setColor(Color.WHITE);
g.draw(field.toRectangle2D());
doSurfingMovement(wave);
}
private void doSurfingMovement(Wave<Data> wave) {
/* Do the actual surfing and such */
g.setColor(Color.ORANGE);
peer.setMaxVelocity(Rules.MAX_VELOCITY);
Risk[] directions = new Risk[] {
checkWaveRisk(wave,orbitDirection,0),
checkWaveRisk(wave,orbitDirection,Rules.MAX_VELOCITY),
checkWaveRisk(wave,-orbitDirection,Rules.MAX_VELOCITY),
};
int bestIndex = 0;
double minRisk = Double.POSITIVE_INFINITY;
for(int i = 0; i < directions.length; ++i) {
if(directions[i].risk < minRisk) {
bestIndex = i;
minRisk = directions[i].risk;
}
}
Move move = predictMove(myPosition,lastEnemyPosition,
status.getHeadingRadians(),status.getVelocity(),
directions[bestIndex].orbitDirection );
peer.setMaxVelocity(Tools.min(directions[bestIndex].maxVelocity,move.maxVelocity));
peer.setTurnBody(move.angleToTurn);
peer.setMove(1000*move.direction);
peer.setCall();
}
private Risk checkWaveRisk(Wave<Data> wave, int orbitDirection, double maxVelocity) {
/*
* Simplify the output to our direction chooser
*/
Risk waveRisk = new Risk();
waveRisk.orbitDirection = orbitDirection;
waveRisk.maxVelocity = maxVelocity;
/*
* Create a simulator for our movement
*/
Simulate sim = createSimulator();
sim.maxVelocity = maxVelocity;
sim.direction = orbitDirection;
/*
* Used for our distance danger
*/
double currentDistance = wave.distance(sim.position);
double predictedDistance = 0;
int intersected = 0;
long timeOffset = 0;
/*
* Reset the factors, so we do not get cross risk check contamination
*/
wave.resetFactors();
/*
* Since our radar is limited to 1200 and the slowest bullet moves at
* 11 per second, calculating past 110 is pointlessly impossible, so
* it is a safe number to use as our break point.
*/
while(timeOffset < 110) {
//DRAW DANGER MOVEMENT
g.drawOval((int)sim.position.x-3, (int)sim.position.y-3, 6, 6);
if(wave.doesIntersect(sim.position, time+timeOffset)) {
++intersected;
predictedDistance += wave.distance(sim.position);
} else if(intersected > 0) {
predictedDistance /= intersected;
waveRisk.risk += checkIntersectionRisk(wave,predictedDistance);
break;
}
checkMove(sim,wave,orbitDirection);
sim.step();
sim.maxVelocity = maxVelocity;
++timeOffset;
}
if(intersected > 0) {
double distanceRisk = currentDistance / predictedDistance;
distanceRisk *= distanceRisk;
//distanceRisk = (distanceRisk + 1.0) / 2.0;
//System.out.println(distanceRisk);
waveRisk.risk *= distanceRisk;
}
return waveRisk;
}
private double checkIntersectionRisk(Wave<Data> wave, double predicted) {
double risk = 0;
double factorCenter = (wave.minFactor + wave.maxFactor) / 2.0;
double factorRange = Tools.abs(wave.maxFactor - wave.minFactor);
double bandwidth = factorRange / Tools.abs(wave.escapeAngle);
List<Entry<Data>> list = tree.nearestNeighbor(wave.data.getWeightedDataArray(),
NearestNeighborsToUseInSurfing, false);
for(Entry<Data> e : list) {
Data data = e.value;
double entryRisk = 0.1/(1.0+Tools.abs(data.guessfactor-factorCenter));
if(wave.minFactor < data.guessfactor && wave.maxFactor > data.guessfactor) {
entryRisk += 0.9;
}
double timeWeight = 1.0+AverageSingleRoundTime/(double)(globalTime-data.globalTime);
//kernel calculation neh?
double density = 0;
for(Entry<Data> ex : list) {
double ux = (e.value.guessfactor-data.guessfactor) / bandwidth;
double sw = ex.value.weight / (1.0+ex.distance);
density += Math.exp(-0.5*ux*ux)*sw;
}
//System.out.println(density);
double weight = (data.weight / (1.0+e.distance)) * timeWeight * density;
//System.out.println(weight);
//System.out.println(entryRisk);
risk += 1.0-(1.0/(1.0+weight*entryRisk));
}
return risk/NearestNeighborsToUseInSurfing;
}
private void doWavelessMovement() {
final int initialTurns = (int)Math.ceil(3.0/coolingRate+4.0);
double safeTurns = status.getGunHeat()/coolingRate;
if(time < initialTurns) {
/* Do we have enough time to move around before they can start firing? */
if(safeTurns > TimeBeforeFirstWaveToStartTurning) {
doMinimalRiskMovement();
} else {
Move data = predictMove(myPosition,lastEnemyPosition,
status.getHeadingRadians(),status.getVelocity(),orbitDirection);
peer.setTurnBody(data.angleToTurn);
peer.setMaxVelocity(0);
peer.setMove(0);
//peer.setTurnBody(0);
peer.setMaxVelocity(0);
}
} else {
/*
* Things are no longer safe, set our movement to `sane`
*
* TODO: RAM if enemy is disabled
*/
if(status.getOthers() == 0) {
/*
* No bullets in the air, enemy dead, we can do our victory DANCE (YAY!)
*/
doVictoryDance();
} else {
//Some other thing to do when nothing is in the air??
doMinimalRiskMovement();
}
}
}
protected void doMinimalRiskMovement() {
double heading = status.getHeadingRadians();
double velocity = status.getVelocity();
Rectangle escapeField = new Rectangle(30,30,fieldWidth-60,fieldHeight-60);
g.setColor(Color.WHITE);
g.draw(escapeField.toRectangle2D());
//Do minimal risk movement
Vector target = myPosition.copy();
Vector bestTarget = myPosition;
double angle = 0;
double bestRisk = checkWavelessRisk(bestTarget);
double enemyDistance = myPosition.distance(lastEnemyPosition)+18;
while(angle < Math.PI*2) {
double targetDistance = Tools.min(200,enemyDistance);
target.setProject(myPosition, angle, targetDistance);
if(escapeField.contains(target)) {
double risk = checkWavelessRisk(target);
if(risk < bestRisk) {
bestRisk = risk;
bestTarget = target.copy();
}
g.setColor(Color.BLUE);
g.drawRect((int)target.x-2, (int)target.y-2, 4, 4);
}
angle += Math.PI/32.0;
}
g.setColor(bodyColor);
g.drawRect((int)bestTarget.x-2, (int)bestTarget.y-2, 4, 4);
double travelAngle = myPosition.angleTo(bestTarget);
double forward = myPosition.distance(bestTarget);
double angleToTurn = Utils.normalRelativeAngle(travelAngle - status.getHeadingRadians());
int direction = 1;
if(Tools.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
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;
if(angleToTurn > 1.0) {
maxVelocity = 0;
}
peer.setMaxVelocity(maxVelocity);
peer.setTurnBody(angleToTurn);
peer.setMove(forward*direction);
}
private double checkWavelessRisk(Vector pos) {
double risk = lastEnemyEnergy/pos.distanceSq(lastEnemyPosition);
for(Line edge : field.getLines()) {
risk += 5.0/(1.0+edge.ptSegDistSq(pos));
}
g.setColor(Color.RED);
/*
* get points between enemy location and corner and add risk!!!!
* these are bad places to be! Our hitbox is larger here if nothing else!
*/
for(Vector corner : field.getCorners()) {
corner.add(lastEnemyPosition);
corner.scale(0.5);
if(corner.distanceSq(lastEnemyPosition) < 22500 /* 150 */) {
g.drawRect((int)corner.x-2, (int)corner.y-2, 4, 4);
}
risk += 5.0/(1.0+corner.distanceSq(pos));
}
return risk;
}
/**
* Account for our bullets hitting the enemy
*/
@Override
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.
*/
@Override
public void onBulletHitBullet(BulletHitBulletEvent e) {
super.onBulletHitBullet(e);
handleBullet(e.getHitBullet());
}
/**
* Account for one of the enemies bullets hitting us.
*/
@Override
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<Data>> waveIterator = enemyWaves.iterator();
while(waveIterator.hasNext()) {
Wave<Data> 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(Tools.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(Tools.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. */
/* updateTree(wave,wave.angleTo(bulletPosition)); */
updateTree(wave,b.getHeadingRadians());
waveIterator.remove();
return;
}
}
}
/* If not found say so, cause someone (me) SCREWED UP!!! */
println("Error: Unknown Bullet Collision");
println("\tBullet:["+b.getX()+","+b.getY()+"] @ h"+b.getHeadingRadians()+" @ p" + b.getPower());
}
private void updateTree(Wave<Data> wave, double angleToBullet) {
double angleOffset = Utils.normalRelativeAngle(angleToBullet - wave.directAngle);
wave.data.guessfactor = angleOffset / wave.escapeAngle;
//TODO
//tree.addPoint(wave.data.getDataArray(), wave.data);
tree.addPoint(wave.data.getWeightedDataArray(), wave.data);
}
}
/**
* Data gained from movement prediction
*/
class Move {
double angleToTurn = 0;
double maxVelocity = Rules.MAX_VELOCITY;
int direction = 1;
}
class Risk {
double risk;
double maxVelocity;
int orbitDirection;
}
public class State {
Vector myPosition;
Vector enemyPosition;
Data data = new Data();
}
public class Data {
long globalTime;
double lateralVelocity;
double advancingVelcity;
double guessfactor;
double weight = 1.0;
public double[] getDataArray() {
return new double[] {
Math.abs(lateralVelocity)/8.0,
(advancingVelcity+8.0)/16.0,
};
}
public double[] getDataWeights() {
return new double[] {
1,
1,
};
}
public double[] getWeightedDataArray() {
double[] data = getDataArray();
double[] weights = getDataWeights();
for(int i=0;i<data.length;++i) {
data[i] *= weights[i];
}
return data;
}
}
public class Wave<T> extends Vector {
public long fireTime;
public double escapeAngle;
public double directAngle;
public double speed;
public double power;
public boolean imaginary = false;
public T data;
public double getRadius(long time) {
return speed*(time - fireTime);
}
public int intersected = 0;
/**
* Used to determine if we can safely remove this wave
*/
public final boolean didIntersect(Vector target, long time) {
hitbox.set(target, 36, 36);
double radius = getRadius(time);
double nextRadius = getRadius(time+1);
Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
if(current.length != 0 || next.length != 0) {
++intersected;
} else {
if(intersected > 0) {
return true;
}
}
return false;
}
public static Rectangle hitbox = new Rectangle();
/**
* Used for calculating where a bullet will intersect a target
*/
public final boolean doesIntersect(Vector target, long time) {
hitbox.set(target, 36, 36);
double radius = getRadius(time);
double nextRadius = getRadius(time+1);
Vector[] current = Tools.intersectRectCircle(hitbox, this, radius);
Vector[] next = Tools.intersectRectCircle(hitbox, this, nextRadius);
if(current.length != 0 || next.length != 0) {
for(Vector v : current)
expandFactors(v);
for(Vector v : next)
expandFactors(v);
Vector[] corners = hitbox.getCorners();
for(Vector v : corners) {
double distance = distanceSq(v);
if(distance < nextRadius*nextRadius
&& distance > radius*radius) {
expandFactors(v);
}
}
return true;
}
return false;
}
public double minFactor = Double.POSITIVE_INFINITY;
public double maxFactor = Double.NEGATIVE_INFINITY;
/**
* Expand the guessfactors based on target location
*/
private void expandFactors(Vector pos) {
double angle = Utils.normalRelativeAngle(angleTo(pos) - directAngle) / escapeAngle;
if(angle < minFactor) minFactor = angle;
if(angle > maxFactor) maxFactor = angle;
}
/**
* Reset our factors for calculating position (important!)
*/
public void resetFactors() {
minFactor = Double.POSITIVE_INFINITY;
maxFactor = Double.NEGATIVE_INFINITY;
}
}