Difference between revisions of "Midboss/Score-Estimation based Firepower Selection"

From Robowiki
Jump to navigation Jump to search
(Post Midboss 1s' old fancy firepower selection system)
 
(Whoops, posted a bad version of BulletPowerSelector that was based on de-compiling a hack-y version once upon a time. Found the real code.)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
Once upon a time in 2010, in [[Talk:Midboss#1s|Midboss 1s]] I was employing a very intelligent firepower selection algorithm based on score prediction.
+
Once upon a time in 2010-2011 some versions of Midboss I was employing a very intelligent firepower selection algorithm based on score prediction.
  
The current version of Midboss in the rumble, is actually an older revision that doesn't include this, because this old code had some performance problems, but here it is. Yes, I consider it rather ugly at this point, but some folks might find it interesting:
+
The current version of Midboss in the rumble is not using the most recent version of this code than is pasted here, and I'm not quite sure how different the old version is off hand. In any case, I consider it rather ugly at this point, and it's quite slow, but some folks might find it interesting:
  
 
<syntaxhighlight>
 
<syntaxhighlight>
 
package ags.muse.gun;
 
package ags.muse.gun;
  
import ags.util.Range;
 
import ags.util.StatMath;
 
import java.io.PrintStream;
 
import java.util.HashMap;
 
 
import robocode.Rules;
 
import robocode.Rules;
  
public class BulletPowerSelector
+
public class BulletPowerSelector {
{
+
    HitrateEstimator selfEstimator;
  HitrateEstimator selfEstimator;
+
    HitrateEstimator enemyEstimator;
  HitrateEstimator enemyEstimator;
 
  
  public BulletPowerSelector()
+
    public BulletPowerSelector() {
  {
+
        this.selfEstimator = new HitrateEstimator();
    this.selfEstimator = new HitrateEstimator();
 
  }
 
 
 
  public void setEnemyEstimator(HitrateEstimator enemyEstimator) {
 
    this.enemyEstimator = enemyEstimator;
 
  }
 
 
 
  public void waveEnd(SWave wave) {
 
    this.selfEstimator.waveEnd(wave.getHitRange().intersects(wave.getAimGF()), wave.getInitDistance(), wave.getSpeed());
 
  }
 
 
 
  public double getBestPower(double distance, double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken, double enemyFirepower, int enemyCoolticks) {
 
    if (enemyEnergy < 0.1D)
 
      return 0.1D;
 
    if (distance < 50.0D) {
 
      return 3.0D;
 
 
     }
 
     }
  
     double bestPower = (0.0D / 0.0D);
+
     public void setEnemyEstimator(HitrateEstimator enemyEstimator) {
    double bestScore = (-1.0D / 0.0D);
+
        this.enemyEstimator = enemyEstimator;
    double pNeeded = Math.max(0.1D, damageToBulletPower(enemyEnergy));
 
 
 
    for (int i = 1; i <= 60; i++) {
 
      double p = i / 20.0D;
 
      if (p > 3.0D) p = 3.0D;
 
      Estimation e = new Estimation(distance, p, enemyFirepower, enemyCoolticks);
 
      double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
 
 
 
      if (score > bestScore) {
 
        bestScore = score;
 
        bestPower = p;
 
      }
 
 
 
 
     }
 
     }
  
     return bestPower;
+
     public void waveEnd(SWave wave) {
  }
+
        selfEstimator.waveEnd(wave.getHitRange().intersects(wave.getAimGF()), wave.getInitDistance(), wave.getSpeed());
 
 
  private static double damageToBulletPower(double energy)
 
  {
 
    if (energy / 4.0D <= 1.0D) {
 
      return energy / 4.0D;
 
 
     }
 
     }
    return (energy + 2.0D) / 6.0D;
 
  }
 
  
  public void testEstimator() {
+
    public double getBestPower(double distance, double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken, double enemyFirepower, int enemyCoolticks) {
    long ot = System.nanoTime();
+
        if (enemyEnergy < 0.1)
    for (int i = 2; i <= 60; i++) {
+
            return 0.1;
      double myPower = i / 20.0D; double enemyPower = 0.09999999999999432D;
+
        if (distance < 50)
      double myHitrate = 0.8823898603645636D; double enemyHitrate = 0.8969126319398489D;
+
            return 3.0;
      double myEnergy = 113.19999999999999D; double enemyEnergy = 64.200000000000017D;
+
        //if (selfEstimator.getWaveCount()<20 || enemyEstimator.getWaveCount()<20)
      double myDamageTaken = 0.8000000000000019D; double enemyDamageTaken = 36.0D;
+
        //    return Math.min(1.9, damageToBulletPower(enemyEnergy));
  
      Estimation e = new Estimation(myPower, myHitrate, enemyPower, enemyHitrate, 0);
+
        double bestPower=Double.NaN;
      double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
+
        double bestScore=Double.NEGATIVE_INFINITY;
      System.out.println(myPower + " Test score: " + score);
+
        double pNeeded = Math.max(0.1, damageToBulletPower(enemyEnergy));
 +
        //java.text.DecimalFormat twoPlaces = new java.text.DecimalFormat("00.00");
 +
        //Estimation be = null;
 +
        //Estimation me = null;
 +
        for (int i = 2; i <= 60; i++) {
 +
            double p = i/20.0;
 +
            if (p>3) p = 3;
 +
            Estimation e = new Estimation(distance, p, enemyFirepower, enemyCoolticks);
 +
            double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
 +
            //System.out.print(twoPlaces.format(score)+" \t");
 +
            //me = e;
 +
            if (score > bestScore) {
 +
                bestScore = score;
 +
                bestPower = p;
 +
                //be = e;
 +
            }
 +
        }
 +
        /*if (pNeeded < bestPower) {
 +
            System.out.print("Best Power: "+bestPower);
 +
            System.out.println("\tNeeded Power: "+pNeeded);
 +
            System.out.println("Best Score: "+bestScore);
 +
        }*/
 +
        return bestPower;
 
     }
 
     }
    System.out.println(System.nanoTime() - ot);
 
  }
 
  public static void main(String[] args) {
 
    BulletPowerSelector bps = new BulletPowerSelector();
 
    bps.testEstimator();
 
  }
 
  
  public class Estimation
+
     private static double damageToBulletPower(double energy) {
  {
+
        if (energy / 4.0D <= 1.0D) {
    HashMap<SimConfig, Double> cache = new HashMap();
+
            return energy / 4.0D;
    double myHitrate;
+
        }
    double myDamage;
+
        return (energy + 2.0D) / 6.0D;
    double myPower;
 
    double enemyHitrate;
 
    double enemyDamage;
 
    double enemyPower;
 
     double myLossRate;
 
    double myKillRate;
 
    double enemyLossRate;
 
    double enemyKillRate;
 
    double myLossRateSD;
 
    double myKillRateSD;
 
    double enemyLossRateSD;
 
    double enemyKillRateSD;
 
    int enemyCoolticks;
 
    int enemyCooldown;
 
    int myCooldown;
 
    double distance;
 
    int chits;
 
    int cmiss;
 
 
 
    private Estimation(double distance, double myPower, double enemyPower, int enemyCoolticks)
 
    {
 
      this(myPower, BulletPowerSelector.this.selfEstimator.getHitrate(distance, myPower), enemyPower, BulletPowerSelector.this.enemyEstimator.getHitrate(distance, enemyPower), enemyCoolticks);
 
      this.distance = distance;
 
 
     }
 
     }
    private Estimation(double myPower, double myHitrate, double enemyPower, double enemyHitrate, int enemyCoolticks) {
 
      this.enemyPower = enemyPower;
 
      this.enemyHitrate = Math.max(0.0001D, enemyHitrate);
 
      this.enemyDamage = Rules.getBulletDamage(enemyPower);
 
      this.enemyCooldown = (int)Math.ceil(Rules.getGunHeat(enemyPower) / 0.1D);
 
      this.enemyCoolticks = enemyCoolticks;
 
      this.myPower = myPower;
 
      this.myHitrate = Math.max(0.0001D, myHitrate);
 
      this.myDamage = Rules.getBulletDamage(myPower);
 
      this.myCooldown = (int)Math.ceil(Rules.getGunHeat(myPower) / 0.1D);
 
  
      double myLossBase = myPower / this.myCooldown;
+
    public void testEstimator() {
      this.myLossRate = (myLossBase * (1.0D - 3.0D * myHitrate));
+
        long ot = System.nanoTime();
      this.myLossRateSD = (myLossBase * myLossBase * 3.0D * 3.0D * (myHitrate * (1.0D - myHitrate)));
+
        for (int i = 2; i <= 60; i++) {
      double myKillBase = this.myDamage / this.myCooldown;
+
            double myPower = i / 20.0D;
      this.myKillRate = (myKillBase * myHitrate);
+
            double enemyPower = 0.09999999999999432D;
      this.myKillRateSD = (myKillBase * myKillBase * (myHitrate * (1.0D - myHitrate)));
+
            double myHitrate = 0.8823898603645636D;
 +
            double enemyHitrate = 0.8969126319398489D;
 +
            double myEnergy = 113.19999999999999D;
 +
            double enemyEnergy = 64.200000000000017D;
 +
            double myDamageTaken = 0.8000000000000019D;
 +
            double enemyDamageTaken = 36.0D;
  
      double enemyLossBase = enemyPower / (this.enemyCooldown + enemyCoolticks);
+
            Estimation e = new Estimation(myPower, myHitrate, enemyPower, enemyHitrate, 0);
      this.enemyLossRate = (enemyLossBase * (1.0D - 3.0D * enemyHitrate));
+
            double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
      this.enemyLossRateSD = (enemyLossBase * enemyLossBase * 3.0D * 3.0D * (enemyHitrate * (1.0D - enemyHitrate)));
+
            System.out.println(myPower + " Test score: " + score);
      double enemyKillBase = this.enemyDamage / (this.enemyCooldown + enemyCoolticks);
+
        }
      this.enemyKillRate = (enemyKillBase * enemyHitrate);
+
        System.out.println(System.nanoTime() - ot);
      this.enemyKillRateSD = (enemyKillBase * enemyKillBase * (enemyHitrate * (1.0D - enemyHitrate)));
 
 
     }
 
     }
  
     private double estimateScore(double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken) {
+
     public static void main(String[] args) {
      return estimateScore(0, 1.0D, myEnergy, Integer.valueOf(0), myDamageTaken, enemyEnergy, Integer.valueOf(Math.max(1, this.enemyCooldown / 2)), enemyDamageTaken);
+
        BulletPowerSelector bps = new BulletPowerSelector();
 +
        bps.testEstimator();
 
     }
 
     }
  
     private double estimateScore(int depth, double prob, double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken) {
+
     public class Estimation {
      if (myHeat == null) myEnergy = 0.0D;
+
        java.util.HashMap<SimConfig, Double> cache = new java.util.HashMap<SimConfig, Double>();
      if (enemyHeat == null) enemyEnergy = 0.0D;
+
        double myHitrate, myDamage, myPower;
 +
        double enemyHitrate, enemyDamage, enemyPower;
 +
        double myLossRate, myKillRate, enemyLossRate, enemyKillRate;
 +
        double myLossRateSD, myKillRateSD, enemyLossRateSD, enemyKillRateSD;
 +
        int enemyCoolticks;
 +
        int enemyCooldown, myCooldown;
 +
        double distance;
 +
        int chits, cmiss;
 +
        private Estimation(double distance, double myPower, double enemyPower, int enemyCoolticks) {
 +
            this(myPower,  selfEstimator.getHitrate(distance, myPower), enemyPower, enemyEstimator.getHitrate(distance, enemyPower), enemyCoolticks);
 +
            this.distance = distance;
 +
        }
 +
        private Estimation(double myPower, double myHitrate, double enemyPower, double enemyHitrate, int enemyCoolticks) {
 +
            this.enemyPower = enemyPower;
 +
            this.enemyHitrate = Math.max(0.0001, enemyHitrate);
 +
            this.enemyDamage = Rules.getBulletDamage(enemyPower);
 +
            this.enemyCooldown = (int)Math.ceil(Rules.getGunHeat(enemyPower)/0.1);
 +
            this.enemyCoolticks = enemyCoolticks;
 +
            this.myPower = myPower;
 +
            this.myHitrate =  Math.max(0.0001, myHitrate);
 +
            this.myDamage = Rules.getBulletDamage(myPower);
 +
            this.myCooldown = (int)Math.ceil(Rules.getGunHeat(myPower)/0.1);
  
      if ((myEnergy == 0.0D) || (enemyEnergy == 0.0D)) {
+
            double myLossBase = myPower/myCooldown;
        if ((myEnergy == 0.0D) && (enemyEnergy == 0.0D))
+
            this.myLossRate = myLossBase*(1-3*myHitrate);
        {
+
            this.myLossRateSD = myLossBase*myLossBase*3*3*(myHitrate*(1-myHitrate));
          double ret = 0.1D * enemyDamageTaken - 0.1D * myDamageTaken;
+
            double myKillBase = myDamage/myCooldown;
          return ret;
+
            this.myKillRate = myKillBase*myHitrate;
        }if (enemyEnergy == 0.0D)
+
            this.myKillRateSD = myKillBase*myKillBase*(myHitrate*(1-myHitrate));
        {
+
           
          double ret = 60.0D + 0.2D * enemyDamageTaken;
+
            double enemyLossBase = enemyPower / (enemyCooldown+enemyCoolticks);
          return ret;
+
            this.enemyLossRate = enemyLossBase*(1-3*enemyHitrate);
 +
            this.enemyLossRateSD = enemyLossBase*enemyLossBase*3*3*(enemyHitrate*(1-enemyHitrate));
 +
            double enemyKillBase = enemyDamage/(enemyCooldown+enemyCoolticks);
 +
            this.enemyKillRate = enemyKillBase*enemyHitrate;
 +
            this.enemyKillRateSD = enemyKillBase*enemyKillBase*(enemyHitrate*(1-enemyHitrate));
 
         }
 
         }
  
         double ret = -60.0D - 0.2D * myDamageTaken;
+
         private double estimateScore(double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken) {
         return ret;
+
            return estimateScore(0, 1, myEnergy, 0, myDamageTaken, enemyEnergy, Math.max(1, enemyCooldown/2), enemyDamageTaken);
      }
+
         }
  
      if ((prob < 0.002D) || (depth > 30)) {
+
        private double estimateScore(int depth, double prob, double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken) {
        double myRate = this.myLossRate + this.enemyKillRate;
+
            if (myHeat == null) myEnergy = 0;
        double myRateSD = this.myLossRateSD + this.enemyKillRateSD;
+
            if (enemyHeat == null) enemyEnergy = 0;
        double enemyRate = this.enemyLossRate + this.myKillRate;
 
        double enemyRateSD = this.enemyLossRateSD + this.myKillRateSD;
 
  
        double myRating = myEnergy * enemyRate;
+
            // If someone is dead, finish the calculation
        double myRatingSD = myEnergy * myEnergy * enemyRateSD;
+
            if (myEnergy == 0 || enemyEnergy == 0) {
        double enemyRating = enemyEnergy * myRate;
+
                if (myEnergy == 0 && enemyEnergy == 0) {
        double enemyRatingSD = enemyEnergy * enemyEnergy * myRateSD;
+
                    // Tied
 +
                    double ret = 0.1*enemyDamageTaken - 0.1*myDamageTaken;
 +
                    return ret;
 +
                } else if (enemyEnergy == 0) {
 +
                    // Win
 +
                    double ret = 60 + 0.2*enemyDamageTaken;
 +
                    return ret;
 +
                } else {
 +
                    // Loss
 +
                    double ret = - 60 - 0.2*myDamageTaken;
 +
                    return ret;
 +
                }
 +
            }
  
        double ratingSum = myRating - enemyRating;
+
            // If depth is high, do a quick estimation to skip forward
        double ratingSumSD = myRatingSD + enemyRatingSD;
+
            if (prob < 0.002 || depth > 30) {
 +
                double myRate = myLossRate + enemyKillRate;
 +
                double myRateSD = myLossRateSD + enemyKillRateSD;
 +
                double enemyRate = enemyLossRate + myKillRate;
 +
                double enemyRateSD = enemyLossRateSD + myKillRateSD;
  
        double myTimeLeft = myRate > 0.0D ? myEnergy / myRate : (1.0D / 0.0D);
+
                double myRating = myEnergy * enemyRate;
        double enemyTimeLeft = enemyRate > 0.0D ? enemyEnergy / enemyRate : (1.0D / 0.0D);
+
                double myRatingSD = myEnergy * myEnergy * enemyRateSD;
        double expectedTimeLeft = Math.min(myTimeLeft, enemyTimeLeft);
+
                double enemyRating = enemyEnergy * myRate;
        if (Double.isInfinite(expectedTimeLeft)) expectedTimeLeft = 0.0D;
+
                double enemyRatingSD = enemyEnergy * enemyEnergy * myRateSD;
  
        myDamageTaken += expectedTimeLeft * this.enemyKillRate;
+
                double ratingSum = myRating - enemyRating;
        enemyDamageTaken += expectedTimeLeft * this.myKillRate;
+
                double ratingSumSD = myRatingSD + enemyRatingSD;
        double thisBulletScore = expectedTimeLeft * (this.myKillRate - this.enemyKillRate);
+
               
 +
                double myTimeLeft = (myRate > 0) ? myEnergy / myRate : Double.POSITIVE_INFINITY;
 +
                double enemyTimeLeft = (enemyRate > 0) ? enemyEnergy / enemyRate : Double.POSITIVE_INFINITY;
 +
                double expectedTimeLeft = Math.min(myTimeLeft, enemyTimeLeft);
 +
                if (Double.isInfinite(expectedTimeLeft)) expectedTimeLeft = 0;
 +
               
 +
                // Predict the future state of affairs
 +
                myDamageTaken += expectedTimeLeft*enemyKillRate;
 +
                enemyDamageTaken += expectedTimeLeft*myKillRate;
 +
                double thisBulletScore = expectedTimeLeft*(myKillRate - enemyKillRate);
  
        double winChance = 1.0D - StatMath.phi(0.0D, ratingSum, Math.sqrt(ratingSumSD));
+
                double winChance = 1.0 - ags.util.StatMath.phi(0, ratingSum, Math.sqrt(ratingSumSD));
  
        double h1 = (1.0D - winChance) * (-60000.0D - 0.2D * myDamageTaken);
+
                double h1 = (1.0-winChance) * (-60 - 0.2*myDamageTaken);
        double h2 = winChance * (60000.0D + 0.2D * enemyDamageTaken);
+
                double h2 = winChance * (60 + 0.2*enemyDamageTaken);
        double ret = thisBulletScore + h1 + h2;
+
                double ret = thisBulletScore + h1 + h2;
        return ret;
+
                return ret;
      }
+
            }
  
      SimConfig conf = new SimConfig(myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
+
            // Check cache for the possibility already being calculated
      Double cachevalue = (Double)this.cache.get(conf);
+
            SimConfig conf = new SimConfig(myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
      if (cachevalue != null) return cachevalue.doubleValue();
+
            Double cachevalue = cache.get(conf);
 +
            if (cachevalue != null) { return cachevalue; }
  
      if ((enemyHeat == null) || (myHeat.intValue() <= enemyHeat.intValue()))
+
            if (enemyHeat == null || myHeat <= enemyHeat) {
      {
+
                // Self fires
        double hitDamage = Math.min(enemyEnergy, this.myDamage);
+
                // Calculate next gunheats/energy
 +
                double hitDamage = Math.min(enemyEnergy, myDamage);
  
        if (this.myPower < myEnergy) {
+
                if (myPower < myEnergy) {
          myEnergy -= this.myPower;
+
                    myEnergy -= myPower;
          double hitProb = this.myHitrate;
+
                    double hitProb = myHitrate;
          enemyHeat = Integer.valueOf(enemyHeat.intValue() - myHeat.intValue());
+
                    enemyHeat -= myHeat;
          myHeat = Integer.valueOf(this.myCooldown);
+
                    myHeat = myCooldown;
  
          double hitScore = hitProb > 0.0D ? hitDamage + estimateScore(depth + 1, prob * hitProb, myEnergy + 3.0D * this.myPower, myHeat, myDamageTaken, enemyEnergy - hitDamage, enemyHeat, enemyDamageTaken + hitDamage) : 0.0D;
+
                    double hitScore = hitDamage + estimateScore(depth+1, prob*hitProb, myEnergy+3*myPower, myHeat, myDamageTaken, enemyEnergy-hitDamage, enemyHeat, enemyDamageTaken+hitDamage);
          double missScore = hitProb < 1.0D ? estimateScore(depth + 1, prob * (1.0D - hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0D;
+
                    double missScore = estimateScore(depth+1, prob*(1-hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
          double ret = hitScore * hitProb + missScore * (1.0D - hitProb);
+
                    double ret = hitScore*hitProb + missScore*(1-hitProb);
          this.cache.put(conf, Double.valueOf(ret));
+
                    cache.put(conf, ret);
          return ret;
+
                    return ret;
        }
+
                } else {
        myHeat = null;
+
                    myHeat = null;
        double ret = estimateScore(depth + 1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
+
                    double ret = estimateScore(depth+1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
        this.cache.put(conf, Double.valueOf(ret));
+
                    cache.put(conf, ret);
        return ret;
+
                    return ret;
      }
+
                }
      if (enemyHeat != null)
+
            } else if (enemyHeat != null) {
      {
+
                // Enemy fires
        enemyHeat = Integer.valueOf(this.enemyCooldown + this.enemyCoolticks);
+
                // Calculate next gunheats/energy
        double hitDamage = Math.min(myEnergy, this.enemyDamage);
+
                enemyHeat = enemyCooldown+enemyCoolticks;
 +
                double hitDamage = Math.min(myEnergy, enemyDamage);
  
        if (this.enemyPower < enemyEnergy) {
+
                if (enemyPower < enemyEnergy) {
          enemyEnergy -= this.enemyPower;
+
                    enemyEnergy -= enemyPower;
          double hitProb = this.enemyHitrate;
+
                    double hitProb = enemyHitrate;
          myHeat = Integer.valueOf(myHeat.intValue() - enemyHeat.intValue());
+
                    myHeat -= enemyHeat;
          enemyHeat = Integer.valueOf(this.enemyCooldown + this.enemyCoolticks);
+
                    enemyHeat = enemyCooldown+enemyCoolticks;
  
          double hitScore = hitProb > 0.0D ? -hitDamage + estimateScore(depth + 1, prob * hitProb, myEnergy - hitDamage, myHeat, myDamageTaken + hitDamage, enemyEnergy + 3.0D * this.enemyPower, enemyHeat, enemyDamageTaken) : 0.0D;
+
                    double hitScore = hitProb > 0.0D ? -hitDamage + estimateScore(depth+1, prob*hitProb, myEnergy-hitDamage, myHeat, myDamageTaken+hitDamage, enemyEnergy+3*enemyPower, enemyHeat, enemyDamageTaken) : 0.0;
          double missScore = hitProb < 1.0D ? estimateScore(depth + 1, prob * (1.0D - hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0D;
+
                    double missScore = hitProb < 1.0D ? estimateScore(depth+1, prob*(1-hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0;
          double ret = hitScore * hitProb + missScore * (1.0D - hitProb);
+
                    double ret = hitScore*hitProb + missScore*(1-hitProb);
          this.cache.put(conf, Double.valueOf(ret));
+
                    cache.put(conf, ret);
          return ret;
+
                    return ret;
 +
                } else {
 +
                    enemyHeat = null;
 +
                    double ret = estimateScore(depth+1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
 +
                    cache.put(conf, ret);
 +
                    return ret;
 +
                }
 +
            } else {
 +
                return 0;
 +
            }
 
         }
 
         }
        enemyHeat = null;
 
        double ret = estimateScore(depth + 1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
 
        this.cache.put(conf, Double.valueOf(ret));
 
        return ret;
 
      }
 
  
      return 0.0D;
+
        private class SimConfig {
    }
+
            private final double myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken;
 +
            private final Integer myHeat, enemyHeat;
 +
            private final int hash;
 +
           
 +
            private SimConfig(double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken) {
 +
                this.myEnergy = myEnergy;
 +
                this.myHeat = myHeat;
 +
                this.myDamageTaken = myDamageTaken;
 +
                this.enemyEnergy = enemyEnergy;
 +
                this.enemyHeat = enemyHeat;
 +
                this.enemyDamageTaken = enemyDamageTaken;
 +
                long longhash = (
 +
                        Double.doubleToRawLongBits(myEnergy)^
 +
                        Double.doubleToRawLongBits(myDamageTaken)^
 +
                        Double.doubleToRawLongBits(enemyEnergy)^
 +
                        Double.doubleToRawLongBits(enemyDamageTaken)+
 +
                        ((myHeat != null) ? myHeat.hashCode() : -1) +
 +
                        ((enemyHeat != null) ? enemyHeat.hashCode() : -2)
 +
                        );
 +
                this.hash = ((int)longhash) ^ ((int)Long.reverse(longhash));
 +
            }
  
    private class SimConfig
+
            @Override
    {
+
            public int hashCode() {
      private final double myEnergy;
+
                return hash;
      private final double myDamageTaken;
+
            }
      private final double enemyEnergy;
 
      private final double enemyDamageTaken;
 
      private final Integer myHeat;
 
      private final Integer enemyHeat;
 
      private final int hash;
 
  
      private SimConfig(double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken)
+
            @Override
      {
+
            public boolean equals(Object o) {
        this.myEnergy = myEnergy;
+
                if (!(o instanceof SimConfig)) return false;
        this.myHeat = myHeat;
+
                SimConfig s = (SimConfig)o;
        this.myDamageTaken = myDamageTaken;
+
                if (myEnergy != s.myEnergy) return false;
        this.enemyEnergy = enemyEnergy;
+
                else if (enemyEnergy != s.enemyEnergy) return false;
        this.enemyHeat = enemyHeat;
+
                else if (myDamageTaken != s.myDamageTaken) return false;
        this.enemyDamageTaken = enemyDamageTaken;
+
                else if (enemyDamageTaken != s.enemyDamageTaken) return false;
        long longhash = Double.doubleToRawLongBits(myEnergy) ^ Double.doubleToRawLongBits(myDamageTaken) ^ Double.doubleToRawLongBits(enemyEnergy) ^ Double.doubleToRawLongBits(enemyDamageTaken) + (myHeat != null ? myHeat.hashCode() : -1) + (enemyHeat != null ? enemyHeat.hashCode() : -2);
+
                else if (myHeat != s.myHeat) return false;
 
+
                else if (enemyHeat != s.enemyHeat) return false;
        this.hash = ((int)longhash ^ (int)Long.reverse(longhash));
+
                else return true;
      }
+
            }
 
+
        }
      public int hashCode()
 
      {
 
        return this.hash;
 
      }
 
 
 
      public boolean equals(Object o)
 
      {
 
        if (!(o instanceof SimConfig)) return false;
 
        SimConfig s = (SimConfig)o;
 
        if (this.myEnergy != s.myEnergy) return false;
 
        if (this.enemyEnergy != s.enemyEnergy) return false;
 
        if (this.myDamageTaken != s.myDamageTaken) return false;
 
        if (this.enemyDamageTaken != s.enemyDamageTaken) return false;
 
        if (this.myHeat != s.myHeat) return false;
 
        return this.enemyHeat == s.enemyHeat;
 
      }
 
 
     }
 
     }
  }
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 03:31, 24 June 2021

Once upon a time in 2010-2011 some versions of Midboss I was employing a very intelligent firepower selection algorithm based on score prediction.

The current version of Midboss in the rumble is not using the most recent version of this code than is pasted here, and I'm not quite sure how different the old version is off hand. In any case, I consider it rather ugly at this point, and it's quite slow, but some folks might find it interesting:

package ags.muse.gun;

import robocode.Rules;

public class BulletPowerSelector {
    HitrateEstimator selfEstimator;
    HitrateEstimator enemyEstimator;

    public BulletPowerSelector() {
        this.selfEstimator = new HitrateEstimator();
    }

    public void setEnemyEstimator(HitrateEstimator enemyEstimator) {
        this.enemyEstimator = enemyEstimator;
    }

    public void waveEnd(SWave wave) {
        selfEstimator.waveEnd(wave.getHitRange().intersects(wave.getAimGF()), wave.getInitDistance(), wave.getSpeed());
    }

    public double getBestPower(double distance, double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken, double enemyFirepower, int enemyCoolticks) {
        if (enemyEnergy < 0.1)
            return 0.1;
        if (distance < 50)
            return 3.0;
        //if (selfEstimator.getWaveCount()<20 || enemyEstimator.getWaveCount()<20)
        //    return Math.min(1.9, damageToBulletPower(enemyEnergy));

        double bestPower=Double.NaN;
        double bestScore=Double.NEGATIVE_INFINITY;
        double pNeeded = Math.max(0.1, damageToBulletPower(enemyEnergy));
        //java.text.DecimalFormat twoPlaces = new java.text.DecimalFormat("00.00");
        //Estimation be = null;
        //Estimation me = null;
        for (int i = 2; i <= 60; i++) {
            double p = i/20.0;
            if (p>3) p = 3;
            Estimation e = new Estimation(distance, p, enemyFirepower, enemyCoolticks);
            double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
            //System.out.print(twoPlaces.format(score)+" \t");
            //me = e;
            if (score > bestScore) {
                bestScore = score;
                bestPower = p;
                //be = e;
            }
        }
        /*if (pNeeded < bestPower) {
            System.out.print("Best Power: "+bestPower);
            System.out.println("\tNeeded Power: "+pNeeded);
            System.out.println("Best Score: "+bestScore);
        }*/
        return bestPower;
    }

    private static double damageToBulletPower(double energy) {
        if (energy / 4.0D <= 1.0D) {
            return energy / 4.0D;
        }
        return (energy + 2.0D) / 6.0D;
    }

    public void testEstimator() {
        long ot = System.nanoTime();
        for (int i = 2; i <= 60; i++) {
            double myPower = i / 20.0D;
            double enemyPower = 0.09999999999999432D;
            double myHitrate = 0.8823898603645636D;
            double enemyHitrate = 0.8969126319398489D;
            double myEnergy = 113.19999999999999D;
            double enemyEnergy = 64.200000000000017D;
            double myDamageTaken = 0.8000000000000019D;
            double enemyDamageTaken = 36.0D;

            Estimation e = new Estimation(myPower, myHitrate, enemyPower, enemyHitrate, 0);
            double score = e.estimateScore(myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken);
            System.out.println(myPower + " Test score: " + score);
        }
        System.out.println(System.nanoTime() - ot);
    }

    public static void main(String[] args) {
        BulletPowerSelector bps = new BulletPowerSelector();
        bps.testEstimator();
    }

    public class Estimation {
        java.util.HashMap<SimConfig, Double> cache = new java.util.HashMap<SimConfig, Double>();
        double myHitrate, myDamage, myPower;
        double enemyHitrate, enemyDamage, enemyPower;
        double myLossRate, myKillRate, enemyLossRate, enemyKillRate;
        double myLossRateSD, myKillRateSD, enemyLossRateSD, enemyKillRateSD;
        int enemyCoolticks;
        int enemyCooldown, myCooldown;
        double distance;
        int chits, cmiss;
        private Estimation(double distance, double myPower, double enemyPower, int enemyCoolticks) {
            this(myPower,  selfEstimator.getHitrate(distance, myPower), enemyPower, enemyEstimator.getHitrate(distance, enemyPower), enemyCoolticks);
            this.distance = distance;
        }
        private Estimation(double myPower, double myHitrate, double enemyPower, double enemyHitrate, int enemyCoolticks) {
            this.enemyPower = enemyPower;
            this.enemyHitrate = Math.max(0.0001, enemyHitrate);
            this.enemyDamage = Rules.getBulletDamage(enemyPower);
            this.enemyCooldown = (int)Math.ceil(Rules.getGunHeat(enemyPower)/0.1);
            this.enemyCoolticks = enemyCoolticks;
            this.myPower = myPower;
            this.myHitrate =  Math.max(0.0001, myHitrate);
            this.myDamage = Rules.getBulletDamage(myPower);
            this.myCooldown = (int)Math.ceil(Rules.getGunHeat(myPower)/0.1);

            double myLossBase = myPower/myCooldown;
            this.myLossRate = myLossBase*(1-3*myHitrate);
            this.myLossRateSD = myLossBase*myLossBase*3*3*(myHitrate*(1-myHitrate));
            double myKillBase = myDamage/myCooldown;
            this.myKillRate =  myKillBase*myHitrate;
            this.myKillRateSD = myKillBase*myKillBase*(myHitrate*(1-myHitrate));
            
            double enemyLossBase = enemyPower / (enemyCooldown+enemyCoolticks);
            this.enemyLossRate = enemyLossBase*(1-3*enemyHitrate);
            this.enemyLossRateSD = enemyLossBase*enemyLossBase*3*3*(enemyHitrate*(1-enemyHitrate));
            double enemyKillBase = enemyDamage/(enemyCooldown+enemyCoolticks);
            this.enemyKillRate = enemyKillBase*enemyHitrate;
            this.enemyKillRateSD = enemyKillBase*enemyKillBase*(enemyHitrate*(1-enemyHitrate));
        }

        private double estimateScore(double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken) {
            return estimateScore(0, 1, myEnergy, 0, myDamageTaken, enemyEnergy, Math.max(1, enemyCooldown/2), enemyDamageTaken);
        }

        private double estimateScore(int depth, double prob, double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken) {
            if (myHeat == null) myEnergy = 0;
            if (enemyHeat == null) enemyEnergy = 0;

            // If someone is dead, finish the calculation
            if (myEnergy == 0 || enemyEnergy == 0) {
                if (myEnergy == 0 && enemyEnergy == 0) {
                    // Tied
                    double ret = 0.1*enemyDamageTaken - 0.1*myDamageTaken;
                    return ret;
                } else if (enemyEnergy == 0) {
                    // Win
                    double ret = 60 + 0.2*enemyDamageTaken;
                    return ret;
                } else {
                    // Loss
                    double ret = - 60 - 0.2*myDamageTaken;
                    return ret;
                }
            }

            // If depth is high, do a quick estimation to skip forward
            if (prob < 0.002 || depth > 30) {
                double myRate = myLossRate + enemyKillRate;
                double myRateSD = myLossRateSD + enemyKillRateSD;
                double enemyRate = enemyLossRate + myKillRate;
                double enemyRateSD = enemyLossRateSD + myKillRateSD;

                double myRating = myEnergy * enemyRate;
                double myRatingSD = myEnergy * myEnergy * enemyRateSD;
                double enemyRating = enemyEnergy * myRate;
                double enemyRatingSD = enemyEnergy * enemyEnergy * myRateSD;

                double ratingSum = myRating - enemyRating;
                double ratingSumSD = myRatingSD + enemyRatingSD;
                
                double myTimeLeft = (myRate > 0) ? myEnergy / myRate : Double.POSITIVE_INFINITY;
                double enemyTimeLeft = (enemyRate > 0) ? enemyEnergy / enemyRate : Double.POSITIVE_INFINITY;
                double expectedTimeLeft = Math.min(myTimeLeft, enemyTimeLeft);
                if (Double.isInfinite(expectedTimeLeft)) expectedTimeLeft = 0;
                
                // Predict the future state of affairs
                myDamageTaken += expectedTimeLeft*enemyKillRate;
                enemyDamageTaken += expectedTimeLeft*myKillRate;
                double thisBulletScore = expectedTimeLeft*(myKillRate - enemyKillRate);

                double winChance = 1.0 - ags.util.StatMath.phi(0, ratingSum, Math.sqrt(ratingSumSD));

                double h1 = (1.0-winChance) * (-60 - 0.2*myDamageTaken);
                double h2 = winChance * (60 + 0.2*enemyDamageTaken);
                double ret = thisBulletScore + h1 + h2;
                return ret;
            }

            // Check cache for the possibility already being calculated
            SimConfig conf = new SimConfig(myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
            Double cachevalue = cache.get(conf);
            if (cachevalue != null) { return cachevalue; }

            if (enemyHeat == null || myHeat <= enemyHeat) {
                // Self fires
                // Calculate next gunheats/energy
                double hitDamage = Math.min(enemyEnergy, myDamage);

                if (myPower < myEnergy) {
                    myEnergy -= myPower;
                    double hitProb = myHitrate;
                    enemyHeat -= myHeat;
                    myHeat = myCooldown;

                    double hitScore = hitDamage + estimateScore(depth+1, prob*hitProb, myEnergy+3*myPower, myHeat, myDamageTaken, enemyEnergy-hitDamage, enemyHeat, enemyDamageTaken+hitDamage);
                    double missScore = estimateScore(depth+1, prob*(1-hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
                    double ret = hitScore*hitProb + missScore*(1-hitProb);
                    cache.put(conf, ret);
                    return ret;
                } else {
                    myHeat = null;
                    double ret = estimateScore(depth+1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
                    cache.put(conf, ret);
                    return ret;
                }
            } else if (enemyHeat != null) {
                // Enemy fires
                // Calculate next gunheats/energy
                enemyHeat = enemyCooldown+enemyCoolticks;
                double hitDamage = Math.min(myEnergy, enemyDamage);

                if (enemyPower < enemyEnergy) {
                    enemyEnergy -= enemyPower;
                    double hitProb = enemyHitrate;
                    myHeat -= enemyHeat;
                    enemyHeat = enemyCooldown+enemyCoolticks;

                    double hitScore = hitProb > 0.0D ? -hitDamage + estimateScore(depth+1, prob*hitProb, myEnergy-hitDamage, myHeat, myDamageTaken+hitDamage, enemyEnergy+3*enemyPower, enemyHeat, enemyDamageTaken) : 0.0;
                    double missScore = hitProb < 1.0D ? estimateScore(depth+1, prob*(1-hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0;
                    double ret = hitScore*hitProb + missScore*(1-hitProb);
                    cache.put(conf, ret);
                    return ret;
                } else {
                    enemyHeat = null;
                    double ret = estimateScore(depth+1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
                    cache.put(conf, ret);
                    return ret;
                }
            } else {
                return 0;
            }
        }

        private class SimConfig {
            private final double myEnergy, myDamageTaken, enemyEnergy, enemyDamageTaken;
            private final Integer myHeat, enemyHeat;
            private final int hash;
            
            private SimConfig(double myEnergy, Integer myHeat, double myDamageTaken, double enemyEnergy, Integer enemyHeat, double enemyDamageTaken) {
                this.myEnergy = myEnergy;
                this.myHeat = myHeat;
                this.myDamageTaken = myDamageTaken;
                this.enemyEnergy = enemyEnergy;
                this.enemyHeat = enemyHeat;
                this.enemyDamageTaken = enemyDamageTaken;
                long longhash = (
                        Double.doubleToRawLongBits(myEnergy)^
                        Double.doubleToRawLongBits(myDamageTaken)^
                        Double.doubleToRawLongBits(enemyEnergy)^
                        Double.doubleToRawLongBits(enemyDamageTaken)+
                        ((myHeat != null) ? myHeat.hashCode() : -1) +
                        ((enemyHeat != null) ? enemyHeat.hashCode() : -2)
                        );
                this.hash = ((int)longhash) ^ ((int)Long.reverse(longhash));
            }

            @Override
            public int hashCode() {
                return hash;
            }

            @Override
            public boolean equals(Object o) {
                if (!(o instanceof SimConfig)) return false;
                SimConfig s = (SimConfig)o;
                if (myEnergy != s.myEnergy) return false;
                else if (enemyEnergy != s.enemyEnergy) return false;
                else if (myDamageTaken != s.myDamageTaken) return false;
                else if (enemyDamageTaken != s.enemyDamageTaken) return false;
                else if (myHeat != s.myHeat) return false;
                else if (enemyHeat != s.enemyHeat) return false;
                else return true;
            }
        }
    }
}