Foilist/Code

From Robowiki
Jump to navigation Jump to search
/*
Foilist v1.3.1 by Sheldor.  03/29/2025  1497 bytes
a minibot with wave surfing and pattern matching
v1.3.1 -- movement, energy management

Foil is one of the three forms of modern sport fencing,
along with épée and sabre.  https://en.wikipedia.org/wiki/Foil_%28fencing%29

Credits:
	Movement : jk.mini.CunobelinDC 1.2, simonton.mini.WeeksOnEnd 1.10.4, kc.micro.WaveShark 0.5,
		kc.mega.BeepBoop 2.0,	kc.serpent.Hydra
	Targeting: kc.micro.WaveShark 0.5, jk.micro.Cotillion 0.8, pez.mini.Pugilist 2.5.1f
Also, a general thanks to all open source bot authors and contributors to the RoboWiki.

Foilist is open source and released under the terms of the RoboWiki Public Code License (RWPCL) - Version 1.1.
see license here:  https://robowiki.net/wiki/RWPCL
*/

package sheldor.mini;

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.*;
import java.util.*;

public class Foilist extends AdvancedRobot {		

	static final int    GUESS_FACTORS = 100;
	static final int    MIDDLE_FACTOR = 16;
	
  private static final int MAX_BFT = 70;
  private static final int MAX_MATCH_LENGTH = 40;
  private static final double WALL_FEATURE = 0.17;//0.45;
  
	private static final int SCAN_LENGTH = 48;
	private static final int VELOCITY_WEIGHT = (SCAN_LENGTH * 3) / 2;
	
	private static final int VELOCITY_VALUE = 1;	
	
	private static final int WEIGHTS = 5;
	private static final int WEIGHT_LENGTH = 8;
	private static final int SCANS = 127;

 //  static ArrayList[] hitScans = new ArrayList[10];
  // static int[] indices = new int[9];
   static ArrayList hitScans = new ArrayList();
   
   public static Point2D.Double myLocation;    
   public static Point2D.Double enemyLocation;

   public static ArrayList enemyWaves;
   public static ArrayList pastWaves = new ArrayList();;

   public static double enemyEnergy;
   public static final double WALL_STICK = 160;
      
   static double lastLatVel;
   static double lastAdvVel;
   static double previousLastLatVel;
   static double lastPreviousLastLatVel;
   static double lastLatVelChange;
   
	static double waveCount;
		
	static double velocity;
	static double lastVelocity;
	static int ticksSinceVelocityChange;
	static int previousTicksSinceVelocityChange;
	static int ticksSinceHit;
	
//	static double[] weightScores = new double[WEIGHTS];
	static int weightIndex;
	static double powerSinceLastHit;
	static double hits;
	static double decay;
	static int deathCount;
	static double threshold;
	
	static double directionDistance;
	static double previousDirectionDistance;
	
	static double lastFactor;

	static ScannedRobotEvent e;
	
   public void run() {
      setAdjustGunForRobotTurn(true);
      setAdjustRadarForGunTurn(true);
    
      enemyWaves = new ArrayList();
		
	  setColors(java.awt.Color.white, java.awt.Color.black, java.awt.Color.lightGray);
   } 
   
   public void onStatus(StatusEvent e){    
      setTurnRadarRightRadians(1); 
   }

   public void onScannedRobot(ScannedRobotEvent e) {
		     	int i;   
      double absoluteBearing;  
      double enemyDistance;  
      double tempIndex;
	  double bearing;
      double latVel;
		double bulletPower;  
	  
      Wave ew;
	
		//wave surfing based on CunobelinDC's
		
      (ew = new Wave()).direction = (//direction =
 (int)Math.signum(//(direction / 100.0) +
	  
			(//history[--historyIndex] = 
			
(latVel = (getVelocity()*Math.sin(bearing = e.getBearingRadians())))) + 1E-10));
 //     double advVel = (getVelocity()*Math.cos(absoluteBearing));
   	
      setTurnRadarRightRadians(Utils.normalRelativeAngle((absoluteBearing = (bearing) + getHeadingRadians()) - getRadarHeadingRadians()) * 2);
	  
      pastWaves.add(0,ew);
    
      addWave(enemyEnergy - (enemyEnergy = e.getEnergy()));
   
     // update after EnemyWave detection, because that needs the previous
     // enemy location as the source of the Wave
      
      enemyLocation = project((myLocation = new Point2D.Double(getX(), getY())), (ew.directAngle = absoluteBearing), (enemyDistance = e.getDistance()));
   
		updateWaves();
		  
	if (velocity != (velocity = getVelocity())
//	if (getVelocity() == 0
){
			//previousTicksSinceVelocityChange = ticksSinceVelocityChange;
			ticksSinceVelocityChange = 0;//deceleration;
			}
	
	double distance;
	double[] localScan;
	(localScan = (ew.scan = new double[WEIGHT_LENGTH]))[1] = lastLatVel;
	localScan[2] = 1.34 * //10 * 
(lastLatVel = Math.abs(latVel));
	localScan[3] = ((distance = enemyDistance));
	localScan[5] = ++ticksSinceVelocityChange;//Math.cbrt(--ticksSinceVelocityChange);//VELOCITY_HISTORY_TABLE.charAt(ticksSinceVelocityChange += 3);
	localScan[7] = velocity*
	Math.cos(bearing);
	
		i = 25;
		do{
			localScan[5 - Integer.signum(--i)] += (fieldContains(enemyLocation, (absoluteBearing + Math.PI) + (ew.direction * (i * 0.04)), enemyDistance));
		}while(i > -25);

//	int span = 2 + (400 / (int)enemyDistance);
	double lowest = Double.POSITIVE_INFINITY;
tempIndex = (i = (2 + (2 * (480 / (int)enemyDistance))));
	do {
		double angle;
		double test;
		if ((test = checkDanger(
			angle = ((short)ANGLE_TABLE.charAt(i) / 10.0)//Math.cbrt((i * 8))//6.375
//5//((35 / Math.cbrt(enemyDistance)))
)) < lowest){
			tempIndex = angle;
			lowest = test;
		}
	}
	while (i-- > 0);//(--i >= -2);	

	  	setAhead(0);
		if(tempIndex != 0){
		      double goAngle;
      setAhead(Math.cos(goAngle = 
wallSmoothing(myLocation, 
            absoluteBearing,
            tempIndex)
            - getHeadingRadians()) / 0);
      setTurnRightRadians(Math.tan(goAngle));
	  }
      
		//calculate wall proximity
		//possibly inspired by Pugilist
		int wallScore = 0;
		i = 24;
		do{
			wallScore += fieldContains(myLocation, absoluteBearing + (4.29//(60.0 / bulletVelocity) 
/ (short)WALL_TABLE.charAt(--i)), enemyDistance);
		} while(i > 0);
		

			//pattern matching based on kc.micro.WaveShark's
		   // Update log of enemy movements and pattern match
		      enemyHistory = String.valueOf(
        (char)(
            ((int)Math.round((e.getVelocity() *
                Math.sin(e.getHeadingRadians() - absoluteBearing)) 
				//+ (Math.random() - 0.5)
				) << 5)
            | wallScore//(fieldContains(absoluteBearing + WALL_FEATURE, enemyDistance) << 1)
         //   | fieldContains(absoluteBearing - WALL_FEATURE, enemyDistance)
          )
        ).concat(enemyHistory);
	
		if (getGunHeat() <= 0.2){
    int matchLength = MAX_MATCH_LENGTH;//(int)(Math.ceil(Math.random() * MAX_MATCH_LENGTH));
		int matchPos;
			
 do{}
    while ((matchPos = enemyHistory.indexOf(
        enemyHistory.substring(0, (--matchLength)), MAX_BFT)) < 0);
		

		  do {
       absoluteBearing += Math.signum(enemyEnergy) * 
((short)enemyHistory.charAt(--matchPos) >> 5) / enemyDistance;  
		} while ((distance -= Rules.getBulletSpeed(bulletPower = //(240 / (int)enemyDistance) +
			Math.min(enemyEnergy / 4, BULLET_POWER_TABLE.charAt(
			((int)enemyDistance >> 5) * 127 + (int)getEnergy())
//((int)getEnergy() >> 2) * 127 + (int)enemyEnergy)
 / 100.01))
			) > 0);
		
		if (getEnergy() > 0.21){
			setFire(bulletPower);
		}
		
}
	
    setTurnGunRightRadians(Utils.normalRelativeAngle(
	   (Math.random() * 0.007) +
		absoluteBearing - getGunHeadingRadians()));
   } 
    	
