Difference between revisions of "BrokenSword/Code"

From Robowiki
Jump to navigation Jump to search
m (source code category)
m (Undo revision 31282 by Broksword (talk))
 
(4 intermediate revisions by 4 users not shown)
Line 1: Line 1:
The source code for BrokenSword 1.0. Code size is 1443.
+
{{Navbox small
 
+
| parent = BrokenSword
 +
| title        = Sub-pages
 +
| page1        = Version History
 +
| page2        = Code
 +
}}
 
----
 
----
 +
The source code for [[BrokenSword]] 2.0.0. Code size is 1499.
  
<pre>
+
<syntaxhighlight>
 
package voidious.mini;
 
package voidious.mini;
 
import robocode.*;
 
import robocode.util.Utils;
 
  
 
import java.awt.Color;
 
import java.awt.Color;
 
import java.awt.geom.Point2D;
 
import java.awt.geom.Point2D;
 +
import java.awt.geom.Rectangle2D;
 
import java.util.ArrayList;
 
import java.util.ArrayList;
 
import java.util.HashMap;
 
import java.util.HashMap;
 +
import java.util.List;
 +
import java.util.Map;
  
public class BrokenSword extends AdvancedRobot {
+
import robocode.AdvancedRobot;
    static final int GF_ZERO = 23;
+
import robocode.Condition;
    static final int GF_ONE = 46;
+
import robocode.RobotDeathEvent;
 +
import robocode.Rules;
 +
import robocode.ScannedRobotEvent;
 +
import robocode.util.Utils;
  
    protected static java.awt.geom.Rectangle2D.Double _fieldRect;
+
/**
    protected static Point2D.Double _myLocation;
+
* Copyright (c) 2012 - Voidious
    protected static HashMap _enemyLocationsByName = new HashMap();
+
*
    protected static double _randomDirChangeAmount;
+
* This software is provided 'as-is', without any express or implied
    protected long _timeSinceDirChangeCounter;
+
* warranty. In no event will the authors be held liable for any damages
    protected static double _lastHeading;
+
* arising from the use of this software.
    protected ArrayList _recentLocations;
+
*
 +
* 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.
 +
*
 +
*    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.
 +
*/
  
    protected double _targetRating = Double.POSITIVE_INFINITY;
+
/**
    protected static String _targetName = "";
+
* A MiniBot melee specialist. Uses Minimum Risk movement and Shadow/Melee Gun.
 +
* Movement based on its little brother, BlitzBat.
 +
*/
  
    protected static final double WAVE_BULLET_POWER = 1.99;
+
public class BrokenSword extends AdvancedRobot {
    protected static final double WAVE_BULLET_VELOCITY = 14.03;
+
  private static final double TWO_PI = Math.PI * 2;
    protected static final double WAVE_MAX_ESCAPE_ANGLE = 0.62;
 
    protected static double _bulletPower;
 
  
    private static int _radarDirection = 1;
+
  private static Rectangle2D.Double _battleField;
 +
  private static Point2D.Double _destination;
 +
  private static String _nearestName;
 +
  private static double _nearestDistance;
 +
  private static Map<String, EnemyData> _enemies =
 +
      new HashMap<String, EnemyData>();
 +
  private static List<Point2D.Double> _recentLocations;
 +
 
