Skip to content

Commit

Permalink
New method to calculate chance to stay unscathed
Browse files Browse the repository at this point in the history
@mattsc spotted that attack prediction sometimes gives incorrect results
for chance to stay unscathed when the other unit can die during the battle.

The old calculation algorithm worked by determining the chance that the
unit survives each unit unscathed, and multiplying the probabilities
together.

However, that method of probability calculations works only if the
attacks are independent, i.e. the probability of surviving *this* attack
unscathed doesn't depend on the chance of being already unscathed. And
that's not the case if either combatant can kill the other one.

If combatant A has killed combatant B, combatant B can't strike it any more
and therefore combatant A is more likely to *be* unscathed. Inversely, if A
is unscathed, it's more likely that B is dead. As a result, if A is
unscathed, A is also more likely to *stay* unscathed. The old method
couldn't take that into account and therefore produced incorrect results.

In my local tests, this new algorithm appears to produce correct results.
Only rounding errors (and drain ability) separate it from the HP
distribution tables.
  • Loading branch information
jyrkive authored and GregoryLundberg committed Nov 30, 2017
1 parent 13da901 commit 1c3ad7f
Showing 1 changed file with 22 additions and 3 deletions.
25 changes: 22 additions & 3 deletions src/attack_prediction.cpp
Expand Up @@ -2070,6 +2070,10 @@ void complex_fight(attack_prediction_mode mode,
const double original_opp_not_hit = opp_not_hit;
const double hit_chance = stats.chance_to_hit / 100.0;
const double opp_hit_chance = opp_stats.chance_to_hit / 100.0;
double self_hit = 0.0;
double opp_hit = 0.0;
double self_hit_unknown = 1.0; // Probability we don't yet know if A will be hit or not
double opp_hit_unknown = 1.0; // Ditto for B

// Prepare the matrix that will do our calculations.
std::unique_ptr<combat_matrix> m;
Expand All @@ -2086,27 +2090,42 @@ void complex_fight(attack_prediction_mode mode,
for(unsigned int i = 0; i < max_attacks; ++i) {
if(i < strikes) {
debug(("A strikes\n"));
opp_not_hit *= 1.0 - hit_chance * (1.0 - pm->dead_prob_a());
double b_already_dead = pm->dead_prob_b();
pm->receive_blow_b(hit_chance);
pm->dump();

double first_hit = hit_chance * opp_hit_unknown;
opp_hit += first_hit;
opp_hit_unknown -= first_hit;
double this_hit_killed_b = (pm->dead_prob_b() - b_already_dead) / ((1.0 - b_already_dead) * (1.0 - pm->dead_prob_a()));
self_hit_unknown *= (1.0 - this_hit_killed_b);
}
if(i < opp_strikes) {
debug(("B strikes\n"));
self_not_hit *= 1.0 - opp_hit_chance * (1.0 - pm->dead_prob_b());
double a_already_dead = pm->dead_prob_a();
pm->receive_blow_a(opp_hit_chance);
pm->dump();

double first_hit = opp_hit_chance * self_hit_unknown;
self_hit += first_hit;
self_hit_unknown -= first_hit;
double this_hit_killed_a = (pm->dead_prob_a() - a_already_dead) / ((1.0 - a_already_dead) * (1.0 - pm->dead_prob_b()));
opp_hit_unknown *= (1.0 - this_hit_killed_a);
}
}

debug(("Combat ends:\n"));
pm->dump();
} while(--rounds && pm->dead_prob() < 0.99);

self_not_hit = original_self_not_hit * (1.0 - self_hit);
opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);

if(stats.slows) {
/* The calculation method for the "not hit" probability above is incorrect if either unit can slow.
* Because a slowed unit deals less damage, it is more likely for the slowing unit to be alive if it
* has hit the other unit. In that situation, the "not hit" probability can no longer be calculated
* with a simple multiplication.
* with simple addition.
* Instead, just fetch the probability from the combat matrix.
*/
opp_not_hit = original_opp_not_hit * pm->col_sum(plane_index(stats, opp_stats), opp_stats.hp);
Expand Down

0 comments on commit 1c3ad7f

Please sign in to comment.