January 9th, 2018, 02:29
(This post was last modified: January 9th, 2018, 02:40 by crusader.mike.)
Posts: 89
Threads: 4
Joined: Dec 2017
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:
Posts: 89
Threads: 4
Joined: Dec 2017
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 :-)
Posts: 89
Threads: 4
Joined: Dec 2017
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
January 9th, 2018, 03:03
(This post was last modified: January 9th, 2018, 03:40 by crusader.mike.)
Posts: 89
Threads: 4
Joined: Dec 2017
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.
January 9th, 2018, 17:02
(This post was last modified: January 9th, 2018, 17:03 by teelaurila.)
Posts: 386
Threads: 43
Joined: Dec 2017
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.
Posts: 10,492
Threads: 395
Joined: Aug 2015
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.
January 17th, 2018, 01:45
(This post was last modified: January 17th, 2018, 01:49 by crusader.mike.)
Posts: 89
Threads: 4
Joined: Dec 2017
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)?
January 17th, 2018, 01:49
(This post was last modified: January 17th, 2018, 04:40 by crusader.mike.)
Posts: 89
Threads: 4
Joined: Dec 2017
(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 smile](https://www.realmsbeyond.net/forums/images/smilies/smile2.gif) (I'll try to put together a calc)
January 17th, 2018, 04:05
Posts: 117
Threads: 4
Joined: Nov 2017
(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?
January 17th, 2018, 05:11
(This post was last modified: January 17th, 2018, 05:12 by teelaurila.)
Posts: 386
Threads: 43
Joined: Dec 2017
(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 (I'll try to put together a calc)
That's the hard part isn't it ![wink wink](https://www.realmsbeyond.net/forums/images/smilies/wink2.gif)
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.
|