Are you, in fact, a pregnant lady who lives in the apartment next door to Superdeath's parents? - Commodore

Create an account  

 
Combat math

I am going to put here some calcs related to MoM combat system, which is rather unique -- it requires non-trivial calculations to figure out average damage, etc. The only online resource I found that allows me to run these calcs is Wolfram Programming Lab.

Anyway, here is a calculation for average damage. Start new notebook ("File/New Notebook"; you should be able to do it without registration), copy/paste code, press Shift-Enter to run. Change params to figure out expected outcome of your specific scenario:

Code:
(* Probability of rolling precisely k successes in N attempts, p is probability of success *)
P[N_, k_, p_] := Binomial[N,k]*p^k*(1-p)^(N-k)

(* Avg damage done to defense D (with to-def pd) with attack A (with to-hit pa) *)
AvgDam[A_, pa_,D_, pd_] := Sum[P[A,a,pa]*Sum[(a-d)*P[D,d,pd],{d,0,a-1}],{a,1,A}]

(* Ultra Elite berserker without buffs *)
Melee := 10
Thrown := 6
ToHitBonus := +2
EnemyToDefBonus := +0

ToHit := (3 + ToHitBonus)/10
ToDef := (3 + EnemyToDefBonus)/10

(* Average damage 6-figure berserker does vs D defense *)
BerserkAvgDam[D_] := 6*(AvgDam[Melee, ToHit, D, ToDef] + AvgDam[Thrown, ToHit, D, ToDef])

(* Average damage done depending on defense *)
DiscretePlot[BerserkAvgDam[d],{d,0,40}]

(* in grid form, rounded to nearest 0.01 *)
Grid[ {Range[0,15],Table[Round[BerserkAvgDam[d],0.01], {d,0,15}]}, Frame -> All]

it will produce graph of expected damage ultra elite berserker unit (without buffs) does to a target depending on it's defense rating (assuming no to-def bonuses and assuming throw attack uses same to-hit as melee) in "graph" and "table" format:
   
Reply

Here is another one -- distribution of damage (probability of making 0 dam, 1 dam, 2 dam, etc depending on attacker and defenders params). It produces quite unexpected results, tbh...

Code:
(* Probability of rolling precisely k successes in N attempts, p is probability of success *)
P[N_, k_, p_] := Binomial[N,k]*p^k*(1-p)^(N-k)

(* Probability of making damage 'dam' to defense D (with to-def pd) using attack A (with to-hit pa); correct only for dam > 0 *)
DamProbability[dam_, A_, pa_,D_, pd_] := Sum[P[A,a,pa]*P[D,a - dam,pd],{a,dam,A}]

(* Probability of making zero damage to defense D (with to-def pd) using attack A (with to-hit pa) *)
DamZeroProbability[A_, pa_,D_, pd_] := 1 - Sum[DamProbability[dam, A, pa, D, pd], {dam, 1, A}]

(* Probability of making damage 'dam' to defense D (with to-def pd) using attack A (with to-hit pa); correct for any dam *)
DamChance[dam_, A_, pa_, D_, pd_] := If[dam > 0 && dam <= A, DamProbability[dam, A, pa, D, pd], If[dam==0, DamZeroProbability[A,pa,D,pd], 0]]