   public static void addWave(double deltaEnergy){		
		
      if (pastWaves.size() > 2 && (deltaEnergy > 0 || enemyWaves.isEmpty()
)){
		
         Wave ew;
         (ew = (Wave)pastWaves.get(2)).fireLocation = enemyLocation;
		  ew.bulletVelocity = Rules.getBulletSpeed(deltaEnergy);
		   enemyWaves.add(ew);
	  }
   }

 // CREDIT: mini sized predictor from Apollon, by rozu
 // http://robowiki.net?Apollon

   public  double checkDanger(double direction) {
	   
	Wave wave;// = null;
	int waveIndex = 0;
	double totalScore = 0;
	
	try{
	while(true){
	//Iterator it = enemyWaves.iterator();
//	while (it.hasNext()){
//  while (waveIndex < enemyWaves.size()){
	//	if ((wave = (Wave)it.next()).surf > 0)
//	{
      Point2D.Double predictedPosition = myLocation;
      double predictedVelocity;// = getVelocity();
      double predictedHeading = getHeadingRadians();
      double moveAngle = 0;//(predictedHeading = getHeadingRadians());

		double moveDir = -Math.signum(predictedVelocity = getVelocity());
      
      int counter = 0; // number of ticks in the future
	  
	  //wave = (Wave)enemyWaves.get(waveIndex);//(Wave)enemyWaves.get(waveIndex);
      do {
	//	  moveDir = 1;
			 
	  	 if(direction != 0){
		 moveDir = 1;
         if(Math.cos(moveAngle = wallSmoothing(predictedPosition,
			 absoluteBearing(predictedPosition,enemyLocation), direction) - predictedHeading) < 0) {
            moveAngle += Math.PI;
            moveDir = -1;
         
		 }
		/* else{
		 	moveDir = 1;
			}*/
			}
		else if (Math.abs(predictedVelocity) <= 2){
			counter = 127;
			}
      
       predictedPosition = project(predictedPosition,
            predictedHeading +=
            posNegLimit(Utils.normalRelativeAngle(moveAngle),
              Rules.getTurnRateRadians(Math.abs(predictedVelocity)))
             ,
              predictedVelocity = posNegLimit(predictedVelocity += (predictedVelocity * moveDir < 0 ? 2*moveDir : moveDir), 8)
              );
			  
		//	System.out.println(Math.copySign(8, direction));

	//	}
            
      } while(predictedPosition.distance((wave = (Wave)enemyWaves.get(waveIndex)).fireLocation)
      > wave.distanceTraveled + ++counter*wave.bulletVelocity);
	  
	  predictedVelocity = //1 - 
(predictedHeading = getFactor(wave, predictedPosition));
	  //predictedHeading = getFactor(wave,predictedPosition);
	  waveIndex++;
      if(wave.dist > (wave.bulletVelocity-22)){
	  //		int index;
		  int i = 0;//indices[index = (((int)wave.scan[3]))];//depth;
		  try{
			do{//if(i < SCANS)
{//while(i <= threshold){
         //Iterator i = hitScans.iterator();
        // while(i.hasNext()){
		/*	if (i > threshold){
				break;
				}*/
            double[] scan = //((Wave)hitScans[index][--i]).scan;
(double[])hitScans.get(i);
//i.next();

            double dist = (1/4000.0);
			int j = 1;
			do{
               dist += Math.abs//pow
			   ((wave.scan[j] - scan[j])  / (double)WEIGHT_TABLE.charAt(//weightIndex +//(weightIndex * WEIGHT_LENGTH) +
 j)
//, 2
);
            }
			while(++j < scan.length);
			 predictedVelocity += ((double)(20 + ++i)//(1.0 / Math.cbrt(++i))//1.0//scan[1]//Math.pow(scan[1], random)
/(dist*dist*(//0.25
//random
(1.0/200//50
) 
+ Math.pow(//pow(100 * 
(predictedHeading - scan[0])
, 2
))));
            }} while (true);//i < SCANS);
			}catch(Exception ex){}
    	totalScore += ((predictedVelocity) / predictedPosition.distanceSq(enemyLocation)) / waveIndex;//Math.pow(waveIndex, 2);
   // 	totalScore += predictedVelocity / Math.pow(predictedPosition.distance(enemyLocation) * (wave.dist + 150), 2);			  
  //    }
	  }
	  }
	}
	catch(Exception ex){}
	  return totalScore;
   }   

