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 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 of loops:</td>
<td><input type="text" name="nol" maxlength="9" size="4" class="number"></td>
<td>e. 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"> <input type="reset"></td>
<td id="iderror">JavaScript disabled or not available – 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 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