(* Let's look how probability of doing damage X looks like depending on A and D *)
DiscretePlot[{DamChance[d, 10, 0.3, 5, 0.3], DamChance[d, 15, 0.3, 10, 0.3]}, {d, 0, 10}, ExtentSize->0.8, PlotLegends->{"10 vs 5", "15 vs 10"}, AxesLabel->{"Damage", "Probability"}]
DiscretePlot[{DamChance[d, 5, 0.3, 10, 0.3], DamChance[d, 10, 0.3, 15, 0.3]}, {d, 0, 4}, ExtentSize->0.8, PlotLegends->{"5 vs 10", "10 vs 15"}, AxesLabel->{"Damage", "Probability"}]

It's result:
   

Note how 10 attack vs 5 defense has higher chance to make 1-2 damage than 15 vs 10. Plug in some to-hit bonuses and distribution changes quite dramatically!

Disclaimer: these calcs will be "correct" only if MoM uses decent quality random number generator (which it likely doesn't) -- where each dice roll is statistically independent from others. But we'll pretend that it does :-)
Reply

This is what +2 to-hit does to performance of single figure Elite Berserker when it attacks 10 def, +0 to-def adversary (melee attack only):

Code:
AvgDam[10, 0.3, 10, 0.3]
AvgDam[10, 0.5, 10, 0.3]

DiscretePlot[{DamChance[d, 10, 0.3, 10, 0.3], DamChance[d, 10, 0.5, 10, 0.3]}, {d, 0, 10}, ExtentSize->0.8, PlotLegends->{"10 vs 10", "10 vs 10, +2 to-hit"}, AxesLabel->{"Damage", "Probability"}]

   

As you could see change is quite dramatic smile
Reply

One more note -- that bar chart is effectively a probability mass function (PMF) of discrete random variable. Not a distribution function (that has different definition). But it is trivial to build distribution function from PMF -- at each point X height of the bar is a sum of all bars to the left of point X on PMF graph. Each bar will represent probabilty of making "less than X" damage. I.e. each subsequent bar is bigger than previous ones and at max value bar height will be 1.
Reply

I would be surprised if the RNG did not produce correct averages in the long run. Funny correlations are probably there (such as sampling only "stripes" of true probability space), but these one would never notice, and are frankly irrelevant. It is very unlikely that such correlation would somehow match a hit-defend roll cycle length so that it would consistently give wrong results.

But hey, nice graphs, well done! Gives some nice numbers that seem to match gut feeling.

One interesting calculation would be the amount of armor and +to-defend one would need to be practically impervious to attacks of different strengths and +to-hits.
Reply

Well, I don't know much about random number generation but this is what the game uses :


Code:
mov     [bp+var_4], 0
push    si
push    di
mov     si, RNGSeed1
mov     di, RNGSeed2
mov     cx, 9
loc_1D5D2:
mov     ax, si
mov     bx, si
mov     dx, di
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
xor     al, dh
mov     dx, ax
shr     dx, 1
rcl     [bp+var_4], 1
shr     ax, 1
rcr     di, 1
rcr     si, 1
loop    loc_1D5D2
cmp     si, 0
jnz     short loc_1D618
cmp     di, 0
jnz     short loc_1D618
mov     si, 30BEh
loc_1D618:
mov     RNGSeed1, si
mov     RNGSeed2, di
pop     di
pop     si
mov     ax, [bp+var_4]
cwd
idiv    [bp+arg_0]
inc     dx
mov     [bp+var_2], dx
mov     ax, [bp+var_2]
The output is in AX. The range is in Arg0.
Reply

Found a much easier way to build these calcs, but unfortunately Wolfram Lab backend has bugs in that area. :-(

Seravy, I am too lazy to pull apart this RNG in attempt to analyze it's statistical properties. Typically even if given RNG is ok to model one random variable, it doesn't work very well to model two variates by taking each 2nd invocation. All sorts of statistical anomalies creep up -- like complete inability to roll 0 and 0.

I have a question -- how often defense roll happens? Does it happen for every figure? Does it happen for every attack (i.e. one roll for 'thrown attack' phase, one for 'melee', etc)?
Reply

(January 9th, 2018, 17:02)teelaurila Wrote: One interesting calculation would be the amount of armor and +to-defend one would need to be practically impervious to attacks of different strengths and +to-hits.

define being "practically impervious", pls smile (I'll try to put together a calc)
Reply

(January 9th, 2018, 17:09)Seravy Wrote: Well, I don't know much about random number generation but this is what the game uses :


Code:
mov     [bp+var_4], 0
push    si
push    di
mov     si, RNGSeed1
mov     di, RNGSeed2
mov     cx, 9
loc_1D5D2:
mov     ax, si
mov     bx, si
mov     dx, di
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
rcr     bx, 1
shr     dx, 1
rcr     bx, 1
xor     ax, bx
shr     dx, 1
xor     al, dh
mov     dx, ax
shr     dx, 1
rcl     [bp+var_4], 1
shr     ax, 1
rcr     di, 1
rcr     si, 1
loop    loc_1D5D2
cmp     si, 0
jnz     short loc_1D618
cmp     di, 0
jnz     short loc_1D618
mov     si, 30BEh
loc_1D618:
mov     RNGSeed1, si
mov     RNGSeed2, di
pop     di
pop     si
mov     ax, [bp+var_4]
cwd
idiv    [bp+arg_0]
inc     dx
mov     [bp+var_2], dx
mov     ax, [bp+var_2]
The output is in AX. The range is in Arg0.

Interesting, would you know what's put in the other variables?
Reply

(January 17th, 2018, 01:49)crusader.mike Wrote:
(January 9th, 2018, 17:02)teelaurila Wrote: One interesting calculation would be the amount of armor and +to-defend one would need to be practically impervious to attacks of different strengths and +to-hits.

define being "practically impervious", pls smile (I'll try to put together a calc)

That's the hard part isn't it wink
Your guess is as good as mine, I'm sure. But here goes:

If a single figure has 95% of delivering zero damage in an attack, then a 4 figure unit has 81% chance and a 6 figure unit 74% chance of not doing any damage.

If a single figure has 90% chance of delivering zero damage, then a 6 figure unit will deal damage with about every other attack. That would mostly be 1 point of damage. Assuming a single "impervious" unit needs to hit an enemy unit 4 times to kill it, it still takes ~20 points of damage against a stack of 9 (36 attacks, but most against reduced strength enemies). And at least with turtles it was more than 4 attacks to kill. Stag beetles could be another story. So 90% seems a bit low.

So maybe 95% would be decent for the borderline of "impervious" for a unit that relies purely on armor and expects to be humped from all sides for a dozen turns and still come out on top.
Reply



Forum Jump: