The Lurker Lounge Forums
Any Probability/Statistic majors among us? - Printable Version

+- The Lurker Lounge Forums (https://www.lurkerlounge.com/forums)
+-- Forum: The Lurker Lounge (https://www.lurkerlounge.com/forums/forum-4.html)
+--- Forum: The Lounge (https://www.lurkerlounge.com/forums/forum-12.html)
+--- Thread: Any Probability/Statistic majors among us? (/thread-386.html)



Any Probability/Statistic majors among us? - the Langolier - 10-14-2009

I am working on calculating the probabilities for monsters to spawn in original diablo based on the monster size information in Jarulf's guide. Essentially, monster generation works like spending money within a budget. The total "money" (size) available for monsters per level is 3614; a monter is randomly picked to spawn, and it's "price" (size) is subtracted from the total. The process is repeated until there isn't enough "money" left for any enemy. Taking dlvl 15 as an example, we have:

Code:
Balrog         2200
Azure Drake    1270
Snow Witch      980
Hell Spawn      980
Soul Burner     980
Doom Guard     2120
Steel Lord     2120
Magistrate     2000
Cabalist       2000

For example, suppose Doom Guard is randomly picked. 3600 - 2120 = 1480, therefore Azure Drake, Snow Witch, Hell Spawn, and Soul Burner can still spawn. Once any of these monsters are picked, no more monsters may spawn (not enough size available). In this case, a Soul Burner has a 1/9 * 1/4 = 1/36 chance of being picked.

Alternatively, suppose Snow Witch is randomly chosen. 3600 - 980 = 2620, therefore all monsters except for Snow Witch can still spawn. If Azure Drake is randomly chosen next, 2620 - 1270 = 1350, therefore either Hell Spawn or Soul Burner can still spawn. In this case, however, the Soul Burner now only has as a 1/9 * 1/8 * 1/2 = 1/144 chance of being picked.

How can you determine the overall probability of a particular monster being picked to spawn?


Any Probability/Statistic majors among us? - Vandiablo - 10-14-2009

Quote:How can you determine the overall probability of a particular monster being picked to spawn?
Well, maybe there's some handy dandy equation or a simpler way, but if there isn't I'll point out that you would be better off calculating the prob that a monster won't spawn and then subtract it from one.

For instance, the prob for Balrog would be

P(Bal) = 1 - P'(1,Bal) P'(2,Bal) P'(3,Bal)

where P' = 1 - P, meaning prob of not getting picked, and
P'(k, Mon) is the prob of a monster Mon not getting picked in round k

For Balrog, P(3,Bal) is zero (impossible for Balrog to be picked third), so P'(3,Bal) = 1.

So
P(Bal) = 1 - P'(1,Bal) P'(2,Bal) (1) = 1 - P'(1,Bal) P'(2,Bal)

As you already know, we also have P(1,Mon) is equal to 1 divided by the count of monsters, in this case 9. So P(1,Bal) = 1/9 and thus
P'(1,Bal) = 8/9

So
P(Bal) = 1 - (8/9) P'(2,Bal)

oops gotta go


Any Probability/Statistic majors among us? - Nystul - 10-14-2009

Here is what I call "The Amazing Brute Force Solution":

rog/az
rog/snow
rog/spawn
rog/soul
az/snow/spawn
az/snow/soul
az/spawn/soul
az/doom
az/steel
az/magi
az/cabal
snow/spawn/soul
snow/doom
snow/steel
snow/magi
snow/cabal
spawn/doom
spawn/steel
spawn/magi
spawn/cabal
soul/doom
soul/steel
soul/magi
soul/cabal

total possible combos for level 15 = 24
odds for rog = 4/24 = 1/6
odds for az = 8/24 = 1/3
same odds for the knight or lawyer types as for rog
same odds for the witch types as for az
Resulting percentages match Jarulf's Guide.

Interestingly, this method could be derived empirically through game experience without any knowledge of "monster size" data.

Perhaps a real stat wiz can provide a more elegant solution.


Any Probability/Statistic majors among us? - the Langolier - 10-14-2009

Unfortunately, while that works for dlvl 15, it isn't a consistent method as it doesn't work for any other dlvl. I have a program that generates every possible combination for each level. Dlvl 15, for instance, has 24 unique combinations but there are 64 ways to generate the level (including duplicate combinations picked in a different order). Dlvl 5 has over 3.5 million ways to generate the level with some 60,000 unique combinations. Anyways, neither the ratio of the number of ways to pick a monster to the number of combinations for unique combinations nor total combinations result in the probabilities - dlvl 15 is just a lucky case.


Any Probability/Statistic majors among us? - Nystul - 10-15-2009

Quote:Unfortunately, while that works for dlvl 15, it isn't a consistent method as it doesn't work for any other dlvl. I have a program that generates every possible combination for each level. Dlvl 15, for instance, has 24 unique combinations but there are 64 ways to generate the level (including duplicate combinations picked in a different order). Dlvl 5 has over 3.5 million ways to generate the level with some 60,000 unique combinations. Anyways, neither the ratio of the number of ways to pick a monster to the number of combinations for unique combinations nor total combinations result in the probabilities - dlvl 15 is just a lucky case.

Doh, I see what I did there. :wacko: Well I think you already know what has to be done.






Copy off Jarulf, of course.


Any Probability/Statistic majors among us? - DeeBye - 10-15-2009

You're overthinking it. 9 potential monsters can spawn, so the probability is 1/9.

:shuriken:


Any Probability/Statistic majors among us? - Crusader - 10-15-2009

On the first spawn, yes. And on the second spawn with the leftover points?


Any Probability/Statistic majors among us? - Neyda - 10-16-2009

Quote:How can you determine the overall probability of a particular monster being picked to spawn?

Well, you could still brute force all levels by a computer. I guess that's what Jarulf used. I don't see anything else.

On a side note, the question you ask doesn't contain all information. You don't get correlations. By this I mean that large monsters are less likely to appear together. For example, if you see Winged Demons on dlvl5, that information pushes the probability of Black Deaths present way down.

For the record, I'm doing my PhD in Probability.



Any Probability/Statistic majors among us? - the Langolier - 10-20-2009

Solved it.

I was trying to calculate the probabilites based on how many combinations an enemy appeared in in relation total number of combinations. Instead, you must calculate the individual probability to generate each particular combination. The sum of these probabilities for all combinations will be 100%; to get the probability of any one monsters spawning, you must only sum the probabilites of the combinations in which the particular enemy appears in.

From the first example of my first post, 1/36 isn't the probability to pick a Soul Burner itself, but rather the probability of picking that particular combination in that particular order (Doom Guard, Soul Burner). This requires that you keep track of how many possibilites there are to pick from based on previously chosen enemies.

For example, opposed to a die which will always have 6 possible outcomes regardless of a previous roll, the number of possible enemies to pick to spawn will depend on what was picked previously. If Doom guard is picked first, the restrictions on the "size" will limit the next choice to 4 enemies, hence the DG/SB combination has a 1/9 * 1/4 = 1/36 probability of being generated. If Soul Burner is picked first, however, the game can still pick any of the remaining 8 enemies, hence the SB/DG combination has a 1/9 * 1/8 = 1/72 probability of being generated. The total probability to generate Doom Guards and Soul Burners is the sum of these two probabilities 1/36 + 1/72 = 1/24.

It is not the occurances of a particular enemy in different combinations that must be summed, but the probabilites of those combinations that must be summed to determine the overall probability.


Any Probability/Statistic majors among us? - --Pete - 10-20-2009

Hi,

Quote:It is not the occurances of a particular enemy in different combinations that must be summed, but the probabilites of those combinations that must be summed to determine the overall probability.
However, there is one more wrinkle that you need to consider, and that is how the monster is chosen. To work that out, you need to know the exact algorithm used. It is the same type of problem as dealing virtual cards from a deck. Let's consider two ways to do it which will give us two different answers.

The first way actually simulates the result of a real deal of a random deck. Number the cards in some manner, for instance (A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q. K) of clubs is 1-13, diamonds 14-26, etc. Then generate a random integer in the range [1, 52]. The corresponding card is 'dealt'. Now, renumber all the cards above the dealt card by -1, thus eliminating the 'hole'. Generate a random integer from [1, 51] and repeat the process. Each time, the 'holes' in the deck are closed up and the random number generated is reduced by one. Using this method, any card in the deck has a 1/52 probability of being dealt first, a 1/51 probability of being dealt second, etc. However, setting up this algorithm is a bit of a pain and not too many people use it.

Now, the second way is much easier to calculate, but gives a different result. Like the first way, start by numbering the cards. Then to select each card, generate a random integer from [1, 52] each time. The first time, deal that card and mark it as 'dealt'. The second time, if the card chosen hasn't been dealt, then deal it and mark it as dealt. Repeat for the remaining cards. So far, so good. But how do we handle the case of the random integer pointing to a dealt card? We could re-roll, which gives us a 'true' deal. However, as the number of cards left gets small, it can take a *lot* of rolls before we hit one of the remaining cards. So, most people don't do that. A common way of resolving this is to go to the next undealt card. But this throws the odds off. Say we're working on the second card and the first card was 9. We roll a number, and if it is anything other than a 9, we pick that card. If it is a 9, then we pick the 10. This means that the remaining cards each have a 1/52 chance of being picked except for the 10 which has a 2/52 chance. As the holes in the deck get longer, the odds for the cards after the holes get more skewed.

There are other ways of doing this, some of which replicate a deal with real cards and some don't. But, a little though and you can see that the problems are common anytime you want to pick items randomly from a list. In the case of populating Diablo levels, after a certain point, the mobs whose value is greater than the remaining budget go into the 'can't pick' list with the mobs that have been picked. If the 'next available' algorithm is being used, this will further throw of the probabilities.

And, of course, the characteristics of the PRNG enter the picture. But that's a whole book in itself.

Have fun:)

--Pete



Any Probability/Statistic majors among us? - eppie - 10-20-2009

Quote:Hi,
However, there is one more wrinkle that you need to consider, and that is how the monster is chosen. To work that out, you need to know the exact algorithm used. It is the same type of problem as dealing virtual cards from a deck. Let's consider two ways to do it which will give us two different answers.

The first way actually simulates the result of a real deal of a random deck. Number the cards in some manner, for instance (A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q. K) of clubs is 1-13, diamonds 14-26, etc. Then generate a random integer in the range [1, 52]. The corresponding card is 'dealt'. Now, renumber all the cards above the dealt card by -1, thus eliminating the 'hole'. Generate a random integer from [1, 51] and repeat the process. Each time, the 'holes' in the deck are closed up and the random number generated is reduced by one. Using this method, any card in the deck has a 1/52 probability of being dealt first, a 1/51 probability of being dealt second, etc. However, setting up this algorithm is a bit of a pain and not too many people use it.

Now, the second way is much easier to calculate, but gives a different result. Like the first way, start by numbering the cards. Then to select each card, generate a random integer from [1, 52] each time. The first time, deal that card and mark it as 'dealt'. The second time, if the card chosen hasn't been dealt, then deal it and mark it as dealt. Repeat for the remaining cards. So far, so good. But how do we handle the case of the random integer pointing to a dealt card? We could re-roll, which gives us a 'true' deal. However, as the number of cards left gets small, it can take a *lot* of rolls before we hit one of the remaining cards. So, most people don't do that. A common way of resolving this is to go to the next undealt card. But this throws the odds off. Say we're working on the second card and the first card was 9. We roll a number, and if it is anything other than a 9, we pick that card. If it is a 9, then we pick the 10. This means that the remaining cards each have a 1/52 chance of being picked except for the 10 which has a 2/52 chance. As the holes in the deck get longer, the odds for the cards after the holes get more skewed.

There are other ways of doing this, some of which replicate a deal with real cards and some don't. But, a little though and you can see that the problems are common anytime you want to pick items randomly from a list. In the case of populating Diablo levels, after a certain point, the mobs whose value is greater than the remaining budget go into the 'can't pick' list with the mobs that have been picked. If the 'next available' algorithm is being used, this will further throw of the probabilities.

And, of course, the characteristics of the PRNG enter the picture. But that's a whole book in itself.

Have fun:)

--Pete

I love probabilty calculation......in high school we used to have quite a good book, and apart from learning some rules there is some puzzling and logic thinking to do, which I was able to do quite well at that time. Of course now I lost all that, and even don't have the books (we used to 'rent' books in high school). At university we didn't do probability calculus anymore.

Pete, do you happen to know some titles of good text books dealing with probability calculus (prefereably something that can still be ordered)? In terms of the level I am thinking about first year university course level (for general science students, not mathematics students) or maybe end of high school level.



Any Probability/Statistic majors among us? - kandrathe - 10-20-2009

Quote:I love probabilty calculation......in high school we used to have quite a good book, and apart from learning some rules there is some puzzling and logic thinking to do, which I was able to do quite well at that time. Of course now I lost all that, and even don't have the books (we used to 'rent' books in high school). At university we didn't do probability calculus anymore.

Pete, do you happen to know some titles of good text books dealing with probability calculus (prefereably something that can still be ordered)? In terms of the level I am thinking about first year university course level (for general science students, not mathematics students) or maybe end of high school level.
Problems like this are why I began writing programs in the 8th grade.



Any Probability/Statistic majors among us? - --Pete - 10-20-2009

Hi,

Quote:Pete, do you happen to know some titles of good text books dealing with probability calculus (prefereably something that can still be ordered)? In terms of the level I am thinking about first year university course level (for general science students, not mathematics students) or maybe end of high school level.
Sorry, I can't really help you on this. I was never really interested in probability and statistics for itself and never took a course in it. I was first forced to learn some when taking statistical mechanics (we used Atoms and Information Theory IIRC). Then some of the problems I worked on were best solved by Monte Carlo methods, so I did some library research. Laboratory work forced me to learn some things about statistics and treatment of experimental data. Then working Pk (probability of kill), CEP (circular error probable), survivability, vulnerability, and reliability led me to do more research. So, I can't even remember the books I used, much less recommend any.

However, what I can recommend is a research strategy that has let me get on the curve in many subjects over the years. You need access to a good open stacks library. Then, either by consulting a list of library numbers or by looking up a few key words, determine the location of the type of material of interest. Go to those stacks and grab a few 'interesting' looking books and take them to a table. Come up with a short list of keywords and look for them in each book's index. Anything you find, do a quick check of the text to see if it is clear and readable. If so, put that book on the 'keep' pile. When you've found three or four books, check them out. Take them home and treat them like dictionaries, not novels. Skim through them, find the parts that you're interested in and study just that. Pretty soon, you too can be an ex-spurt on anything. :whistling:

--Pete


Any Probability/Statistic majors among us? - the Langolier - 10-20-2009

Quite right; an important assumption I used is that each enemy has an equal chance of being chosen. Jarulf summarized the proceedure the game uses in his guide, however it doesn't have details that would indicate the particular method used. I'm also certain that Jarulf himself no longer remembers the algorithm the game uses. I duplicated his work (random generation) and used an algorithm would populate a new list of enemies without gaps each time, getting the same results he did. That leads me to think that each enemy has an equal chance of being picked each time (subject to the pRNG, of course).

In any case, I'm happy with the results:)


Any Probability/Statistic majors among us? - Spelunker - 11-10-2009

Congratulations Lang. If anybody is interested, I managed to develop a calculator with calculates the probabilities by a let me call it "loop random algorithm". It generates a random combination of monsters and it does this many times and finally averages. I just did not get a way to produce an algorithm that generates every possible combination only once and than averages / sums up which would get the "ideal" result and would be faster at the same time.

First here is a FreeMat version:
[codebox]
function [probabilities, monsternames, loops, elapsedtime] = monocc (dlvl,numberofloops,dontshow)

start = clock;

monsternames = {
'Zombie (Zombie)';
'Ghoul (Zombie)';
'Rotting Carcass (Zombie)';
'Black Death (Zombie)';
'Fallen One (Fallen One, spear)';
'Carver (Fallen One, spear)';
'Devil Kin (Fallen One, spear)';
'Dark One (Fallen One, spear)';
'Fallen One (Fallen One, sword)';
'Carver (Fallen One, sword)';
'Devil Kin (Fallen One, sword)';
'Dark One (Fallen One, sword)';
'Skeleton (Skeleton)';
'Corpse Axe (Skeleton)';
'Burning Dead (Skeleton)';
'Horror (Skeleton)';
'Skeleton Archer (Skeleton Archer)';
'Corpse Bow (Skeleton Archer)';
'Burning Dead Archer (Skeleton Archer)';
'Horror Archer (Skeleton Archer)';
'Skeleton Captain (Skeleton Captain)';
'Corpse Captain (Skeleton Captain)';
'Burning Dead Captain (Skeleton Captain)';
'Horror Captain (Skeleton Captain)';
'Scavenger (Scavenger)';
'Plague Eater (Scavenger)';
'Shadow Beast (Scavenger)';
'Bone Gasher (Scavenger)';
'Fiend (Winged Fiend)';
'Blink (Winged Fiend)';
'Gloom (Winged Fiend)';
'Familiar (Winged Fiend)';
'Hidden (The Hidden)';
'Stalker (The Hidden)';
'Unseen (The Hidden)';
'Illusion Weaver (The Hidden)';
'Flesh Clan (Goat Man)';
'Stone Clan (Goat Man)';
'Fire Clan (Goat Man)';
'Night Clan (Goat Man)';
'Flesh Clan Archer (Goat Man Archer)';
'Stone Clan Archer (Goat Man Archer)';
'Fire Clan Archer (Goat Man Archer)';
'Night Clan Archer (Goat Man Archer)';
'Overlord (Overlord)';
'Mud Man (Overlord)';
'Toad Demon (Overlord)';
'Flayed One (Overlord)';
'Winged-Demon (Gargoyle)';
'Gargoyle (Gargoyle)';
'Blood Claw (Gargoyle)';
'Death Wing (Gargoyle)';
'Magma Demon (Magma Demon)';
'Blood Stone (Magma Demon)';
'Hell Stone (Magma Demon)';
'Lava Lord (Magma Demon)';
'Horned Demon (Horned Demon)';
'Mud Runner (Horned Demon)';
'Frost Charger (Horned Demon)';
'Obsidian Lord (Horned Demon)';
'Acid Beast (Spitting Terror)';
'Poison Spitter (Spitting Terror)';
'Pit Beast (Spitting Terror)';
'Lava Maw (Spitting Terror)';
'Red Storm (Lightning Demon)';
'Storm Rider (Lightning Demon)';
'Storm Lord (Lightning Demon)';
'Maelstorm (Lightning Demon)';
'Slayer (Balrog)';
'Guardian (Balrog)';
'Vortex Lord (Balrog)';
'Balrog (Balrog)';
'Cave Viper (Viper)';
'Fire Drake (Viper)';
'Gold Viper (Viper)';
'Azure Drake (Viper)';
'Succubus (Succubus)';
'Snow Witch (Succubus)';
'Hell Spawn (Succubus)';
'Soul Burner (Succubus)';
'Black Knight (Knight)';
'Doom Guard (Knight)';
'Steel Lord (Knight)';
'Blood Knight (Knight)';
'Counselor (Mage)';
'Magistrate (Mage)';
'Cabalist (Mage)';
'Advocate (Mage)';
};

monstersizes_template = [
799;
543;
623;
553;
567;
575;
410;
364;
992;
1030;
1040;
1130;
1650;
1680;
1630;
716;
1740;
2200;
1270;
980;
2120;
2000;
];
monstersizes = zeros(4*max(size(monstersizes_template)),1);
for i = 1:4
monstersizes(i:4:4*max(size(monstersizes_template))) = monstersizes_template;
end
clear i monstersizes_template

switch dlvl
case 1 dlvlmonsters = [1,5,9,13,21,25];
case 2 dlvlmonsters = [1,2,3,5,6,7,9,10,11,13,14,15,17,18,21,22,25,26,29,33];
case 3 dlvlmonsters = [2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,21,22,23,25,26,27,29,30,33];
case 4 dlvlmonsters = [3,4,7,8,11,12,15,16,18,19,20,22,23,24,26,27,28,30,31,33,37,41];
case 5 dlvlmonsters = [4,8,12,16,19,20,23,24,27,28,30,31,33,34,37,38,41,42,45,49];
case 6 dlvlmonsters = [20,24,28,31,32,34,35,37,38,39,41,42,43,45,49,61];
case 7 dlvlmonsters = [32,34,35,38,39,40,42,43,44,45,46,49,50,57,61];
case 8 dlvlmonsters = [32,35,36,39,40,43,44,46,47,50,53,54,57,58,61,62];
case 9 dlvlmonsters = [36,40,44,46,47,50,51,53,54,55,56,57,58,59,62,65];
case 10 dlvlmonsters = [36,47,48,51,52,54,55,56,58,59,60,62,63,65,66,69];
case 11 dlvlmonsters = [48,51,52,55,56,59,60,63,65,66,67,69,70,73];
case 12 dlvlmonsters = [48,52,60,63,64,66,67,68,69,70,71,73,74,77,81];
case 13 dlvlmonsters = [64,67,68,70,71,72,73,74,75,77,78,81,82,83,84,85];
case 14 dlvlmonsters = [64,68,71,72,74,75,77,78,79,81,82,83,84,85,86];
case 15 dlvlmonsters = [72,76,78,79,80,82,83,86,87];
case 16 dlvlmonsters = [81,84,88];
otherwise error('First argument (dungeon level) must be an integer from 1 to 16.')
end

monsternames = monsternames(dlvlmonsters);
monstersizes = monstersizes(dlvlmonsters);
clear dlvlmonsters

switch dlvl
case 2 dungeonsize = 4000-386- 980;
case 3 dungeonsize = 4000-386-1010;
case 16 dungeonsize = 8240;
otherwise dungeonsize = 4000-386;
end

if sum(monstersizes) <= dungeonsize;
probabilities = 100*ones(size(monstersizes));
loops = 0;
else
probabilities = zeros(size(monstersizes));
for loops = 1:numberofloops
sizeleft = dungeonsize;
inactive = ~zeros(size(monstersizes),'logical');
if dlvl == 3
select = randi(12,20); % choose skeleton type
inactive(select) = false;
sizeleft = sizeleft - monstersizes(select);
probabilities(select) = probabilities(select)+1;
end
% while min(monstersizes(find(inactive))) <= sizeleft
while 1
candidates = find(inactive && (monstersizes<=sizeleft));
if isempty(candidates) % little bit faster than the while condition
break
end
select = candidates(randi(1,max(size(candidates))));
inactive(select) = false;
sizeleft = sizeleft - monstersizes(select);
probabilities(select) = probabilities(select)+1;
end
end
probabilities = probabilities/loops*100;
end

if nargin < 3
printf(['\nMonster Occurence on Dungeon Level ' num2str(dlvl) ' in percentage:\n'])
for i = 1:max(size(probabilities))
printf([' ' num2str(probabilities(i)) ' ' monsternames{i} '\n'])
end
switch dlvl
case 2 printf('Assuming multi player: The Butcher present.\n')
case 3 printf('Assuming multi player: Skeleton King present.\n')
case 15 printf('Not including Arch-Bishop Lazarus´ room on dlvl 15.\n')
case 16 printf('Dlvl 16 is created by special code.\n')
end
printf('\n')
end
stop = clock;
elapsedtime = etime(stop,start);
[/codebox]

Then I had some fun to turn this into a HTML with JavaScript version:
[codebox]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>

<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<title>Diablo Monster Occurence Probability Calculator</title>
<meta name="author" content="Spelunker">
<style type="text/css">
body { margin:2em; font-family:sans-serif; background-color:#c0c0c0; }
h1 { font-weight:normal; }
kbd { color:white; background-color:#999999; border:1px dotted white; padding:0.05em 0.2em; }
table { border-collapse:collapse; margin:1em 0em; }
caption { white-space:nowrap; font-size:large; }
table, thead, tfoot, tbody { border:2px solid black; }
td, th { border:1px solid black; padding:0.05em 0.4em; }
th { background-color:#999999; }
td { background-color:#cccccc; }
.br { border-right:2px solid black; }
.number { text-align:right; }
.oneline { white-space:nowrap; }
</style>
<script type="text/javascript">
// ATTENTION: Algorithm was developed for Freemat.
// There arrays start with index one instead of zero!
function tic () { ticdate = new Date(); } // global variable
function toc () {
if (typeof(ticdate) == "object" && typeof(ticdate.getTime) == "function") {
var tocdate = new Date();
return (tocdate.getTime()-ticdate.getTime())/1000;
}
return "Call tic() first!";
}
function errormsg (s) {
document.getElementById("iderror").firstChild.nodeValue = s;
}
function extract[quote] (old[quote], elements) {
var a = new [quote](elements.length);
for (var i = 0; i < elements.length; i++) {
a[i] = old[quote][elements[i]-1]; // The "-1" fixes the 0/1 first index thing
}
return a;
}
function sum (a) {
var s = 0;
for (var i = 0; i < a.length; i++) s += a[i];
return s;
}
function new[quote] (length, value) {
var a = new [quote](length);
for (var i = 0; i < length; i++) a[i] = value;
return a;
}
function randi (low,high) {
return low + Math.floor(Math.random()*(high-low+1));
}
function clr () {
errormsg("");
}
function compute () {
tic();
errormsg("computing ...");
// input
var dlvl = Number(document.dmopc.dlvl.value);
var nol = Number(document.dmopc.nol.value);
if (isNaN(nol) || nol < 1) {
errormsg("The number of loops must be an integer greater zero.");
return false;
}
// computation
// ATTENTION: Algorithm was developed for Freemat.
// There arrays start with index one instead of zero!
var monsternames = new [quote]('Zombie (Zombie)','Ghoul (Zombie)','Rotting Carcass (Zombie)','Black Death (Zombie)','Fallen One (Fallen One, spear)','Carver (Fallen One, spear)','Devil Kin (Fallen One, spear)','Dark One (Fallen One, spear)','Fallen One (Fallen One, sword)','Carver (Fallen One, sword)','Devil Kin (Fallen One, sword)','Dark One (Fallen One, sword)','Skeleton (Skeleton)','Corpse Axe (Skeleton)','Burning Dead (Skeleton)','Horror (Skeleton)','Skeleton Archer (Skeleton Archer)','Corpse Bow (Skeleton Archer)','Burning Dead Archer (Skeleton Archer)','Horror Archer (Skeleton Archer)','Skeleton Captain (Skeleton Captain)','Corpse Captain (Skeleton Captain)','Burning Dead Captain (Skeleton Captain)','Horror Captain (Skeleton Captain)','Scavenger (Scavenger)','Plague Eater (Scavenger)','Shadow Beast (Scavenger)','Bone Gasher (Scavenger)','Fiend (Winged Fiend)','Blink (Winged Fiend)','Gloom (Winged Fiend)','Familiar (Winged Fiend)','Hidden (The Hidden)','Stalker (The Hidden)','Unseen (The Hidden)','Illusion Weaver (The Hidden)','Flesh Clan (Goat Man)','Stone Clan (Goat Man)','Fire Clan (Goat Man)','Night Clan (Goat Man)','Flesh Clan Archer (Goat Man Archer)','Stone Clan Archer (Goat Man Archer)','Fire Clan Archer (Goat Man Archer)','Night Clan Archer (Goat Man Archer)','Overlord (Overlord)','Mud Man (Overlord)','Toad Demon (Overlord)','Flayed One (Overlord)','Winged-Demon (Gargoyle)','Gargoyle (Gargoyle)','Blood Claw (Gargoyle)','Death Wing (Gargoyle)','Magma Demon (Magma Demon)','Blood Stone (Magma Demon)','Hell Stone (Magma Demon)','Lava Lord (Magma Demon)','Horned Demon (Horned Demon)','Mud Runner (Horned Demon)','Frost Charger (Horned Demon)','Obsidian Lord (Horned Demon)','Acid Beast (Spitting Terror)','Poison Spitter (Spitting Terror)','Pit Beast (Spitting Terror)','Lava Maw (Spitting Terror)','Red Storm (Lightning Demon)','Storm Rider (Lightning Demon)','Storm Lord (Lightning Demon)','Maelstorm (Lightning Demon)','Slayer (Balrog)','Guardian (Balrog)','Vortex Lord (Balrog)','Balrog (Balrog)','Cave Viper (Viper)','Fire Drake (Viper)','Gold Viper (Viper)','Azure Drake (Viper)','Succubus (Succubus)','Snow Witch (Succubus)','Hell Spawn (Succubus)','Soul Burner (Succubus)','Black Knight (Knight)','Doom Guard (Knight)','Steel Lord (Knight)','Blood Knight (Knight)','Counselor (Mage)','Magistrate (Mage)','Cabalist (Mage)','Advocate (Mage)');
var monstersizes = new [quote](799,799,799,799,543,543,543,543,623,623,623,623,553,553,553,553,567,567,567,567,575,575,575,575,410,410,410,410,364,364,364,364,992,992,992,992,1030,1030,1030,1030,1040,1040,1040,1040,1130,1130,1130,1130,1650,1650,1650,1650,1680,1680,1680,1680,1630,1630,1630,1630,716,716,716,716,1740,1740,1740,1740,2200,2200,2200,2200,1270,1270,1270,1270,980,980,980,980,2120,2120,2120,2120,2000,2000,2000,2000);
switch (dlvl) {
case 1: var dlvlmonsters = new [quote](1,5,9,13,21,25); break;
case 2: var dlvlmonsters = new [quote](1,2,3,5,6,7,9,10,11,13,14,15,17,18,21,22,25,26,29,33); break;
case 3: var dlvlmonsters = new [quote](2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,21,22,23,25,26,27,29,30,33); break;
case 4: var dlvlmonsters = new [quote](3,4,7,8,11,12,15,16,18,19,20,22,23,24,26,27,28,30,31,33,37,41); break;
case 5: var dlvlmonsters = new [quote](4,8,12,16,19,20,23,24,27,28,30,31,33,34,37,38,41,42,45,49); break;
case 6: var dlvlmonsters = new [quote](20,24,28,31,32,34,35,37,38,39,41,42,43,45,49,61); break;
case 7: var dlvlmonsters = new [quote](32,34,35,38,39,40,42,43,44,45,46,49,50,57,61); break;
case 8: var dlvlmonsters = new [quote](32,35,36,39,40,43,44,46,47,50,53,54,57,58,61,62); break;
case 9: var dlvlmonsters = new [quote](36,40,44,46,47,50,51,53,54,55,56,57,58,59,62,65); break;
case 10: var dlvlmonsters = new [quote](36,47,48,51,52,54,55,56,58,59,60,62,63,65,66,69); break;
case 11: var dlvlmonsters = new [quote](48,51,52,55,56,59,60,63,65,66,67,69,70,73); break;
case 12: var dlvlmonsters = new [quote](48,52,60,63,64,66,67,68,69,70,71,73,74,77,81); break;
case 13: var dlvlmonsters = new [quote](64,67,68,70,71,72,73,74,75,77,78,81,82,83,84,85); break;
case 14: var dlvlmonsters = new [quote](64,68,71,72,74,75,77,78,79,81,82,83,84,85,86); break;
case 15: var dlvlmonsters = new [quote](72,76,78,79,80,82,83,86,87); break;
case 16: var dlvlmonsters = new [quote](81,84,88); break;
default: errormsg("First argument (dungeon level) must be an integer from 1 to 16."); return false;
}
var monsternames = extract[quote](monsternames,dlvlmonsters); // 0/1 first index thing, see above
var monstersizes = extract[quote](monstersizes,dlvlmonsters); // 0/1 first index thing, see above
delete dlvlmonsters;
switch (dlvl) {
case 2: var dungeonsize = 4000-386- 980; break;
case 3: var dungeonsize = 4000-386-1010; break;
case 16: var dungeonsize = 8240; break;
default: var dungeonsize = 4000-386; break;
}
if (sum(monstersizes) <= dungeonsize) {
var probabilities = new[quote](monstersizes.length,100);
var loops = 0;
} else {
var probabilities = new[quote](monstersizes.length,0);
var done = 0;
for (var loops = 0; loops < nol; loops++) {
if (loops >= done*nol) {
errormsg("computing ... " + Math.round(done*100) + " %");
done += 0.01;
}
var sizeleft = dungeonsize;
var inactive = new[quote](monstersizes.length,true);
if (dlvl == 3) {
var select = randi(12,20)-1; // The "-1" fixes the 0/1 first index thing
inactive[select] = false;
sizeleft -= monstersizes[select];
probabilities[select] += 1;
}
while (1) {
var candidates = new [quote]();
for (var i = 0; i < monstersizes.length; i++) {
if (inactive[i] && monstersizes[i]<=sizeleft) candidates.push(i);
}
if (candidates.length == 0) break;
var select = candidates[randi(1,candidates.length)-1]; // The "-1" fixes the 0/1 first index thing
inactive[select] = false;
sizeleft -= monstersizes[select];
probabilities[select] += 1;
}
}
for (var i = 0; i < probabilities.length; i++) {
probabilities[i] *= 100/loops;
}
}
// output
var o = document.getElementById("output");
var list = document.createElement("table");
var thead = document.createElement("thead");
var tr = document.createElement("tr");
var th = document.createElement("th");
th.colSpan = "2";
var looptext = (loops==1) ? " loop, " : " loops, ";
th.appendChild(document.createTextNode("Monster Occurence Probabilitis on dlvl " + dlvl + " (" + loops + looptext + toc() + "s)"));
tr.appendChild(th);
thead.appendChild(tr);
var tr = document.createElement("tr");
var th = document.createElement("th")
th.appendChild(document.createTextNode("%"));
tr.appendChild(th);
var th = document.createElement("th")
th.appendChild(document.createTextNode("Monster"));
tr.appendChild(th);
thead.appendChild(tr);
list.appendChild(thead);
if (dlvl == 2 || dlvl == 3 || dlvl == 15 || dlvl == 16) {
var tfoot = document.createElement("tfoot");
var tr = document.createElement("tr");
var td = document.createElement("td");
td.colSpan = "2";
switch (dlvl) {
case 2: var fn = "Assuming multi player: The Butcher present."; break;
case 3: var fn = "Assuming multi player: Skeleton King present."; break;
case 15: var fn = "Not including Arch-Bishop Lazarus' room on dlvl 15."; break;
case 16: var fn = "Dlvl 16 is created by special code."; break;
}
td.appendChild(document.createTextNode(fn));
tr.appendChild(td);
tfoot.appendChild(tr);
list.appendChild(tfoot);
}
for (var i = 0; i < monstersizes.length; i++) {
var tr = document.createElement("tr");
var td = document.createElement("td");
td.className = "number";
td.appendChild(document.createTextNode(probabilities[i].toFixed(2)));
tr.appendChild(td);
var td = document.createElement("td");
td.appendChild(document.createTextNode(monsternames[i]));
tr.appendChild(td);
list.appendChild(tr);
}
o.insertBefore(list, o.firstChild);
errormsg("ready");
return false;
}
</script>
</head>

<body>

<h1>Diablo Monster Occurence Probability Calculator</h1>

<form action="" name="dmopc" onsubmit="return compute();" onreset="clr();">
<table>
<colgroup span="2" class="br"></colgroup>
<thead>
<tr>
<th colspan="2">Input</th>
<th>Help</th>
</tr>
</thead>
<tr>
<td class="oneline">dungeon&nbsp;level:</td>
<td><input type="text" name="dlvl" maxlength="2" size="4" class="number"></td>
<td>1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 or 16. Hellfire is not implemented.</td>
</tr>
<tr>
<td class="oneline">number&nbsp;of&nbsp;loops:</td>
<td><input type="text" name="nol" maxlength="9" size="4" class="number"></td>
<td>e.&nbsp;g. 1e<var>n</var> = 10<sup><var>n</var></sup>, <var>n</var>=3: <kbd>1e3</kbd> = 1000. Larger values should give better results but take more time. Use <kbd>1</kbd> to get a concrete possible monster configuration.</td>
</tr>
<tr>
<td colspan="2" class="oneline"><input type="submit">&nbsp;<input type="reset"></td>
<td id="iderror">JavaScript disabled or not available &ndash; no calculator</td>
</tr>
</table>
</form>

<p>
Based on <a href="http://www.lurkerlounge.com/jarulf">Jarulf's Guide to Diablo and Hellfire</a> Version&nbsp;1.62 by Pedro Faria / Jarulf. Written and developed in HTML and JavaScript by Spelunker. No warranty of correctness.
</p>

<div id="output"></div>

<script type="text/javascript">errormsg("");</script>

</body>

</html>
[/codebox]

Some numbers are more close to Jarulf's results than other numbers. There could be some bugs left, of course. Surprisingly (to me) the HTML/JavaScript version seems to be faster than the FreeMat version.

Spelunker