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

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