Midboss/Score-Estimation based Firepower Selection

From Robowiki
< Midboss
Revision as of 11:11, 23 June 2021 by Rednaxela (talk | contribs) (reword)
Jump to navigation Jump to search

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 ags.util.Range;
import ags.util.StatMath;
import java.io.PrintStream;
import java.util.HashMap;
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) {
    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);
    double bestScore = (-1.0D / 0.0D);
    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;
  }

  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
  {
    HashMap<SimConfig, Double> cache = new HashMap();
    double myHitrate;
    double myDamage;
    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;
      this.myLossRate = (myLossBase * (1.0D - 3.0D * myHitrate));
      this.myLossRateSD = (myLossBase * myLossBase * 3.0D * 3.0D * (myHitrate * (1.0D - myHitrate)));
      double myKillBase = this.myDamage / this.myCooldown;
      this.myKillRate = (myKillBase * myHitrate);
      this.myKillRateSD = (myKillBase * myKillBase * (myHitrate * (1.0D - myHitrate)));

      double enemyLossBase = enemyPower / (this.enemyCooldown + enemyCoolticks);
      this.enemyLossRate = (enemyLossBase * (1.0D - 3.0D * enemyHitrate));
      this.enemyLossRateSD = (enemyLossBase * enemyLossBase * 3.0D * 3.0D * (enemyHitrate * (1.0D - enemyHitrate)));
      double enemyKillBase = this.enemyDamage / (this.enemyCooldown + enemyCoolticks);
      this.enemyKillRate = (enemyKillBase * enemyHitrate);
      this.enemyKillRateSD = (enemyKillBase * enemyKillBase * (enemyHitrate * (1.0D - enemyHitrate)));
    }

    private double estimateScore(double myEnergy, double myDamageTaken, double enemyEnergy, double enemyDamageTaken) {
      return estimateScore(0, 1.0D, myEnergy, Integer.valueOf(0), myDamageTaken, enemyEnergy, Integer.valueOf(Math.max(1, this.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.0D;
      if (enemyHeat == null) enemyEnergy = 0.0D;

      if ((myEnergy == 0.0D) || (enemyEnergy == 0.0D)) {
        if ((myEnergy == 0.0D) && (enemyEnergy == 0.0D))
        {
          double ret = 0.1D * enemyDamageTaken - 0.1D * myDamageTaken;
          return ret;
        }if (enemyEnergy == 0.0D)
        {
          double ret = 60.0D + 0.2D * enemyDamageTaken;
          return ret;
        }

        double ret = -60.0D - 0.2D * myDamageTaken;
        return ret;
      }

      if ((prob < 0.002D) || (depth > 30)) {
        double myRate = this.myLossRate + this.enemyKillRate;
        double myRateSD = this.myLossRateSD + this.enemyKillRateSD;
        double enemyRate = this.enemyLossRate + this.myKillRate;
        double enemyRateSD = this.enemyLossRateSD + this.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.0D ? myEnergy / myRate : (1.0D / 0.0D);
        double enemyTimeLeft = enemyRate > 0.0D ? enemyEnergy / enemyRate : (1.0D / 0.0D);
        double expectedTimeLeft = Math.min(myTimeLeft, enemyTimeLeft);
        if (Double.isInfinite(expectedTimeLeft)) expectedTimeLeft = 0.0D;

        myDamageTaken += expectedTimeLeft * this.enemyKillRate;
        enemyDamageTaken += expectedTimeLeft * this.myKillRate;
        double thisBulletScore = expectedTimeLeft * (this.myKillRate - this.enemyKillRate);

        double winChance = 1.0D - StatMath.phi(0.0D, ratingSum, Math.sqrt(ratingSumSD));

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

      SimConfig conf = new SimConfig(myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
      Double cachevalue = (Double)this.cache.get(conf);
      if (cachevalue != null) return cachevalue.doubleValue();

      if ((enemyHeat == null) || (myHeat.intValue() <= enemyHeat.intValue()))
      {
        double hitDamage = Math.min(enemyEnergy, this.myDamage);

        if (this.myPower < myEnergy) {
          myEnergy -= this.myPower;
          double hitProb = this.myHitrate;
          enemyHeat = Integer.valueOf(enemyHeat.intValue() - myHeat.intValue());
          myHeat = Integer.valueOf(this.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 missScore = hitProb < 1.0D ? estimateScore(depth + 1, prob * (1.0D - hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0D;
          double ret = hitScore * hitProb + missScore * (1.0D - hitProb);
          this.cache.put(conf, Double.valueOf(ret));
          return ret;
        }
        myHeat = null;
        double ret = estimateScore(depth + 1, prob, myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken);
        this.cache.put(conf, Double.valueOf(ret));
        return ret;
      }
      if (enemyHeat != null)
      {
        enemyHeat = Integer.valueOf(this.enemyCooldown + this.enemyCoolticks);
        double hitDamage = Math.min(myEnergy, this.enemyDamage);

        if (this.enemyPower < enemyEnergy) {
          enemyEnergy -= this.enemyPower;
          double hitProb = this.enemyHitrate;
          myHeat = Integer.valueOf(myHeat.intValue() - enemyHeat.intValue());
          enemyHeat = Integer.valueOf(this.enemyCooldown + this.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 missScore = hitProb < 1.0D ? estimateScore(depth + 1, prob * (1.0D - hitProb), myEnergy, myHeat, myDamageTaken, enemyEnergy, enemyHeat, enemyDamageTaken) : 0.0D;
          double ret = hitScore * hitProb + missScore * (1.0D - hitProb);
          this.cache.put(conf, Double.valueOf(ret));
          return ret;
        }
        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;
      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)
      {
        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));
      }

      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;
      }
    }
  }
}