 +
  public void run() {
 +
    setAdjustGunForRobotTurn(true);
 +
    setAdjustRadarForGunTurn(true);
 +
    setColors(Color.black, Color.black, new Color(141, 220, 175));
 +
   
 +
    _battleField = new Rectangle2D.Double(50, 50,
 +
        getBattleFieldWidth() - 100, getBattleFieldHeight() - 100);
 +
    _recentLocations = new ArrayList<Point2D.Double>();
 +
    _nearestDistance = Double.POSITIVE_INFINITY;
 +
    _destination = null;
 
      
 
      
public void run() {
+
    do {
        setAdjustGunForRobotTurn(true);
+
      Point2D.Double myLocation = myLocation();
        setAdjustRadarForGunTurn(true);
+
      _recentLocations.add(0, myLocation);
setColors(new Color(141, 220, 175), Color.white, Color.white);
 
        _recentLocations = new ArrayList();
 
  
    _fieldRect = new java.awt.geom.Rectangle2D.Double(30, 30,
+
      //***********************************************************************
                getBattleFieldWidth() - 60, getBattleFieldHeight() - 60);
+
      // Gun
 +
      double bulletPower = 3 - ((20 - getEnergy()) / 6);
 +
      if (getGunTurnRemaining() == 0) {
 +
        setFire(bulletPower);
 +
      }
  
do {
+
      List<MeleeFiringAngle> firingAngles = new ArrayList<MeleeFiringAngle>();
          setFireBullet(_bulletPower);
+
      for (EnemyData enemyData : _enemies.values()) {
          setTurnRadarRight(45 * _radarDirection);
+
        if (enemyData.alive) {
move();
+
          double enemyDistance = enemyData.distance(myLocation);
execute();
+
          int bulletTicks =
} while (true);
+
              (int) (enemyDistance / Rules.getBulletSpeed(bulletPower));
}
+
          for (Point2D.Double vector : enemyData.lastVectors) {
 +
            if (vector != null) {
 +
              Point2D.Double projectedLocation = project(enemyData,
 +
                  enemyData.heading + vector.x, vector.y * bulletTicks);
 +
              if (_battleField.contains(projectedLocation)) {
 +
                firingAngles.add(new MeleeFiringAngle(
 +
                    absoluteBearing(myLocation, projectedLocation),
 +
                    enemyDistance, 18 / enemyDistance));
 +
              }
 +
            }
 +
          }
 +
        }
 +
      }
  
    public void onScannedRobot(ScannedRobotEvent e) {
+
      try {
        String eName = e.getName();
+
        double bestDensity = 0;
        double eDistance = e.getDistance();
+
        for (int x = 0; x < 160; x++) {
        double eEnergy = e.getEnergy();
+
          double angle = Math.PI * x / 80;
        double eVelocity = e.getVelocity();
+
          double density = 0;
 +
          for (MeleeFiringAngle meleeAngle : firingAngles) {
 +
            double ux =
 +
                Math.abs(Utils.normalRelativeAngle(angle - meleeAngle.angle))
 +
                    / meleeAngle.bandwidth;
 +
            if (ux < 1) {
 +
              density += square(1 - square(ux)) / meleeAngle.distance;
 +
            }
 +
          }
 +
          if (density > bestDensity) {
 +
            bestDensity = density;
 +
            setTurnGunRightRadians(
 +
                Utils.normalRelativeAngle(angle - getGunHeadingRadians()));
 +
          }
 +
        }
 +
      } catch (NullPointerException npe) {
 +
        // expected before any scans
 +
      }
  
       
+
      //***********************************************************************
        ////////////////////////////////////////////////
+
      // Movement
         // General stuff
+
      double bestRisk;
         ////////////////////////////////////////////////
+
      try {
       
+
         bestRisk = evalDestinationRisk(_destination) * .85;
         double enemyAbsoluteBearing =  
+
      } catch (NullPointerException ex) {
        e.getBearingRadians() + getHeadingRadians();
+
         bestRisk = Double.POSITIVE_INFINITY;
 
+
      }
        if (!_enemyLocationsByName.containsKey(eName)) {
+
      try {
        _enemyLocationsByName.put(eName, new EnemyData());
+
         for (double d = 0; d < TWO_PI; d += 0.1) {
 +
          Point2D.Double newDest = project(myLocation, d,
 +
              Math.min(_nearestDistance, 100 + Math.random() * 500));
 +
          double thisRisk = evalDestinationRisk(newDest);
 +
          if (_battleField.contains(newDest) && thisRisk < bestRisk) {
 +
            bestRisk = thisRisk;
 +
            _destination = newDest;
 +
          }
 
         }
 
         }
        EnemyData eData = (EnemyData)_enemyLocationsByName.get(eName);
 
        eData.alive = true;
 
        eData.location = project(_myLocation, enemyAbsoluteBearing, eDistance);
 
        double absSinRelativeHeading = Math.abs(Math.sin(e.getHeadingRadians()
 
        - enemyAbsoluteBearing));
 
        eData.energy = eEnergy;
 
 
          
 
          
         if (getOthers() == 1 ||
+
         double angle = Utils.normalRelativeAngle(
            (_targetName.equals(eName) &&
+
            absoluteBearing(myLocation, _destination) - getHeadingRadians());
              (eDistance < 300 || (getGunHeat() < .8 && getOthers() <= 5)))) {  
+
        setTurnRightRadians(Math.tan(angle));
       
+
        setAhead(Math.cos(angle) * Double.POSITIVE_INFINITY);
        _radarDirection *= -1;  
+
      } catch (NullPointerException ex) {
 +
        // expected before we have a _destination
 +
      }
 +
     
 +
      //***********************************************************************
 +
      // Radar
 +
      setTurnRadarRightRadians(1);
 +
      try {
 +
        long stalestTime = Long.MAX_VALUE;
 +
        for (EnemyData enemyData : _enemies.values()) {
 +
          if (getTime() > 20 && enemyData.alive
 +
              && enemyData.lastScanTime < stalestTime) {
 +
            stalestTime = enemyData.lastScanTime;
 +
            setTurnRadarRightRadians(Math.signum(Utils.normalRelativeAngle(
 +
                absoluteBearing(myLocation, enemyData)
 +
                    - getRadarHeadingRadians())));
 +
          }
 
         }
 
         }
       
+
      } catch (NullPointerException npe) {
       
+
         // expected before we have any scans
         ////////////////////////////////////////////////
+
      }
        // Gun wave creation
+
      //***********************************************************************
        ////////////////////////////////////////////////
+
      execute();
 +
    } while (true);   
 +
  }
 +
 
 +
  public void onScannedRobot(ScannedRobotEvent e) {
 +
    double distance = e.getDistance();
 +
    String botName = e.getName();
 +
   
 +
    if (!_enemies.containsKey(botName)) {
 +
      _enemies.put(botName, new EnemyData());
 +
    }
  
        MicroWave w;
+
    DisplacementTimer timer;
        addCustomEvent(w = new MicroWave());
+
    addCustomEvent(timer = new DisplacementTimer());
        w.targetName = eName;
+
    EnemyData enemyData = timer.enemyData = _enemies.get(botName);
        w.sourceLocation = _myLocation;
+
    enemyData.energy = e.getEnergy();
        w.directAngle = enemyAbsoluteBearing;
+
    enemyData.alive = true;
        w.orientation = sign((eVelocity)*Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
+
    enemyData.lastScanTime = getTime();
  
        w.waveGuessFactors = eData.gunStats[(int)Math.abs(eVelocity) / 3][(int)(eDistance / 300)][(int)Math.min(2, absSinRelativeHeading * 3)];
+
    timer.displacementVector = (enemyData.lastVectors = enemyData.gunVectors
 +
        [(int) (distance / 300)]
 +
        [(int) (Math.abs(e.getVelocity()) / 4)])
 +
            [enemyData.nextIndex++ % 200] = new Point2D.Double(0, 0);
  
       
+
    enemyData.setLocation(timer.targetLocation = project(
        ////////////////////////////////////////////////
+
         myLocation(), e.getBearingRadians() + getHeadingRadians(),
         // Target selection and actual firing.
+
         distance));
         ////////////////////////////////////////////////
 
  
        double thisRating = (eEnergy * square(eDistance));  
+
    timer.bulletTicks = (int) (distance / 11);
        if (thisRating < (_targetRating * .8) || eName.equals(_targetName)) {
+
    timer.targetHeading = enemyData.heading = e.getHeadingRadians()
            _targetRating = thisRating;
+
         + (e.getVelocity() < 0 ? Math.PI : 0);
            _targetName = eName;
+
   
         }
+
    if (distance < _nearestDistance || botName.equals(_nearestName)) {
 
+
      _nearestDistance = distance;
        if (_targetName.equals(eName)) {
+
      _nearestName = botName;
     
 
            _bulletPower = 1.99;
 
           
 
            if (eDistance < 250 || getOthers() >= 6) {
 
            _bulletPower = 3;
 
            }
 
           
 
            if (eDistance > 600 && (getOthers() <= 2)) {
 
            _bulletPower = 1.4;
 
            }
 
           
 
            if (getEnergy() < 30 && eEnergy > getEnergy()) {
 
            _bulletPower = Math.min(_bulletPower, 2 - ((33 - getEnergy()) / 15));
 
            }
 
           
 
            int bestGF = GF_ZERO;
 
            double bestGFRank = 0;
 
            for (int x = 0; x < GF_ONE; x++) {
 
            if (w.waveGuessFactors[x] > bestGFRank) {
 
            bestGFRank = w.waveGuessFactors[x];
 
            bestGF = x;
 
            }
 
            }
 
                   
 
            setTurnGunRightRadians(Utils.normalRelativeAngle(
 
            enemyAbsoluteBearing - getGunHeadingRadians() +
 
            (e.getEnergy() > 0 ?
 
            (w.orientation
 
            * (maxEscapeAngle(bulletVelocity(_bulletPower)) / GF_ZERO)
 
            * (bestGF - GF_ZERO)) : 0)));       
 
        }
 
 
     }
 
     }
 +
  }
 +
 
 +
  public void onRobotDeath(RobotDeathEvent e) {
 +
    _enemies.get(e.getName()).alive = false;
 +
    _nearestDistance = Double.POSITIVE_INFINITY;
 +
  }
 +
 
 +
  private double evalDestinationRisk(Point2D.Double destination) {
 +
    double risk = 0;
 
      
 
      
     public void onRobotDeath(RobotDeathEvent e) {
+
     for (EnemyData enemy1 : _enemies.values()) {
    String eName = e.getName();
+
      double distSq = enemy1.distanceSq(destination);
           
+
      int closer = 0;
        if (_targetName.equals(eName)) {
+
      for (EnemyData enemy2 : _enemies.values()) {
            _targetName = "";
+
         if (enemy1.distanceSq(enemy2) < distSq) {
            _targetRating = Double.POSITIVE_INFINITY;
+
          closer++;
        }
 
       
 
        ((EnemyData)_enemyLocationsByName.get(eName)).alive = false;
 
    }
 
 
 
    protected void move() {
 
        _myLocation = new Point2D.Double(getX(), getY());
 
       
 
        if (Math.random() > 0.9) {
 
        _recentLocations.add(0, _myLocation);
 
        }
 
       
 
        double bestRisk = Double.POSITIVE_INFINITY;
 
        double bestAngle = 0;
 
        double currentHeading =
 
        getHeadingRadians() + (getVelocity() < 0?Math.PI:0);
 
       
 
        Object[] enemies =
 
            (_enemyLocationsByName.values().toArray());
 
       
 
      _timeSinceDirChangeCounter++;
 
         if (Math.abs(Utils.normalRelativeAngle(currentHeading - _lastHeading))
 
          > Math.PI / 4) {
 
        _timeSinceDirChangeCounter = 0;
 
        _randomDirChangeAmount = (Math.random() * Math.random() * 50);
 
 
         }
 
         }
       
+
      }
        for (double x = 0; x < 2; x += .04) {
 
            double testAngle = x * Math.PI;
 
            Point2D.Double testPoint = project(_myLocation, testAngle,
 
                50 + Math.random() * 200);
 
           
 
            if (_fieldRect.contains(testPoint)) {
 
                double testRisk = 0;
 
               
 
                for (int y = 0; y < enemies.length; y++) {
 
                    EnemyData eData = ((EnemyData)enemies[y]);
 
                    if (eData.alive) {
 
                        double distSquaredToEnemy =
 
                        testPoint.distanceSq(eData.location);
 
                       
 
                        testRisk +=
 
                            ((limit(0.5, (eData.energy / getEnergy()), 2)
 
                            / distSquaredToEnemy))
 
                            * (1 + square(Math.cos(absoluteBearing(
 
                                _myLocation, eData.location) - testAngle)))
 
                            ;
 
                    }
 
                }
 
                               
 
            if (Math.abs(Utils.normalRelativeAngle(currentHeading
 
                    - testAngle)) < (Math.PI / 4) && getOthers() <= 6) {
 
            testRisk /= 2;
 
            if (_timeSinceDirChangeCounter > _randomDirChangeAmount) {
 
                testRisk *= 20;
 
                }
 
                }
 
           
 
                try {
 
            for (int z = 0; z < 4 && z < _recentLocations.size(); z++) {
 
              testRisk *= (1 + (((400) - (50 * z)) / (testPoint.distanceSq((Point2D.Double)_recentLocations.get(z)))));
 
            }
 
                } catch (Exception e) { }
 
                               
 
                if (testRisk < bestRisk) {
 
                    bestRisk = testRisk;
 
                    bestAngle = x * Math.PI;
 
                }       
 
            }         
 
        }
 
             
 
        _lastHeading = currentHeading;
 
  
        moveWithBackAsFront(bestAngle);
+
      java.awt.geom.Point2D.Double myLocation = myLocation();
    }
+
      risk += Math.max(0.5, Math.min(enemy1.energy / getEnergy(), 2))
+
          * (1 + Math.abs(Math.cos(absoluteBearing(myLocation, destination)
    private static double absoluteBearing(Point2D.Double source, Point2D.Double target) {
+
              - absoluteBearing(myLocation, enemy1))))
        return Math.atan2(target.x - source.x, target.y - source.y);
+
          / closer
 +
          / distSq
 +
          / (200000 + destination.distanceSq(
 +
              getBattleFieldWidth() / 2, getBattleFieldHeight() / 2));
 
     }
 
     }
 
      
 
      
     // CREDIT: code by Iiley,
+
     for (int x = 1; x < 6; x++) {
    // http://robowiki.net?BackAsFront
+
      try {
    void moveWithBackAsFront(double bearing) {
+
         risk *= 1 + (500 / x
         double angle = Utils.normalRelativeAngle(bearing - getHeadingRadians());
+
            / _recentLocations.get(x * 10).distanceSq(destination));
        double turnAngle;
+
      } catch (Exception ex) {
        setTurnRightRadians(turnAngle = Math.atan(Math.tan(angle)));
+
         // ok
        setAhead((angle == turnAngle) ? 100 : -100);
+
      }
    }
 
   
 
    protected static Point2D.Double project(Point2D.Double sourceLocation, double angle, double length) {
 
         return new Point2D.Double(sourceLocation.x + Math.sin(angle) * length,
 
            sourceLocation.y + Math.cos(angle) * length);
 
 
     }
 
     }
 
      
 
      
     private static double maxEscapeAngle(double velocity) {
+
     return risk;
         return Math.asin(8.0/velocity);
+
  }
     }
+
 
 +
  public static double absoluteBearing(
 +
      Point2D.Double source, Point2D.Double target) {
 +
    return Math.atan2(target.x - source.x, target.y - source.y);
 +
  }
 +
 
 +
  public static Point2D.Double project(Point2D.Double sourceLocation,
 +
      double angle, double length) {
 +
    return new Point2D.Double(
 +
         sourceLocation.x + Math.sin(angle) * length,
 +
        sourceLocation.y + Math.cos(angle) * length);
 +
  }
 +
 
 +
  public static double square(double x) {
 +
     return x * x;
 +
  }
  
    protected static double bulletVelocity(double power) {
+
  private Point2D.Double myLocation() {
        return (20.0 - (3.0*power));
+
    return new Point2D.Double(getX(), getY());
    }
+
  }
   
 
    static int sign(double d) {
 
        if (d >= 0) return 1;
 
        return-1;
 
    }
 
   
 
    static double square(double d) {
 
        return d * d;
 
    }
 
   
 
    protected static double limit(double min, double value, double max) {
 
        return Math.max(min, Math.min(value, max));
 
    }
 
  
    class MicroWave extends Condition {
+
  public class DisplacementTimer extends Condition {
        String targetName;
+
    EnemyData enemyData;
        Point2D.Double sourceLocation;
+
    Point2D.Double targetLocation;
        long[] waveGuessFactors;
+
    double targetHeading;
        double bulletVelocity, directAngle, distance;
+
    Point2D.Double displacementVector;
        int orientation;
+
    int bulletTicks;
 +
    int timer;
  
        public boolean test(){
+
    public boolean test() {
        try {
+
      if (++timer > bulletTicks && enemyData.alive) {
                Point2D.Double enemyLocation =
+
        displacementVector.setLocation(
                    ((EnemyData)_enemyLocationsByName.get(targetName)).location;
+
            absoluteBearing(targetLocation, enemyData) - targetHeading,
                if ((enemyLocation).distance(sourceLocation)
+
            targetLocation.distance(enemyData) / bulletTicks);
                    <= (distance+=WAVE_BULLET_VELOCITY) + (WAVE_BULLET_VELOCITY/2)) {
+
        removeCustomEvent(this);
                try {
+
      }
                double guessFactor = (Utils.normalRelativeAngle(
+
      return false;
                absoluteBearing(sourceLocation, enemyLocation)
 
                - directAngle) * orientation) / WAVE_MAX_ESCAPE_ANGLE;
 
                int guessFactorIndex = (int)((guessFactor * GF_ZERO) + GF_ZERO);
 
                waveGuessFactors[guessFactorIndex]++;
 
/*
 
                for (int x = GF_ONE - 1; x >= 0; x--) {
 
                waveGuessFactors[x] +=
 
                (1D / (square(x - guessFactorIndex) + 1));
 
//               waveGuessFactors[x] = ((waveGuessFactors[x] * 200) +
 
//                (1D / (square(x - guessFactorIndex) + 1)))
 
//                / 201;
 
                }
 
*/
 
                } catch (Exception e) { }
 
                    removeCustomEvent(this);
 
                }
 
        } catch (Exception e) { }
 
            return false;
 
        }
 
 
     }
 
     }
 +
  }
 +
 +
  @SuppressWarnings("serial")
 +
  public static class EnemyData extends Point2D.Double {
 +
    public double energy;
 +
    public boolean alive;
 +
    public Point2D.Double[][][] gunVectors = new Point2D.Double[5][5][200];
 +
    public Point2D.Double[] lastVectors;
 +
    public int nextIndex = 0;
 +
    public double heading;
 +
    public long lastScanTime;
 +
  }
  
    class EnemyData {
+
  public static class MeleeFiringAngle {
        protected long[][][][] gunStats = new long[3][5][3][GF_ONE+1];
+
    public double angle;
 +
    public double distance;
 +
    public double bandwidth;
  
        boolean alive;
+
    public MeleeFiringAngle(double angle, double distance, double bandwidth) {
        Point2D.Double location;
+
      this.angle = angle;
        double energy;
+
      this.distance = distance;
 +
      this.bandwidth = bandwidth;
 
     }
 
     }
 +
  }
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
[[Category:Source Code]]
 
[[Category:Source Code]]

Latest revision as of 06:01, 3 July 2013

Sub-pages:
BrokenSwordVersion History - Code

The source code for BrokenSword 2.0.0. Code size is 1499.

package voidious.mini;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import robocode.AdvancedRobot;
import robocode.Condition;
import robocode.RobotDeathEvent;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.util.Utils;

/**
 * Copyright (c) 2012 - Voidious
 *
 * 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.
 *
 *    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.
 */

/**
 * A MiniBot melee specialist. Uses Minimum Risk movement and Shadow/Melee Gun.
 * Movement based on its little brother, BlitzBat.
 */

public class BrokenSword extends AdvancedRobot {
  private static final double TWO_PI = Math.PI * 2;

  private static Rectangle2D.Double _battleField;
  private static Point2D.Double _destination;
  private static String _nearestName;
  private static double _nearestDistance;
  private static Map<String, EnemyData> _enemies =
      new HashMap<String, EnemyData>();
  private static List<Point2D.Double> _recentLocations;
  
  public void run() {
    setAdjustGunForRobotTurn(true);
    setAdjustRadarForGunTurn(true);
    setColors(Color.black, Color.black, new Color(141, 220, 175));
    
    _battleField = new Rectangle2D.Double(50, 50, 
        getBattleFieldWidth() - 100, getBattleFieldHeight() - 100);
    _recentLocations = new ArrayList<Point2D.Double>();
    _nearestDistance = Double.POSITIVE_INFINITY;
    _destination = null;
    
    do {
      Point2D.Double myLocation = myLocation();
      _recentLocations.add(0, myLocation);

      //***********************************************************************
      // Gun
      double bulletPower = 3 - ((20 - getEnergy()) / 6);
      if (getGunTurnRemaining() == 0) {
        setFire(bulletPower);
      }

      List<MeleeFiringAngle> firingAngles = new ArrayList<MeleeFiringAngle>();
      for (EnemyData enemyData : _enemies.values()) {
        if (enemyData.alive) {
          double enemyDistance = enemyData.distance(myLocation);
          int bulletTicks =
              (int) (enemyDistance / Rules.getBulletSpeed(bulletPower));
          for (Point2D.Double vector : enemyData.lastVectors) {
            if (vector != null) {
              Point2D.Double projectedLocation = project(enemyData,
                  enemyData.heading + vector.x, vector.y * bulletTicks);
              if (_battleField.contains(projectedLocation)) {
                firingAngles.add(new MeleeFiringAngle(
                    absoluteBearing(myLocation, projectedLocation),
                    enemyDistance, 18 / enemyDistance));
              }
            }
          }
        }
      }

      try {
        double bestDensity = 0;
        for (int x = 0; x < 160; x++) {
          double angle = Math.PI * x / 80;
          double density = 0;
          for (MeleeFiringAngle meleeAngle : firingAngles) {
            double ux =
                Math.abs(Utils.normalRelativeAngle(angle - meleeAngle.angle))
                    / meleeAngle.bandwidth;
            if (ux < 1) {
              density += square(1 - square(ux)) / meleeAngle.distance;
            }
          }
          if (density > bestDensity) {
            bestDensity = density;
            setTurnGunRightRadians(
                Utils.normalRelativeAngle(angle - getGunHeadingRadians()));
          }
        }
      } catch (NullPointerException npe) {
        // expected before any scans
      }

      //***********************************************************************
      // Movement
      double bestRisk;
      try {
        bestRisk = evalDestinationRisk(_destination) * .85;
      } catch (NullPointerException ex) {
        bestRisk = Double.POSITIVE_INFINITY;
      }
      try {
        for (double d = 0; d < TWO_PI; d += 0.1) {
          Point2D.Double newDest = project(myLocation, d,
              Math.min(_nearestDistance, 100 + Math.random() * 500));
          double thisRisk = evalDestinationRisk(newDest);
          if (_battleField.contains(newDest) && thisRisk < bestRisk) {
            bestRisk = thisRisk;
            _destination = newDest;
          }
        }
        
        double angle = Utils.normalRelativeAngle(
            absoluteBearing(myLocation, _destination) - getHeadingRadians());
        setTurnRightRadians(Math.tan(angle));
        setAhead(Math.cos(angle) * Double.POSITIVE_INFINITY);
      } catch (NullPointerException ex) {
        // expected before we have a _destination
      }
      
      //***********************************************************************
      // Radar
      setTurnRadarRightRadians(1);
      try {
        long stalestTime = Long.MAX_VALUE;
        for (EnemyData enemyData : _enemies.values()) {
          if (getTime() > 20 && enemyData.alive
              && enemyData.lastScanTime < stalestTime) {
            stalestTime = enemyData.lastScanTime;
            setTurnRadarRightRadians(Math.signum(Utils.normalRelativeAngle(
                absoluteBearing(myLocation, enemyData)
                    - getRadarHeadingRadians())));
          }
        }
      } catch (NullPointerException npe) {
        // expected before we have any scans
      }
      //***********************************************************************
      execute();
    } while (true);    
  }
  
  public void onScannedRobot(ScannedRobotEvent e) {
    double distance = e.getDistance();
    String botName = e.getName();
    
    if (!_enemies.containsKey(botName)) {
      _enemies.put(botName, new EnemyData());
    }

    DisplacementTimer timer;
    addCustomEvent(timer = new DisplacementTimer());
    EnemyData enemyData = timer.enemyData = _enemies.get(botName);
    enemyData.energy = e.getEnergy();
    enemyData.alive = true;
    enemyData.lastScanTime = getTime();

    timer.displacementVector = (enemyData.lastVectors = enemyData.gunVectors
        [(int) (distance / 300)]
        [(int) (Math.abs(e.getVelocity()) / 4)])
            [enemyData.nextIndex++ % 200] = new Point2D.Double(0, 0);

    enemyData.setLocation(timer.targetLocation = project(
        myLocation(), e.getBearingRadians() + getHeadingRadians(),
        distance));

    timer.bulletTicks = (int) (distance / 11);
    timer.targetHeading = enemyData.heading = e.getHeadingRadians()
        + (e.getVelocity() < 0 ? Math.PI : 0);
    
    if (distance < _nearestDistance || botName.equals(_nearestName)) {
      _nearestDistance = distance;
      _nearestName = botName;
    }
  }
  
  public void onRobotDeath(RobotDeathEvent e) {
    _enemies.get(e.getName()).alive = false;
    _nearestDistance = Double.POSITIVE_INFINITY;
  }
  
  private double evalDestinationRisk(Point2D.Double destination) {
    double risk = 0;
    
    for (EnemyData enemy1 : _enemies.values()) {
      double distSq = enemy1.distanceSq(destination);
      int closer = 0;
      for (EnemyData enemy2 : _enemies.values()) {
        if (enemy1.distanceSq(enemy2) < distSq) {
          closer++;
        }
      }

      java.awt.geom.Point2D.Double myLocation = myLocation();
      risk += Math.max(0.5, Math.min(enemy1.energy / getEnergy(), 2))
          * (1 + Math.abs(Math.cos(absoluteBearing(myLocation, destination)
              - absoluteBearing(myLocation, enemy1))))
          / closer
          / distSq
          / (200000 + destination.distanceSq(
              getBattleFieldWidth() / 2, getBattleFieldHeight() / 2));
    }
    
    for (int x = 1; x < 6; x++) {
      try {
        risk *= 1 + (500 / x
            / _recentLocations.get(x * 10).distanceSq(destination));
      } catch (Exception ex) {
        // ok
      }
    }
    
    return risk;
  }

  public static double absoluteBearing(
      Point2D.Double source, Point2D.Double target) {
    return Math.atan2(target.x - source.x, target.y - source.y);
  }
  
  public static Point2D.Double project(Point2D.Double sourceLocation, 
      double angle, double length) {
    return new Point2D.Double(
        sourceLocation.x + Math.sin(angle) * length,
        sourceLocation.y + Math.cos(angle) * length);
  }

  public static double square(double x) {
    return x * x;
  }

  private Point2D.Double myLocation() {
    return new Point2D.Double(getX(), getY());
  }

  public class DisplacementTimer extends Condition {
    EnemyData enemyData;
    Point2D.Double targetLocation;
    double targetHeading;
    Point2D.Double displacementVector;
    int bulletTicks;
    int timer;

    public boolean test() {
      if (++timer > bulletTicks && enemyData.alive) {
        displacementVector.setLocation(
            absoluteBearing(targetLocation, enemyData) - targetHeading,
            targetLocation.distance(enemyData) / bulletTicks);
        removeCustomEvent(this);
      }
      return false;
    }
  }

  @SuppressWarnings("serial")
  public static class EnemyData extends Point2D.Double {
    public double energy;
    public boolean alive;
    public Point2D.Double[][][] gunVectors = new Point2D.Double[5][5][200];
    public Point2D.Double[] lastVectors;
    public int nextIndex = 0;
    public double heading;
    public long lastScanTime;
  }

  public static class MeleeFiringAngle {
    public double angle;
    public double distance;
    public double bandwidth;

    public MeleeFiringAngle(double angle, double distance, double bandwidth) {
      this.angle = angle;
      this.distance = distance;
      this.bandwidth = bandwidth;
    }
  }
}