 // CREDIT: Iterative WallSmoothing by Kawigi
 //   - return absolute angle to move at after account for WallSmoothing
 // robowiki.net?WallSmoothing
   public static double wallSmoothing(Point2D.Double botLocation, double angle, double orientation) {
	   angle += orientation;
	   do{}
	   while (fieldContains(botLocation,( angle -= orientation*0.025)
		 , WALL_STICK) > 0);
      return angle;
   }
   
   public static void updateWaves() {
      Wave ew;
	  int i = 0;//enemyWaves.size();
   //   Iterator it = enemyWaves.iterator();
      
		try{
			while(true){
    //  while(it.hasNext()) {         
         
         if (((ew = (Wave)enemyWaves.get(i)).dist = 
		 myLocation.distance(ew.fireLocation)
   	 	  - (ew.distanceTraveled += ew.bulletVelocity )
         ) < -50) {
			// ew.factors[(int)getFactor(ew,myLocation)] += waveCount;
			// lastFactor = getFactor(ew,myLocation);

			enemyWaves.remove(i);
           // it.remove();
            continue;
         }		
		 i++; 
      }
	  }
	  catch(Exception ex){}
   }

 // 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 double getFactor(Wave ew, Point2D.Double targetLocation) {
	//  return Utils.normalRelativeAngle(absoluteBearing(targetLocation, ew.fireLocation) - ew.directAngle) * ew.direction
   //      /  Math.asin(8.0/ew.bulletVelocity);
      return //(MIDDLE_FACTOR + 25) + 
(//MIDDLE_FACTOR * 
(Utils.normalRelativeAngle(absoluteBearing(targetLocation, ew.fireLocation) - ew.directAngle) * ew.direction
         /  Math.asin(8.0/ew.bulletVelocity)));
   }

   public void onBulletHitBullet(BulletHitBulletEvent e){
      logEnemyBullet(e.getHitBullet());
   }
   public void onHitByBullet(HitByBulletEvent e) {
     //threshold = 20 + (Math.random() * 100);// * 50;
      logEnemyBullet(e.getBullet());
      enemyEnergy += 20 - e.getVelocity();
   }
   public void onBulletHit(BulletHitEvent e){
      enemyEnergy -= Rules.getBulletDamage(e.getBullet().getPower());
   }
   public static void logEnemyBullet(Bullet b){
            // look through the EnemyWaves, and find one that could've hit us.
      Point2D.Double hitLocation; 
	  int i = 0;//enemyWaves.size();
     // Iterator it = enemyWaves.iterator();
	  try{
      do{
         Wave ew;
         
         if (Math.abs((ew = (Wave)enemyWaves.get(i)).distanceTraveled
			 - (hitLocation =  new Point2D.Double(b.getX(), b.getY())).distance(ew.fireLocation)) <= 40) {
			ew.scan[0] = getFactor(ew, hitLocation);

			
			//ew.scan[1] = waveCount;//Math.pow(waveCount, decay);// * (1 + b.getPower());	

			//int index;
			//hitScans[index = ((int)ew.scan[3])][indices[index]++] = ew;
            hitScans.add(0, ew.scan);	
			
             // We can remove this wave now, of course.
			 enemyWaves.remove(i);
            //it.remove();
         }
		 i++;
      }while (true);
	  }  
	  catch(Exception ex){} 
   }

 // This can be defined as an inner class if you want.
   static class Wave {
      Point2D.Double fireLocation;
      int direction;
	//  int surf;
      double[] scan;
	//  int[] factors;
	  double dist;
      double bulletVelocity, directAngle, distanceTraveled;
   }

 // 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 posNegLimit(double value, double max) {
      return Math.max(-max, Math.min(value, max));
   }
   
	 private static int fieldContains(Point2D.Double source, double heading, double distance) {
    return Integer.signum(new Rectangle2D.Double(20, 20, 760, 560).outcode(
        project(source, heading, distance)));
  }
  
	static final String WEIGHT_TABLE = ""
	+ (char)1 + (char)1 + (char)1 + (char)250 
	+ (char)5 + (char)10 + (char)8 + (char)2;	
	
	public static final String ANGLE_TABLE = ""
	+ (char)-20 + (char)0 + (char)20 + (char)22 + (char)-22
	
	+ (char)26 + (char)-26  + (char)30 + (char)-30 
	+ (char)16 + (char)-16 + (char)24 + (char)-24 
	+ (char)28 + (char)-28 + (char)32 + (char)-32
	+ (char)34 + (char)-34 + (char)36 + (char)-36
	+ (char)38 + (char)-38 + (char)40 + (char)-40
	
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0
	+ (char)0 + (char)0 + (char)0 + (char)0 + (char)0;
	
	public static final String WALL_TABLE = ""
	+ (char)6 + (char)6 + (char)6 + (char)6 + (char)6
	+ (char)10 + (char)10 + (char)10 + (char)10 + (char)10
	+ (char)15 + (char)15 + (char)15 + (char)15 + (char)15
	+ (char)30 + (char)30 + (char)30 + (char)30 + (char)30
	+ (char)-6 + (char)-10 + (char)-15 + (char)-30;
	
	public static final String MOVEMENT_WALL_TABLE = ""
	+ (char)2 + (char)2
	+ (char)4 + (char)4
	+ (char)-3;
  	
	static final String BP300 = ""
	+ (char)300 + (char)300 + (char)300 + (char)300
	+ (char)300 + (char)300 + (char)300 + (char)300
	+ (char)300 + (char)300 + (char)300 + (char)300
	+ (char)300 + (char)300 + (char)300 + (char)300
	+ (char)300 + (char)300 + (char)300 + (char)300;	
	
	static final String BP250 = ""	
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250 + (char)250;
	
	static final String BP200 = ""	
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200;		
		
	static final String BP30 = ""
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)30 + (char)30 + (char)30 + (char)30;
	
	static final String BP10 = ""	
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10 + (char)10;		
	
	static final String CLOSE_DISTANCE = ""	
	+ BP300
	+ BP300
	+ BP300
	+ BP300
	+ BP300
	+ BP300
	
	+ (char)300 + (char)300 + (char)300 + (char)300
	+ (char)300 + (char)300 + (char)300;	
	
	static final String MID_CLOSE_DISTANCE = ""	
	+ (char)10 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	
	+ BP250
	+ BP250
	+ BP250
	+ BP250
	+ BP250
	
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250 + (char)250
	+ (char)250 + (char)250 + (char)250;	
	
	static final String MID_DISTANCE = ""	
	+ (char)10 + (char)10 + (char)10 + (char)200
	+ (char)200 + (char)200 + (char)200
	
	+ BP200
	+ BP200
	+ BP200
	+ BP200
	+ BP250
	+ BP250	;	
	
	static final String MID_LONG_DISTANCE = ""	
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10

	+ BP30
	
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)150 + (char)150 + (char)150 + (char)150
	+ (char)150 + (char)150 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	
	+ BP200
	+ BP200
	+ BP200
	+ BP250;
	
	static final String LONG_DISTANCE = ""	
	+ (char)10 + (char)10 + (char)10 + (char)10
	+ (char)10 + (char)10 + (char)10	
	
	+ BP30
	
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)30 + (char)30 + (char)30 + (char)30
	+ (char)150 + (char)150 + (char)150 + (char)150
	+ (char)150 + (char)150 + (char)200 + (char)200
	+ (char)200 + (char)200 + (char)200 + (char)200
	
	+ BP200
	+ BP200
	+ BP200
	+ BP250;
	
	static final String BULLET_POWER_TABLE = ""
	//0 * 127
	+ CLOSE_DISTANCE
	
	//1 * 127	
	+ CLOSE_DISTANCE
	
	//2 * 127	
	+ CLOSE_DISTANCE
	
	//3 * 127	
	+ MID_CLOSE_DISTANCE
	
	//4 * 127	
	+ MID_CLOSE_DISTANCE
	
	//5 * 127
	+ MID_CLOSE_DISTANCE
	
	//6 * 127
	+ MID_CLOSE_DISTANCE
	
	//7 * 127
	+ MID_CLOSE_DISTANCE
	
	//8 * 127
	+ MID_CLOSE_DISTANCE
		
	//9 * 127	
	+ MID_DISTANCE
	
	//10 * 127	
	+ MID_DISTANCE
	
	//11 * 127	
	+ MID_DISTANCE
	
	//12 * 127
	+ MID_DISTANCE
	
	//13 * 127
	+ MID_DISTANCE
	
	//14 * 127
	+ MID_DISTANCE
	
	//15 * 127
	+ MID_LONG_DISTANCE
	
	//16 * 127
	+ MID_LONG_DISTANCE
	
	//17 * 127
	+ MID_LONG_DISTANCE
	
	//18 * 127
	+ MID_LONG_DISTANCE
	
	//19 * 127
	+ MID_LONG_DISTANCE
	
	//20 * 127
	+ MID_LONG_DISTANCE
	
	//21 * 127
	+ LONG_DISTANCE
	
	//22 * 127
	+ LONG_DISTANCE
	
	//23 * 127
	+ LONG_DISTANCE
	
	//24 * 127
	+ LONG_DISTANCE
	
	//25 * 127
	+ LONG_DISTANCE
	
	//26 * 127
	+ LONG_DISTANCE
	
	//27 * 127
	+ LONG_DISTANCE
	
	//28 * 127
	+ LONG_DISTANCE
	
	//29 * 127
	+ LONG_DISTANCE
	
	//30 * 127
	+ LONG_DISTANCE
	
	//31 * 127
	+ LONG_DISTANCE
	
	//32 * 127
	+ LONG_DISTANCE
	
	+ LONG_DISTANCE + LONG_DISTANCE + LONG_DISTANCE + LONG_DISTANCE + LONG_DISTANCE;
	
 // log of enemy movement, initialize to something to avoid out-of-bounds
  static String enemyHistory = "" +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 +
      (char)0 + (char)3 + (char)0 + (char)-3 + (char)0 + (char)3 + (char)0 + (char)-3 + (char)0;
}