[Home]BasicGTSurfer/Code

Robo Home | BasicGTSurfer | Changes | Preferences | AllPages

The code...

    package wiki.tutorial;

   import robocode.*;
   import robocode.util.Utils;
   import java.awt.geom.*;     // for Point2D's
   import java.lang.*;         // for Double and Integer objects
   import java.util.ArrayList; // for collection of waves
   import java.awt.*;

    public class BasicGTSurfer extends AdvancedRobot {
      public static int BINS = 47;
      public static double _surfStats[] = new double[BINS]; // we'll use 47 bins
      public Point2D.Double _myLocation;     // our bot's location
      public Point2D.Double _enemyLocation;  // enemy bot's location
      
      public Point2D.Double _lastGoToPoint;
      public double direction = 1;
   
      public ArrayList _enemyWaves;
      public ArrayList _surfDirections;
      public ArrayList _surfAbsBearings;
   
    // We must keep track of the enemy's energy level to detect EnergyDrop,
    // indicating a bullet is fired
      public static double _oppEnergy = 100.0;
   
    // This is a rectangle that represents an 800x600 battle field,
    // used for a simple, iterative WallSmoothing method (by Kawigi).
    // If you're not familiar with WallSmoothing, the wall stick indicates
    // the amount of space we try to always have on either end of the tank
    // (extending straight out the front or back) before touching a wall.
      public static Rectangle2D.Double _fieldRect
        = new java.awt.geom.Rectangle2D.Double(18, 18, 764, 564);
      public static double WALL_STICK = 160;
   
       public void run() {
         _enemyWaves = new ArrayList();
         _surfDirections = new ArrayList();
         _surfAbsBearings = new ArrayList();
      
         setAdjustGunForRobotTurn(true);
         setAdjustRadarForGunTurn(true);
      
         do {
            // basic mini-radar code
            turnRadarRightRadians(Double.POSITIVE_INFINITY);
         } while (true);
      }
   
       public void onScannedRobot(ScannedRobotEvent e) {
         _myLocation = new Point2D.Double(getX(), getY());
      
         double lateralVelocity = getVelocity()*Math.sin(e.getBearingRadians());
         double absBearing = e.getBearingRadians() + getHeadingRadians();
      
         setTurnRadarRightRadians(Utils.normalRelativeAngle(absBearing - getRadarHeadingRadians()) * 2);
      
         _surfDirections.add(0,
            new Integer((lateralVelocity >= 0) ? 1 : -1));
         _surfAbsBearings.add(0, new Double(absBearing + Math.PI));
      
      
         double bulletPower = _oppEnergy - e.getEnergy();
         if (bulletPower < 3.01 && bulletPower > 0.09
            && _surfDirections.size() > 2) {
            EnemyWave ew = new EnemyWave();
            ew.fireTime = getTime() - 1;
            ew.bulletVelocity = bulletVelocity(bulletPower);
            ew.distanceTraveled = bulletVelocity(bulletPower);
            ew.direction = ((Integer)_surfDirections.get(2)).intValue();
            ew.directAngle = ((Double)_surfAbsBearings.get(2)).doubleValue();
            ew.fireLocation = (Point2D.Double)_enemyLocation.clone(); // last tick
         
            _enemyWaves.add(ew);
         }
      
         _oppEnergy = e.getEnergy();
      
        // update after EnemyWave detection, because that needs the previous
        // enemy location as the source of the wave
         _enemyLocation = project(_myLocation, absBearing, e.getDistance());
      
         updateWaves();
         doSurfing();
      
        // gun code would go here...
      }
   
       public void updateWaves() {
         for (int x = 0; x < _enemyWaves.size(); x++) {
            EnemyWave ew = (EnemyWave)_enemyWaves.get(x);
         
            ew.distanceTraveled = (getTime() - ew.fireTime) * ew.bulletVelocity;
            if (ew.distanceTraveled >
                _myLocation.distance(ew.fireLocation) + 50) {
               _enemyWaves.remove(x);
               x--;
            }
         }
      }
   
       public EnemyWave getClosestSurfableWave() {
         double closestDistance = 50000; // I juse use some very big number here
         EnemyWave surfWave = null;
      
         for (int x = 0; x < _enemyWaves.size(); x++) {
            EnemyWave ew = (EnemyWave)_enemyWaves.get(x);
            double distance = _myLocation.distance(ew.fireLocation)
                - ew.distanceTraveled;
         
            if (distance > ew.bulletVelocity && distance < closestDistance) {
               surfWave = ew;
               closestDistance = distance;
            }
         }
      
         return surfWave;
      }
   
    // Given the EnemyWave that the bullet was on, and the point where we
    // were hit, calculate the index into our stat array for that factor.
       public static int getFactorIndex(EnemyWave ew, Point2D.Double targetLocation) {
         double offsetAngle = (absoluteBearing(ew.fireLocation, targetLocation)
            - ew.directAngle);
         double factor = Utils.normalRelativeAngle(offsetAngle)
            / maxEscapeAngle(ew.bulletVelocity) * ew.direction;
      
         return (int)limit(0,
            (factor * ((BINS - 1) / 2)) + ((BINS - 1) / 2),
            BINS - 1);
      }
   
    // Given the EnemyWave that the bullet was on, and the point where we
    // were hit, update our stat array to reflect the danger in that area.
       public void logHit(EnemyWave ew, Point2D.Double targetLocation) {
         int index = getFactorIndex(ew, targetLocation);
      
         for (int x = 0; x < BINS; x++) {
            // for the spot bin that we were hit on, add 1;
            // for the bins next to it, add 1 / 2;
            // the next one, add 1 / 5; and so on...
            _surfStats[x] += 1.0 / (Math.pow(index - x, 2) + 1);
         }
      }
   
       public void onHitByBullet(HitByBulletEvent e) {
        // If the _enemyWaves collection is empty, we must have missed the
        // detection of this wave somehow.
         if (!_enemyWaves.isEmpty()) {
            Point2D.Double hitBulletLocation = new Point2D.Double(
                e.getBullet().getX(), e.getBullet().getY());
            EnemyWave hitWave = null;
         
            // look through the EnemyWaves, and find one that could've hit us.
            for (int x = 0; x < _enemyWaves.size(); x++) {
               EnemyWave ew = (EnemyWave)_enemyWaves.get(x);
            
               if (Math.abs(ew.distanceTraveled -
                    _myLocation.distance(ew.fireLocation)) < 50
                    && Math.round(bulletVelocity(e.getBullet().getPower()) * 10)
                       == Math.round(ew.bulletVelocity * 10)) {
                  hitWave = ew;
                  break;
               }
            }
         
            if (hitWave != null) {
               logHit(hitWave, hitBulletLocation);
            
                // We can remove this wave now, of course.
               _enemyWaves.remove(_enemyWaves.lastIndexOf(hitWave));
            }
         }
      }
   
    // CREDIT: mini sized predictor from Apollon, by rozu
    // http://robowiki.net?Apollon
       public ArrayList predictPositions(EnemyWave surfWave, int direction) {
         Point2D.Double predictedPosition = (Point2D.Double)_myLocation.clone();
         double predictedVelocity = getVelocity();
         double predictedHeading = getHeadingRadians();
         double maxTurning, moveAngle, moveDir;
         ArrayList traveledPoints = new ArrayList();
      
         int counter = 0; // number of ticks in the future
         boolean intercepted = false;
      
         do {
            double distance = predictedPosition.distance(surfWave.fireLocation);
            double offset = Math.PI/2 - 1 + distance/400;
         
            moveAngle =
                wallSmoothing(predictedPosition, absoluteBearing(surfWave.fireLocation,
                predictedPosition) + (direction * (offset)), direction)
                - predictedHeading;
            moveDir = 1;
         
            if(Math.cos(moveAngle) < 0) {
               moveAngle += Math.PI;
               moveDir = -1;
            }
         
            moveAngle = Utils.normalRelativeAngle(moveAngle);
         
         // maxTurning is built in like this, you can't turn more then this in one tick
            maxTurning = Math.PI/720d*(40d - 3d*Math.abs(predictedVelocity));
            predictedHeading = Utils.normalRelativeAngle(predictedHeading
                + limit(-maxTurning, moveAngle, maxTurning));
         
         // this one is nice ;). if predictedVelocity and moveDir have
            // different signs you want to breack down
         // otherwise you want to accelerate (look at the factor "2")
            predictedVelocity += (predictedVelocity * moveDir < 0 ? 2*moveDir : moveDir);
            predictedVelocity = limit(-8, predictedVelocity, 8);
         
         // calculate the new predicted position
            predictedPosition = project(predictedPosition, predictedHeading, predictedVelocity);
         
         //add this point the our prediction
            traveledPoints.add(predictedPosition);
         
            counter++;
         
            if (predictedPosition.distance(surfWave.fireLocation) - 20 <
                surfWave.distanceTraveled + (counter * surfWave.bulletVelocity)
              //   + surfWave.bulletVelocity
            	 ) {
               intercepted = true;
            }
         } while(!intercepted && counter < 500);
      
      //we can't get the the last point, because we need to slow down
         if(traveledPoints.size() > 1)
            traveledPoints.remove(traveledPoints.size() - 1);
      
         return traveledPoints;
      }
   
       public double checkDanger(EnemyWave surfWave, Point2D.Double position) {
         int index = getFactorIndex(surfWave, position);
         double distance = position.distance(surfWave.fireLocation);
         return _surfStats[index]/distance;
      }
   
       public Point2D.Double getBestPoint(EnemyWave surfWave){
         if(surfWave.safePoints == null){
            ArrayList forwardPoints = predictPositions(surfWave, 1);
            ArrayList reversePoints = predictPositions(surfWave, -1);
            int FminDangerIndex = 0;
            int RminDangerIndex = 0;
            double FminDanger = Double.POSITIVE_INFINITY;
            double RminDanger = Double.POSITIVE_INFINITY;
            for(int i = 0, k = forwardPoints.size(); i < k; i++){
               double thisDanger = checkDanger(surfWave, (Point2D.Double)(forwardPoints.get(i)));
               if(thisDanger <= FminDanger){
                  FminDangerIndex = i;
                  FminDanger = thisDanger;
               }
            }
            for(int i = 0, k = reversePoints.size(); i < k; i++){
               double thisDanger = checkDanger(surfWave, (Point2D.Double)(reversePoints.get(i)));
               if(thisDanger <= RminDanger){
                  RminDangerIndex = i;
                  RminDanger = thisDanger;
               }
            }
            ArrayList bestPoints;
            int minDangerIndex;
            
            if(FminDanger < RminDanger ){
               bestPoints = forwardPoints;
               minDangerIndex = FminDangerIndex;
            }
            else {
               bestPoints = reversePoints;
               minDangerIndex = RminDangerIndex;
            }
            
            Point2D.Double bestPoint = (Point2D.Double)bestPoints.get(minDangerIndex);
         
            while(bestPoints.indexOf(bestPoint) != -1)
               bestPoints.remove(bestPoints.size() - 1);
            bestPoints.add(bestPoint);
               
            surfWave.safePoints = bestPoints;
            
         	//debugging - so that we should always be on top of the last point
            bestPoints.add(0,new Point2D.Double(getX(), getY()));
            
         }
         else
            if(surfWave.safePoints.size() > 1)
               surfWave.safePoints.remove(0);
         
         
         if(surfWave.safePoints.size() >= 1){
            for(int i = 0,k=surfWave.safePoints.size(); i < k; i++){
               Point2D.Double goToPoint = (Point2D.Double)surfWave.safePoints.get(i);
               if(goToPoint.distanceSq(_myLocation) > 20*20*1.1)
               //if it's not 20 units away we won't reach max velocity
                  return goToPoint;
            }
         //if we don't find a point 20 units away, return the end point
            return (Point2D.Double)surfWave.safePoints.get(surfWave.safePoints.size() - 1);
               
         
         }
           
         return null;
      }
   
       public void doSurfing() {
         EnemyWave surfWave = getClosestSurfableWave();
         double distance = _enemyLocation.distance(_myLocation);
         if (surfWave == null || distance < 50) { 
            //do 'away' movement  best distance of 400 - modified from RaikoNano
            double absBearing = absoluteBearing(_myLocation, _enemyLocation);
            double headingRadians = getHeadingRadians();
            double stick = 160;//Math.min(160,distance);
            double  v2, offset = Math.PI/2 + 1 - distance/400;
            
            while(!_fieldRect.
            contains(project(_myLocation,v2 = absBearing + direction*(offset -= 0.02), stick)
            
            // 	getX() + stick * Math.sin(v2 = absBearing + direction * (offset -= .02)), getY() + stick * Math.cos(v2)
            ));
         
         
            if( offset < Math.PI/3 ) 
               direction = -direction;
            setAhead(50*Math.cos(v2 - headingRadians));
            setTurnRightRadians(Math.tan(v2 - headingRadians));
         
         }
         else
            goTo(getBestPoint(surfWave));
      }
       private void goTo(Point2D.Double destination) {
         if(destination == null){
            if(_lastGoToPoint != null)
               destination = _lastGoToPoint;
            else
               return;
         }
         
         _lastGoToPoint = destination;
         Point2D.Double location = new Point2D.Double(getX(), getY());
         double distance = location.distance(destination);
         double angle = Utils.normalRelativeAngle(absoluteBearing(location, destination) - getHeadingRadians());
         if (Math.abs(angle) > Math.PI/2) {
            distance = -distance;
            if (angle > 0) {
               angle -= Math.PI;
            }
            else {
               angle += Math.PI;
            }
         }
         
      	//this is hacked so that the bot doesn't turn once we get to our destination
         setTurnRightRadians(angle*Math.signum(Math.abs((int)distance)));
         
         setAhead(distance);
      }
   
    // This can be defined as an inner class if you want.
       class EnemyWave {
         Point2D.Double fireLocation;
         long fireTime;
         double bulletVelocity, directAngle, distanceTraveled;
         int direction;
         ArrayList safePoints;
      
          public EnemyWave() { }
      }
   
    // CREDIT: Iterative WallSmoothing by Kawigi
    //   - return absolute angle to move at after account for WallSmoothing
    // robowiki.net?WallSmoothing
       public double wallSmoothing(Point2D.Double botLocation, double angle, int orientation) {
         while (!_fieldRect.contains(project(botLocation, angle, 160))) {
            angle += orientation*0.05;
         }
         return angle;
      }
   
    // CREDIT: from CassiusClay, by PEZ
    //   - returns point length away from sourceLocation, at angle
    // robowiki.net?CassiusClay
       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);
      }
   
    // got this from RaikoMicro, by Jamougha, but I think it's used by many authors
    //  - returns the absolute angle (in radians) from source to target points
       public static double absoluteBearing(Point2D.Double source, Point2D.Double target) {
         return Math.atan2(target.x - source.x, target.y - source.y);
      }
   
       public static double limit(double min, double value, double max) {
         return Math.max(min, Math.min(value, max));
      }
   
       public static double bulletVelocity(double power) {
         return (20D - (3D*power));
      }
   
       public static double maxEscapeAngle(double velocity) {
         return Math.asin(8.0/velocity);
      }
   
       public static void setBackAsFront(AdvancedRobot robot, double goAngle) {
         double angle =
            Utils.normalRelativeAngle(goAngle - robot.getHeadingRadians());
         if (Math.abs(angle) > (Math.PI/2)) {
            if (angle < 0) {
               robot.setTurnRightRadians(Math.PI + angle);
            } 
            else {
               robot.setTurnLeftRadians(Math.PI - angle);
            }
            robot.setBack(100);
         } 
         else {
            if (angle < 0) {
               robot.setTurnLeftRadians(-1*angle);
            } 
            else {
               robot.setTurnRightRadians(angle);
            }
            robot.setAhead(100);
         }
      }
   
       public void onPaint(java.awt.Graphics2D g) {
         g.setColor(Color.red);
         for(int i = 0; i < _enemyWaves.size(); i++){
            EnemyWave w = (EnemyWave)(_enemyWaves.get(i));
            Point2D.Double center = w.fireLocation;
         
            //int radius = (int)(w.distanceTraveled + w.bulletVelocity);
            //hack to make waves line up visually, due to execution sequence in robocode engine
            //use this only if you advance waves in the event handlers (eg. in onScannedRobot())
            //NB! above hack is now only necessary for robocode versions before 1.4.2
            //otherwise use: 
            int radius = (int)w.distanceTraveled;
            
            if(radius - 40 < center.distance(_myLocation))
               g.drawOval((int)(center.x - radius ), (int)(center.y - radius), radius*2, radius*2);
         }
      }
   
   }

Robo Home | BasicGTSurfer | Changes | Preferences | AllPages
Edit text of this page | View other revisions
Last edited October 3, 2007 20:36 EST by Skilgannon (diff)
Search: