Difference between revisions of "User:Chase-san/NewTech/KakeruCode"

From Robowiki
Jump to navigation Jump to search
m
m
Line 40: Line 40:
 
  */
 
  */
 
public abstract class Kakeru extends Chikaku {
 
public abstract class Kakeru extends Chikaku {
//Dear Future Chase,
 
//
 
//
 
// But please
 
//
 
// Cheers,
 
// Current Chase
 
 
 
 
private static class TurnEntry {
 
private static class TurnEntry {

Revision as of 09:43, 28 July 2011

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"
 * @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();
			}
		}
	}
}