prkill-1.11, now stops drain going over max hp,
...calculates probability function for the case where a unit was killed
This commit is contained in:
parent
03c1a36908
commit
0fe75098e2
1 changed files with 189 additions and 71 deletions
260
utils/prkill
260
utils/prkill
|
@ -9,8 +9,8 @@ prkill - calculate probability-to-kill in a Wesnoth skirmish
|
|||
attacker: 24hp, 5-4; defender: 30hp, 6-3
|
||||
prkill -p 0.4 -P 0.5 -n 4 -N 3 -d 5 -D 6 -h 24 -H 30
|
||||
|
||||
attacker: 24hp, 5-4; defender: 30hp, 6-3 (3 drain); with berserk
|
||||
prkill -p 0.4 -P 0.5 -n 4 -N 3 -d 5 -D 6 -h 24 -H 30 -x -R
|
||||
attacker: 24hp, 5-4; defender: 30hp (32 max), 6-3 (3 drain); with berserk
|
||||
prkill -p 0.4 -P 0.5 -n 4 -N 3 -d 5 -D 6 -h 24 -H 30 -M 32 -x -R
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
|
@ -25,7 +25,7 @@ and optionally prints the joint conditional probability function
|
|||
Pr[hp_A_final = x, hp_B_final = y | hp_A_init = h, hp_B_init = H]
|
||||
for supplied initial values h and H. In verbose mode, it also shows the
|
||||
probability function after each swing, not just after the last one.
|
||||
Usually the values Pr[hp_A_final < 0] and Pr[hp_B_final < 0] are of most
|
||||
Usually the values Pr[hp_A_final <= 0] and Pr[hp_B_final <= 0] are of most
|
||||
interest: these correspond to the probability of A or B being killed,
|
||||
respectively.
|
||||
|
||||
|
@ -42,15 +42,20 @@ attack and unit A does not, in which case unit B gets the first swing.
|
|||
interchanged.)
|
||||
|
||||
The output of prkill is the probability that A kills B, that B kills A,
|
||||
and that both survive the skirmish. In addition, if there is a
|
||||
reasonable probability that both survive, then the joint conditional
|
||||
probability function is printed. The function is displayed using a two
|
||||
dimensional grid, where the horizontal axis represents the hitpoints of
|
||||
unit A, the vertical axis the hitpoints of unit B, and the values of the
|
||||
probability function are displayed for each pair of hitpoints. Blanks
|
||||
are used for zero values, '*' represents a 100% value, and integer
|
||||
values are scaled by 10,000 (so 1864 represents 18.64%). In verbose
|
||||
mode, the probability function is printed at the end of each swing.
|
||||
and that both survive the skirmish. The expected hitpoints of A
|
||||
(conditional on B being killed) and of B (conditional on A being killed)
|
||||
are also printed.
|
||||
|
||||
In addition, the joint conditional probability function is printed. The
|
||||
function is displayed using a two dimensional grid, where the horizontal
|
||||
axis represents the hitpoints of unit A, the vertical axis the hitpoints
|
||||
of unit B, and the values of the probability function are displayed for
|
||||
each pair of hitpoints. Blanks are used for zero values, '*' represents
|
||||
a 100% value, and integer values are scaled by 10,000 (so 1864
|
||||
represents 18.64%). The values where either coordinate is 0 represent
|
||||
the cumulative probabilities that one unit dies and the other ends up on
|
||||
that number of hitpoints. In verbose mode, the probability function is
|
||||
printed at the end of each swing.
|
||||
|
||||
The output of prkill is somewhat more general than is provided by the
|
||||
game. In-game calculations were coded based on a prior version of
|
||||
|
@ -98,6 +103,16 @@ Hitpoints of attacker.
|
|||
|
||||
Hitpoints of defender.
|
||||
|
||||
=item B<-m>
|
||||
|
||||
Maximum hitpoints of attacker -- only relevant if attacker drains.
|
||||
Default: equal to the value of the -h option.
|
||||
|
||||
=item B<-M>
|
||||
|
||||
Maximum hitpoints of defender -- only relevant if defender drains.
|
||||
Default: equal to the value of the -H option.
|
||||
|
||||
=item B<-r>
|
||||
|
||||
Specify this if the attacker has drain. Default: no.
|
||||
|
@ -130,6 +145,28 @@ each swing are shown. Default: no.
|
|||
prkill currently does not deal with skirmishes where one of the units
|
||||
has the slow ability on its attack.
|
||||
|
||||
= head1 CAVEATS
|
||||
|
||||
Note that in the game, drain will drain half the hitpoints of the
|
||||
attack, rounded down. This is the case even if the unit being hit has
|
||||
fewer hitpoints: an 8 damage swing that drains will add 4 hitpoints to
|
||||
the health of the unit draining, even if the other unit only has 1
|
||||
hitpoint left at that stage.
|
||||
|
||||
Using -x -r -R together will result in a skirmish that cannot occur in
|
||||
the game, since the game currently has no way to specify both berserk
|
||||
and drain on a single attack.
|
||||
|
||||
The method of enumerating the state space scales badly. Time to run
|
||||
this script grows linearly with the number of swings in the skirmish:
|
||||
this is only an issue when berserk is involved, since this typically
|
||||
multiplies the number of potential swings by 30. However, runtime also
|
||||
grows linearly with the product of the initial hitpoints of A and B.
|
||||
Further, drain will slow convergence even further. Ideally, a
|
||||
reasonably accurate estimate for the berserk case should be constructed
|
||||
from the result of one round -- this is left as an exercise for the
|
||||
reader.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
ott <ott@gaon.net>
|
||||
|
@ -155,7 +192,7 @@ L<wesnoth>, F<http://www.wesnoth.org/>
|
|||
=cut
|
||||
|
||||
use strict;
|
||||
($main::VERSION) = ('$Revision: 1.10 $' =~ /[^\d\.]*([\d\.]*)/);
|
||||
($main::VERSION) = ('$Revision: 1.11 $' =~ /[^\d\.]*([\d\.]*)/);
|
||||
|
||||
use Getopt::Std;
|
||||
$Getopt::Std::STANDARD_HELP_VERSION = 1;
|
||||
|
@ -173,6 +210,8 @@ calculate probability of either unit killing the other in a Wesnoth skirmish
|
|||
-D damage per successful swing by defender
|
||||
-h hitpoints of attacker
|
||||
-H hitpoints of defender
|
||||
-m maximum hitpoints of attacker, if attacker drains [value of -h]
|
||||
-M maximum hitpoints of defender, if defender drains [value of -H]
|
||||
-r attacker has drain? [no]
|
||||
-R defender has drain? [no]
|
||||
-x berserk? (berserker attack, up to 30 rounds) [no]
|
||||
|
@ -183,11 +222,11 @@ EOI
|
|||
}
|
||||
|
||||
use vars qw(
|
||||
$opt_n $opt_d $opt_p $opt_h $opt_r
|
||||
$opt_N $opt_D $opt_P $opt_H $opt_R
|
||||
$opt_n $opt_d $opt_p $opt_h $opt_m $opt_r
|
||||
$opt_N $opt_D $opt_P $opt_H $opt_M $opt_R
|
||||
$opt_x $opt_y $opt_v
|
||||
);
|
||||
exit 1 unless (getopts('d:D:h:H:n:N:p:P:rRxyv'));
|
||||
exit 1 unless (getopts('d:D:h:H:m:M:n:N:p:P:rRxyv'));
|
||||
|
||||
my $VERBOSE = $opt_v;
|
||||
my $maxrounds = ($opt_x ? 30 : 1);
|
||||
|
@ -195,6 +234,8 @@ my $firststrike_active = $opt_y;
|
|||
|
||||
my ($hpa,$dmga,$pa,$swa) = ($opt_h,$opt_d,$opt_p,$opt_n);
|
||||
my ($hpb,$dmgb,$pb,$swb) = ($opt_H,$opt_D,$opt_P,$opt_N);
|
||||
my $hpma = $opt_m ? $opt_m : $opt_h;
|
||||
my $hpmb = $opt_M ? $opt_M : $opt_H;
|
||||
my $dra = $opt_r ? int ($dmga / 2) : 0;
|
||||
my $drb = $opt_R ? int ($dmgb / 2) : 0;
|
||||
|
||||
|
@ -218,57 +259,95 @@ my $th = 0.9999995;
|
|||
my @P;
|
||||
my $adf; # intersect y-axis first: A dies first
|
||||
my $bdf; # intersect x-axis first: B dies first
|
||||
my $hpt = $hpa + $hpb;
|
||||
my $kf = $hpb + $hpt;
|
||||
my $lf = $hpa + $hpt;
|
||||
my $hpt = $hpa + $hpb; # total initial hitpoints
|
||||
my $kf = $hpb + $hpt; # 2*hpb + hpa
|
||||
my $lf = $hpa + $hpt; # 2*hpa + hpb
|
||||
my $k = $hpb;
|
||||
$k += int($hpa/2) if $drb; # B drains so provide space
|
||||
$k += int($hpa/2) + $drb if $drb; # B drains so provide space
|
||||
$k = $hpmb if $k > $hpmb; # but limit it to max hp
|
||||
my $l = $hpa;
|
||||
$l += int($hpb/2) if $dra; # A drains so provide space
|
||||
$l += int($hpb/2) + $dra if $dra; # A drains so provide space
|
||||
$l = $hpma if $l > $hpma; # but limit it to max hp
|
||||
# size of matrix needed is max($k,$l)^2
|
||||
|
||||
# k +
|
||||
# drb{|
|
||||
# +
|
||||
# |\
|
||||
# | \
|
||||
# hpb +--+
|
||||
# | |\
|
||||
# | | \
|
||||
# +--+--+-----+
|
||||
# 0 hpa dra l
|
||||
# note that k=int(kf/2)+drb and l=int(lf/2)+dra when drain is involved
|
||||
# with the limitation that k can't exceed B's max hp (and l those of A)
|
||||
# kf and lf are used to calculate all the relevant lattice points, as
|
||||
# lines through (0,k)-(hpa,hpb) and (l,0)-(hpa,hpb) can omit some
|
||||
|
||||
sub print_state {
|
||||
my $s = shift;
|
||||
print "swing $s:\n";
|
||||
my $pbs = 0;
|
||||
if ($adf + $bdf < $th) {
|
||||
foreach my $jj ( 0..($k-1) ) {
|
||||
my $j = $k - $jj;
|
||||
my $t = $j;
|
||||
printf "%2d | ", $j;
|
||||
foreach my $i ( 1..$l ) {
|
||||
++$t; # invariant: $t == $i + $j
|
||||
last if ($t > $hpt) or ($j + $t > $kf) or ($i + $t > $lf);
|
||||
#printf "%.4f ", $P[$i][$j];
|
||||
my $pij = $P[$i][$j];
|
||||
if ($pij == 1) {
|
||||
print ' 'x3 . '* ';
|
||||
} elsif ($pij > 0.0) {
|
||||
printf "%4d ", int(10000*$P[$i][$j]);
|
||||
} else {
|
||||
print ' 'x5;
|
||||
}
|
||||
$pbs += $P[$i][$j];
|
||||
if ($s) {
|
||||
print "swing $s (last swing by ". (a_swings($s) ? 'A' : 'B') ."):\n";
|
||||
} else {
|
||||
print "initial state\n";
|
||||
}
|
||||
my $pbs = 0; # probability both survive
|
||||
my $aev = 0; # expected value of hp(A), given A survives
|
||||
my $bev = 0; # expected value of hp(B), given B survives
|
||||
|
||||
foreach my $jj ( 0..$k ) {
|
||||
my $j = $k - $jj;
|
||||
my $t = $j - 1;
|
||||
printf "%2d | ", $j;
|
||||
foreach my $i ( 0..$l ) {
|
||||
++$t; # invariant: $t == $i + $j
|
||||
last if ($t > $hpt)
|
||||
or ($i > 0 and $j + $t > $kf)
|
||||
or ($j > 0 and $i + $t > $lf);
|
||||
my $pij = $P[$i][$j];
|
||||
if ($pij == 1) {
|
||||
print ' 'x3 . '* ';
|
||||
} elsif ($pij > 0.0) {
|
||||
printf "%4d ", int(10000*$pij);
|
||||
} else {
|
||||
print ' 'x5;
|
||||
}
|
||||
print "\n";
|
||||
$pbs += $pij if ($i > 0 and $j > 0);
|
||||
}
|
||||
print " |";
|
||||
foreach my $i ( 1..$l ) { printf " %4d", $i }
|
||||
print "\n";
|
||||
}
|
||||
|
||||
print "A: ${hpa}hp, ${dmga}-${swa}";
|
||||
print " ($dra drain)" if $dra;
|
||||
print ", B: ${hpb}hp, ${dmgb}-${swb}";
|
||||
print " ($drb drain)" if $drb;
|
||||
print " firststrike" if $firststrike_active;
|
||||
print " berserk" if $opt_x;
|
||||
print " |";
|
||||
foreach my $i ( 0..$l ) { printf " %4d", $i }
|
||||
print "\n";
|
||||
|
||||
printf "Pr[A kills B] = %8.4f%%\n", $bdf*100;
|
||||
printf "Pr[B kills A] = %8.4f%%\n", $adf*100;
|
||||
if ($bdf) {
|
||||
foreach my $i ((1+$dra)..$l) { $aev += $i * $P[$i][0] }
|
||||
$aev /= $bdf;
|
||||
}
|
||||
if ($adf) {
|
||||
foreach my $j ((1+$drb)..$k) { $bev += $j * $P[0][$j] }
|
||||
$bev /= $adf;
|
||||
}
|
||||
|
||||
print "A: ${hpa}hp";
|
||||
print " (${hpma} max)" if $dra;
|
||||
print ", ${dmga}-${swa}, ". 100*$pa . '% to hit';
|
||||
print " ($dra drain)" if $dra;
|
||||
print "; B: ${hpb}hp";
|
||||
print " (${hpmb} max)" if $drb;
|
||||
print ", ${dmgb}-${swb}, ". 100*$pb . '% to hit';
|
||||
print " ($drb drain)" if $drb;
|
||||
print "; firststrike" if $firststrike_active;
|
||||
print "; berserk" if $opt_x;
|
||||
print "\n";
|
||||
|
||||
printf "Pr[A kills B] = %8.4f%%\n", $bdf*100;
|
||||
printf "Pr[B kills A] = %8.4f%%\n", $adf*100;
|
||||
printf "Pr[both survive] = %8.4f%%\n", $pbs*100;
|
||||
$DEBUG and print "total: ", $bdf + $adf + $pbs, "\n";
|
||||
print "total: ", $bdf + $adf + $pbs, "\n" if $bdf + $adf + $pbs < $th;
|
||||
printf "E[hpA | B dies] = %5.1f\n", $aev if $bdf;
|
||||
printf "E[hpB | A dies] = %5.1f\n", $bev if $adf;
|
||||
print "\n";
|
||||
}
|
||||
|
||||
|
@ -283,43 +362,82 @@ my $swt = $maxrounds * ($swa+$swb);
|
|||
foreach my $s ( 1..$swt ) {
|
||||
my $a = a_swings($s); # if true, it is A's turn, otherwise it is B's turn
|
||||
my $akz = 0; # kill zone of A, where B can be killed with one swing
|
||||
my @akz = ();
|
||||
my $bkz = 0; # kill zone of B, where A can be killed with one swing
|
||||
my @bkz = ();
|
||||
# values that are outside the valid zone
|
||||
my @a_large = ();
|
||||
my @b_large = ();
|
||||
my $ht = $hpt;
|
||||
my $kft = $kf;
|
||||
my $lft = $lf;
|
||||
# traverse all lines x+y=t, where t=1,2,...,hpt
|
||||
foreach my $t ( 1..$hpt ) {
|
||||
--$ht; # invariant: $ht == $hpt - $t
|
||||
--$kft; # invariant: $kft == $kf - $t;
|
||||
--$lft; # invariant: $kft == $lf - $t;
|
||||
my $ca = ($dmga - $dra <= $ht);
|
||||
my $cb = ($dmgb - $drb <= $ht);
|
||||
--$lft; # invariant: $lft == $lf - $t;
|
||||
my $j = $t;
|
||||
# traverse the specific line where x+y=t, with x=i and y=j
|
||||
foreach my $i ( 1..($t-1) ) {
|
||||
--$j;
|
||||
# invariant: $t == $i + $j
|
||||
# skip computing if this point is unreachable
|
||||
next if $j > $kft;
|
||||
next if $i > $lft;
|
||||
my $pij = $P[$i][$j];
|
||||
$akz += $pij if $j <= $dmga;
|
||||
$bkz += $pij if $i <= $dmgb;
|
||||
if ($a) {
|
||||
my $pij_mod = (1 - $pa) * $pij;
|
||||
if ($ca && $i > $dra) {
|
||||
$P[$i][$j] = $pij_mod + $pa * $P[$i - $dra][$j + $dmga];
|
||||
} else {
|
||||
$P[$i][$j] = $pij_mod;
|
||||
# check if this point is in 1-swing kill zone of A
|
||||
if ($j <= $dmga) {
|
||||
$akz += $pij;
|
||||
my $hpa_after = $i + $dra;
|
||||
$hpa_after = $hpma if $hpa_after > $hpma;
|
||||
$akz[$hpa_after] += $pij;
|
||||
}
|
||||
} else {
|
||||
my $pij_mod = (1 - $pb) * $pij;
|
||||
if ($cb && $j > $drb) {
|
||||
$P[$i][$j] = $pij_mod + $pb * $P[$i + $dmgb][$j - $drb];
|
||||
my $pij_prev_mod = 0;
|
||||
# P[u][v]=0 if u+v>hpt, while u<=0 or v>hpmb are invalid
|
||||
if (($dmga - $dra <= $ht) and ($j + $dmga <= $hpmb)
|
||||
and ($i > $dra)) {
|
||||
$pij_prev_mod = $pa * $P[$i - $dra][$j + $dmga];
|
||||
}
|
||||
if ($i > $hpma) {
|
||||
$a_large[$j] += $pij_prev_mod;
|
||||
} else {
|
||||
$P[$i][$j] = $pij_mod;
|
||||
my $pij_mod = (1 - $pa) * $P[$i][$j];
|
||||
$P[$i][$j] = $pij_mod + $pij_prev_mod;
|
||||
}
|
||||
} else { # it's B's swing
|
||||
# check if this point is in 1-swing kill zone of B
|
||||
if ($i <= $dmgb) {
|
||||
$bkz += $pij;
|
||||
my $hpb_after = $j + $drb;
|
||||
$hpb_after = $hpmb if $hpb_after > $hpmb;
|
||||
$bkz[$hpb_after] += $pij;
|
||||
# invariant: $bkz == sum $bkz[.]
|
||||
}
|
||||
my $pij_prev_mod = 0;
|
||||
# P[u][v]=0 if u+v>hpt, while v<=0 or u>hpma are invalid
|
||||
if (($dmgb - $drb <= $ht) and ($i + $dmgb <= $hpma)
|
||||
and ($j > $drb)) {
|
||||
$pij_prev_mod = $pb * $P[$i + $dmgb][$j - $drb];
|
||||
}
|
||||
if ($j > $hpmb) {
|
||||
$b_large[$i] += $pij_prev_mod;
|
||||
} else {
|
||||
my $pij_mod = (1 - $pb) * $P[$i][$j];
|
||||
$P[$i][$j] = $pij_mod + $pij_prev_mod;
|
||||
}
|
||||
}
|
||||
}
|
||||
} # end foreach $i
|
||||
} # end foreach $t
|
||||
if ($a) {
|
||||
$bdf += $akz * $pa;
|
||||
foreach my $i ((1+$dra)..$l) { $P[$i][0] += $akz[$i] * $pa }
|
||||
foreach my $j ((1+$drb)..$k) { $P[$hpma][$j] += $a_large[$j] }
|
||||
} else {
|
||||
$adf += $bkz * $pb;
|
||||
foreach my $j ((1+$drb)..$k) { $P[0][$j] += $bkz[$j] * $pb }
|
||||
foreach my $i ((1+$dra)..$l) { $P[$i][$hpmb] += $b_large[$i] }
|
||||
}
|
||||
if ($a) { $bdf += $akz * $pa } else { $adf += $bkz * $pb }
|
||||
print_state($s) if $VERBOSE;
|
||||
if ($adf + $bdf >= $th) { $swt = $s; last }
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue