2009-08-14, 11:40 AM (This post was last modified: 2009-08-14, 11:44 AM by Dusk.)
I'm no good with probability distributions
Basically I'm trying to figure out what damage range I need to
95% 2/3hko each Oblivion monster with Cannon
95% 4/5/6hko each Oblivion monster with Torpedo
95% 3hko Skeles with Torpedo
95% 1hko Skeles with Cannon (if possible o_o) and 2hko with Torpedo
1hko is quite easy to figure, and so is 100% xhko. But I don't know how to calculate points on the bell curve. 100% xhko is overdoing it because it always reports a number way beyond what you actually need.
2009-08-14, 12:45 PM (This post was last modified: 2009-08-14, 01:22 PM by Kortestanov.)
Dusk Wrote:I'm no good with probability distributions
Basically I'm trying to figure out what damage range I need to
95% 2/3hko each Oblivion monster with Cannon
95% 4/5/6hko each Oblivion monster with Torpedo
95% 3hko Skeles with Torpedo
95% 1hko Skeles with Cannon (if possible o_o) and 2hko with Torpedo
1hko is quite easy to figure, and so is 100% xhko. But I don't know how to calculate points on the bell curve. 100% xhko is overdoing it because it always reports a number way beyond what you actually need.
I'm not really good with the damage formulas, but basically to 1hko a monster with 100 hp at 95% you will need the actual range (after skill modifiers) to be 95-195.
Its like, max dmg - min dmg is your 100%. monster hp - min dmg is the change to 1hko a monster (out of the 100%).
so your chance to 1hko a monster is:
max dmg - monster hp
__________________
max dmg - min dmg
for xhko, i suppose you just divide the monster hp by the number of hits. like, to 95% 2hko the monster above (100 hp) you will need the damage range required to 95% 1hko a monster with 50 hp.
2009-08-14, 02:00 PM (This post was last modified: 2009-08-14, 02:08 PM by Stereo.)
Kortestanov Wrote:like, to 95% 2hko the monster above (100 hp) you will need the damage range required to 95% 1hko a monster with 50 hp.
The percentages won't match up though, if you combine multiple uniform distributions (eg. rolling dice) they end up coming closer to a normal distribution. Like if you roll 1d6*6 (ie. it has 6 12 18 24 30 36 on the faces) you only have a 95% chance of rolling at least 6 (1/6). 2d6*3 then you have a 95% chance of getting at least 9 (1/4). 3d6*2 you have 95% chance of getting 12 (1/3 of your max). These numbers are approaching the bottom 50% of the range. With 6d6 it's around 14.
The reason this happens is because if in order to fail, you need to be in the bottom 5% twice, or 6% and 4%, or 7% and 3%, etc. which is combined probability of 0.05*0.05 + 0.06*0.04 etc., which is smaller than 5%. So the actual % of the range increases. (like 10%+10%, 9%+11%, etc.) until the total probability is only 5% again.
The more hits it takes, the closer the 95% probability gets to the bottom 1/2 of your damage range. If (like with TT) it takes 12 independent hits to 4hko, then the 95% chance of doing 4hko or better only requires the average per hit to be around 1/12 of the monster's hp.
So for classes with multiple hits, you could generally assume around 40-45% of your range to have a 95% chance to KO at that rate. It's more difficult for classes that actually only hit 2-4 times, especially if those hits can crit or have other damage mods applied individually.
Dusk Wrote:I'm no good with probability distributions
Basically I'm trying to figure out what damage range I need to
95% 2/3hko each Oblivion monster with Cannon
95% 4/5/6hko each Oblivion monster with Torpedo
95% 3hko Skeles with Torpedo
95% 1hko Skeles with Cannon (if possible o_o) and 2hko with Torpedo
1hko is quite easy to figure, and so is 100% xhko. But I don't know how to calculate points on the bell curve. 100% xhko is overdoing it because it always reports a number way beyond what you actually need.
I second this question. I didn't pass my Statistics class, so this is a mystery to me. To make matters worse, crits completely throw the whole thing for a loop. Is anyone here a Math PHD?
use calculus! find the normal distribution / gaussian curve that goes with what you wanna know and integrate!
entry level stats books for engineers make it even easier, they have tables that tell you the info because those fuckers are too lazy to remember how to derive what they want.
i dont care enough to look in my crappy book atm because i hate the way engineering works
The integral for the bell curve is not analitical. You have to calculate it numerically.
And this types of probability use several uniform distributions on top of each other (for normal range) and a composition of two piles of uniform distributions stacked for crits.
Multihitting stuff makes it even worse.
Idk how Russt managed to do that calculator, I remember he ran into problems with it generating big numbers and such =P
2009-08-15, 06:23 AM (This post was last modified: 2009-08-15, 07:04 AM by singularity.)
Some time last year I posted how I managed to code it in VBA for Excel (for NL's L7/TT/Avenger).
singularity Wrote:Looks like the formatting of the script wasn't preserved when I posted. Anyway, I'll go ahead and break down the algorithm in my script. Oh, I'll say right now that it would be more accurate if I had used "attacks" instead of "hits" in my code.
My commentary is enclosed in the spoiler below because it is quite long.
Spoiler
I'm just going to explicate the first half of the excerpt. The second half just basically uses the same stuff except with different variable names and has a couple lines to account for SE.
Code:
' Skips TT calculation if TT value is blank or empty or if Excel would have reached an error calculating factorials
' (Excel errors when trying to calculate 85 factorial or higher.)
Dim CritDam3 As Double
Dim CritChance3 As Double
Dim MinHits3 As Double
Dim MaxHits3 As Double
CritDam3 = CritDam
CritChance3 = CritChance
singlehit = 3 * (Int(TTMax * (TT + CritDam3)) + Int(ShadowDam * Int(TTMax * (TT + CritDam3))))
If singlehit < 6 Then
If Shadow = 1 Then
singlehit = 6
End If
End If
If singlehit < 3 Then
singlehit = 3
End If
MinHits3 = -1 * Int((-1) * (MonHP / singlehit))
If Shadow = 1 Then
MaxHits3 = ActiveSheet.Cells(67, 10)
Else
MaxHits3 = ActiveSheet.Cells(67, 5)
End If
This section is to calculate the values for the minimum and maximum number of attacks required to KO. The variable singlehit would be more appropriately named if it read "singleattack." Specifically, I store the maximum possible damage from a single attack in the variable. It is determined by the line: "singlehit = 3 * (Int(TTMax * (TT + CritDam3)) + Int(ShadowDam * Int(TTMax * (TT + CritDam3))))" -- and I account for exceptions in the two if-statements below it.
The Int() function in VBA takes the Floor of the value enclosed in the parentheses. TTMax is a value that can be taken off banditcom's spreadsheet. Essentially, this is the maximum value on the range after having taken WDEF (or MDEF) into account. TT+CritDam3 is just the % multiplier (e.g., 2.5 for maxed triple throw, 2.4 for Level 20 triple throw, etc.).
Anyway, we want to know the maximum number of attacks required to kill so that the script can check when to stop. The minimum number of attacks required to kill is also useful because it can be used to speed up the calculation. In particular, we know that any number of attacks less than the minimum required to kill has a 0% probability to kill, or alternatively, a 100% probability to not kill.
Code:
Dim TTArray As Double
Dim TTSEArray As Double
If IsEmpty(ActiveSheet.Range("H22").Value) = True Or TT = 0 Then
TTArray = 0
TTSEArray = 0
End If
If MaxHits3 > 85 Then
TTArray = 0
TTSEArray = 0
Answer = MsgBox("Cannot compute TT Hit Distribution. Max Hits of TT must be 85 or less. Click OK to continue.", vbOKCancel, "TT Calculation Error")
If Answer = vbCancel Then
Exit Sub ' the macro ends if the user selects the CANCEL-button
End If
Else
Checks whether TT skill level is greater than 0. If it is equal to 0, it sets some variables to 0 so that code later on in the script can be skipped.
The test "If MaxHits3 > 85" is very important. Later, we use the binomial coefficient to determine the probabilities so the factorial of the number stored in this variable is needed. Apparently, no errors occur when the value here is 84 or lower (I determined this through trial and error). I think Excel can actually handle up to 170!, but there's a multiplication in the denominator for the binomial coefficient.
Anyway, if the test fails, it prompts the user to say that the calculation will be skipped. The user can then choose "Cancel" to exit the script and fix the problem, or the user can choose "OK" and let the script calculate anything it can (e.g., the script can still try to calculate for Lucky Seven or Avenger).
Code:
' Declaration of the Array that holds the TT NHit Percentages
TTArray = 1
Dim NHitArray3() As Double
Dim A3 As Double
Dim B3 As Double
sum = 0
Pc = 0
ReDim NHitArray3(1 To MaxHits3)
For z = 1 To Int(MinHits3 - 1)
NHitArray3(z) = 1
Next z
Okay, done with preliminary checks to make sure the calculation can actually be done! Now, for the actual meat of the calculation.
For now, ignore the line "sum = 0." In my earlier post, I explained that the variable "Pc" is the probability that a monster is still alive after so-and-so many attacks. Here, I am just setting their initial values before we perform any calculations.
"ReDim NHitArray3(1 To MaxHits3)" sets the length of the array in which I am going to save all the Pc values to. Then the following For-loop sets the entries in the array corresponding to a number of attacks less than the required to kill to have a value of 1. Again, I use the value '1' because there is 100% probability to not kill for these numbers of attacks.
Code:
For h = (MinHits3) To (MaxHits3)
For i = 0 To (3 * h)
' Next two if-statements force Pc to range from 0 to (numer/denom)
A3 = i * (Int(TTMin * (TT + CritDam3)) + Int(ShadowDam * Int(TTMin * (TT + CritDam3)))) + (3 * h - i) * (Int(TTMin * TT) + Int(ShadowDam * Int(TTMin * TT)))
If A3 < 6 * h Then
If Shadow = 1 Then
A3 = 6 * h
End If
End If
If A3 < 3 * h Then
A3 = 3 * h
End If
If A3 < MonHP Then
A3 = A3
Else
A3 = MonHP
End If
B3 = i * (Int(TTMax * (TT + CritDam3)) + Int(ShadowDam * Int(TTMax * (TT + CritDam3)))) + (3 * h - i) * (Int(TTMax * TT) + Int(ShadowDam * Int(TTMax * TT)))
If B3 < 6 * h Then
If Shadow = 1 Then
B3 = 6 * h
End If
End If
If B3 < 3 * h Then
B3 = 3 * h
End If
If B3 > MonHP Then
B3 = B3
Else
B3 = MonHP
End If
numer = fact(3 * h)
denom = (fact(i)) * (fact((3 * h) - i))
If (B3 - A3) = 0 Then
Pc = Pc
Else
Pc = Pc + (numer / denom) * (CritChance3 ^ i) * ((1 - CritChance3) ^ (3 * h - i)) * ((MonHP - A3) / (B3 - A3))
End If
Next i
NHitArray3(h) = Pc
Pc = 0
Next h
The first for-loop basically tells the script for which numbers of attacks that we want to calculate Pc (probability to not kill). So the variable 'h' in each iteration of the loop is the number of attacks. For TT, there is either 0-3 criticals per attack. Thus, we nest a for-loop, and 'i' is the number of criticals for each given 'h', and the value of 'i' can vary from 0 to 3*h.
A3 is the lowest possible damage for a given 'h' and a given 'i', and B3 is the highest possible damage. Notice that A3 and B3 basically calculates the damage for TT given that we know the base range of TT (e.g., TTMin and TTMax) and its multipliers 'TT+CritDam3' when we crit and just 'TT' when we do not crit. 'i' is the number of criticals and '(3 * h - i)' is the number of non-criticals.
Following that are a couple if-statements for both A3 and B3 to account for situations where we would actually hit for just 1 damage on every hit of the attack.
The variable MonHP should be obvious (it holds the value for monster's HP). For now, ignore the if-statements that do tests comparing A3 or B3 vs. MonHP and leave the values alone or change them to equal to MonHP. I'll address these later.
'numer' is the numerical part of the binomial coefficient, and 'denom' is the denominator. Skip down to 'Pc = Pc + (numer / denom)...' This part of the line after the addition operation: '(numer / denom) * (CritChance3 ^ i) * ((1 - CritChance3) ^ (3 * h - i))' is just the binomial distribution in statistics. '(numer/denom)' is the binomial coefficient. The rest is basically a straight substitution given that:
1) 'CritChance3' is your value for p.
2) 'i' is your value for k.
3) '3*h' is your value for n.
Then the final part: '((MonHP - A3) / (B3 - A3))' is just 'probability to not kill'. Note that without the two if-statements comparing A3 or B3 vs. MonHP, then this would be inaccurate and skew your results. So that whole line after the addition operator basically says to find probability to not kill and give it the appropriate weighting for given values of 'h' and 'i'.
Since the value of 'i' can vary for a given 'h', you can see why the line reads: 'Pc = Pc + (numer / denom)...' instead of simply: 'Pc = (numer / denom)...' They have to be added together to get the correct value of 'Pc' for each given 'h'.
In the if-statement testing whether B3-A3 is zero:
For the true case, I think it might be correct if it instead was: 'Pc = Pc + (numer / denom)'. Truthfully, it's been so long since I originally wrote this script that I've forgotten why I have 'Pc = Pc' in that line.
Okay, that brings us outside of the nested for-loop. Now the final two lines of the outer for-loop just stores the value of Pc to the array and resets the value of Pc to zero so it is ready to be used in the next iteration.
Code:
' Changes each element of the array to a percentage. Then recalculates each element
' so that each element contains the percentage of KO's in exactly N-hits. Because we
' previously had KO's in at least N-hits stored in each element of the array.
For k = 1 To Int(MaxHits3)
NHitArray3(k) = 100 * NHitArray3(k)
NHitArray3(k) = 100 - NHitArray3(k) - sum
sum = sum + NHitArray3(k)
Next k
The first line in this for-loop changes all the values to percentages. The other two lines change the values from 'probability to not kill' to 'probability to kill in exactly N-attacks'. (Again, it would be more appropriate if I had 'N-attacks' in the commented part of the code instead of 'N-hits'.)
I think the code I originally posted would be erroneous for any time damage would be 1~(something). For example, some lvl 7x training on Squids. Other than that, should be accurate.
Also, about Russt's calculator, I'm pretty sure there is an error in it.
Spoiler
And because of that we get:
Spoiler
When it should be equal to the second set of percentages below:
Spoiler
Of note is that I had a similar error to Russt's initially. And I had results similar to that second pic of non-conjured up numbers (predicted much higher 1hko than should actually occur). Then I fixed the error.
I'm going to concede that my program doesn't calculate it correctly at the moment. Has a problem with not counting all the different ways a certain damage amount can occur (e.g., 42504 can occur 6 different ways). For the case above, I think it should give correct results due to a 50/50 scenario being symmetric... but being correct because of that is hardly satisfactory.
--------------------------
Also, since I must (at some time) rewrite my program, I have a question.
Question: How exactly is damage calculated? More precisely, at what point in damage calculation does the game (randomly) choose a number?
Spoiler
So I suppose there are a few possibilities.
a) The game uses your stats to calculate your range; then randomly chooses numbers from your range, then applies WDEF modification, critical/damage multipliers, rounds down (calculates damage). b) Or does the game calculate the your min/max damage then randomly choose numbers from this range? (So this possibility is: calculate damage then randomly choose.)
c) For Assassin/Hermit/Night Lord, there is a separate range formula for Lucky 7/Triple Throw from that of Normal Attack, Drain, Avenger, etc. So there's a question here similar to a) and b) above.[INDENT]Does the game calculate this range for Lucky 7/Triple Throw and randomly choose a number from that?
Or does the game calculate the min/max damage possibility and choose randomly from this range?[/INDENT][INDENT] Or there can be a 3rd possibility for this case:
L7/TT Min: 2.5*LUK/100*ATT
L7/TT Max: 5.0*LUK/100*ATT
Does the game choose randomly between 2.5 and 5.0? And then do all the other calculations? (This 3rd possibility seems unlikely though...?)[/INDENT]
-------------------
So the reason it matters is because:
If the game calculates damage by a), then there are gaps in damage. Your min/max damage will be possibilities but not so for all the numbers in between. For example, damage possibilities might be like: 14167, 14169, 14170, 14172, etc.
And of course, this affects how I should write the calculation section.
2009-08-18, 12:29 AM (This post was last modified: 2009-08-18, 12:32 AM by Stereo.)
singularity Wrote:Of note is that I had a similar error to Russt's initially. And I had results similar to that second pic of non-conjured up numbers (predicted much higher 1hko than should actually occur). Then I fixed the error.
It would be tricky but you could reproduce those results ingame on a crit class, probably. You'd need a gap between max non-crit and min crit damage though, which limits the attacks it applies to.
But for example, say the min damage of a bowman is around 81% of the max (due to 90% mastery). It'll actually be a bit higher due to STR.
Then their attack does 81-100% with non-crit Strafe (per arrow), 275.4-340% with crit arrow.
Total it up and you get 1101.6% minimum damage with 4x crit, and 1120% maximum damage with 3x crit, 1x non-crit. Just another 18% and it would have been impossible for the character to do damage between 1120% and 1121% (or whatever), meaning a jump from 1 to 3hkos. Not too likely to happen in any real situations though.
@ Singularity there are 2 random numbers, for defense and for attack. I think they're just applied from the possible ranges of defense (0.5 ~ 0.6) and mastery (%*0.9 ~ 1), and rounded after the skill percents are all applied. I haven't noticed hitting the same numbers and holes over and over on my paladin, even though it has high percent single skills (which are the most likely to generate gaps)
2009-08-18, 12:57 AM (This post was last modified: 2009-08-18, 12:59 AM by Dusk.)
I kind of just want an approximation. I don't think it would hurt to use a random number from your range.
Also, who says it's an exact number? It's probably just a double between 0 and 1 multiplied by your (max-min) and added to your min, then multiplied by the modifiers.
Russt Wrote:I thought that your damage range was calculated first, defense subtracted and all, and then the random was chosen.
One could, of course, do a study of some sort on damage distributions, but it'd be tedious and/or not reveal much.
pretty sure that since we dont do damage in increments of 2-11 (depending on skills used) instead of 1 that everything gets applied before taking the random.
with a damage range of 160~300 powerstrike can dish out both 599 and 600 on [certain monsters] no problem. i dont think you can find a clearer revelation.
2009-08-18, 01:03 AM (This post was last modified: 2009-08-18, 01:12 AM by Russt.)
^ Or the modifiers are applied to the max and min. Or it's a double, as Dusk said.
My method actually assumes that damage distribution is continuous. Doesn't make a difference when the numbers are sufficiently high enough (more than a couple hundred). I think I adjusted for it by subtracting 0.5 or something like that, but the truth is it doesn't matter very much.
Which is why I'm trying to figure out how to calculate probabilities in a multinomial distribution. It shouldn't matter how the numbers are derived, you just need a range and a number of variables.
Okay I went to Wikipedia's page on Dice and found an algorithm to calculate the odds of getting a certain sum with i s-sided dice. Wrote a Perl script to test it.
95% means you'll be doing that at least that number 95% of the time. So if it says 95% = 100, you can 95% 1hko anything with 100 HP.
Only problem is that it's massively recursive so that it takes forever to calculate anything with more than 3 dice, and that's on 2-digit ranges. Does not work at all for large ranges.
Here's the code:
Code:
#!usr/bin/perl
use strict;
use warnings;
die "Usage: $0 min max hits %dmg\n" if (@ARGV != 4);
#main
my $sides = $ARGV[1] - $ARGV[0] + 1;
my $dice = $ARGV[2];
my $offset_add = ($ARGV[0] - 1);
my $offset_mult = $ARGV[3];
my $poss = &power($sides, $dice);
my $sum = 0;
my %distr;
my @checkpoints = qw(100 095 075 050 025 005 001);
my $counter = 0;
for (my $i = $dice; $i <= $sides*$dice; $i++)
{
if (100*$sum/$poss >= 100 - $checkpoints[$counter])
{
$distr{$checkpoints[$counter]} = $i;
$counter++; #find next distribution checkpoint
last if ($counter >= @checkpoints);
}
$sum += &recf($sides, $dice, $i);
}
sub recf #(sds, ithd, num)
#Recursive function to find possible ways to roll a particular number. algorithm is
#F[sds,ithd](num) = sum(F[sds,1](n)*F[sds,i-1](num-n), n) for all integer n
#base case is f[sds,1](num) = 1 if 1 <= num <= sds, 0 else
{
my ($sds, $ithd, $num) = @_;
return ($num >= 1 && $num <= $sds)? 1 : 0 if ($ithd == 1);