2019-06-08 03:38:52 +02:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
2019-06-10 01:15:12 +02:00
< meta name = "author" content = "Felix W. Dekker" / >
< meta name = "application-name" content = "Dice probabilities" / >
< meta name = "description" content = "Calculates the probability of throwing a value given a combination of dice." / >
< meta name = "theme-color" content = "#0033cc" / >
2019-06-08 03:38:52 +02:00
< title > Dice probabilities | FWDekker< / title >
2019-06-08 03:57:10 +02:00
< link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
2019-06-10 01:15:12 +02:00
integrity="sha256-l85OmPOjvil/SOvVt3HnSSjzF1TUMyT9eV0c2BzEGzU=" crossorigin="anonymous" />
2019-06-08 03:57:10 +02:00
< link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.min.css"
2019-06-10 01:15:12 +02:00
integrity="sha256-Ro/wP8uUi8LR71kwIdilf78atpu8bTEwrK5ZotZo+Zc=" crossorigin="anonymous" />
2019-06-08 03:57:10 +02:00
< style >
body {
margin-top: 50px;
margin-bottom: 50px;
}
2019-06-10 01:33:54 +02:00
.footer {
margin-top: 50px;
}
2019-06-08 03:57:10 +02:00
< / style >
2019-06-08 03:38:52 +02:00
< / head >
< body >
2019-06-10 01:33:54 +02:00
< main class = "wrapper" >
<!-- Header -->
< header class = "header" >
< section class = "container" >
< h1 > Dice probabilities< / h1 >
< blockquote >
< p > < em > Calculates the probability of throwing a value given a combination of dice.< / em > < / p >
< / blockquote >
< / section >
< / header >
2019-06-08 03:57:10 +02:00
<!-- Input -->
2019-06-10 01:33:54 +02:00
< section class = "container" >
< div class = "row" >
< div class = "column" >
< form >
< fieldset >
< table id = "dieSettings" >
< thead >
< tr >
< th > Sides per die< / th >
< th > Number of throws< / th >
< th > < / th >
< / tr >
< / thead >
< tbody >
< tr >
< td >
< button id = "addDieRowButton" class = "button-outline" type = "button" > Add dice
< / button >
< / td >
< / tr >
< / tbody >
< / table >
< button id = "submit" type = "button" > Submit< / button >
< / fieldset >
< / form >
< / div >
2019-06-08 03:57:10 +02:00
< / div >
2019-06-10 01:33:54 +02:00
< / section >
2019-06-08 03:57:10 +02:00
<!-- Output -->
2019-06-10 01:33:54 +02:00
< section class = "container" >
< h2 > Probabilities< / h2 >
< div class = "row" >
< div class = "column" >
< canvas id = "probChart" > < / canvas >
< / div >
2019-06-08 03:57:10 +02:00
< / div >
2019-06-10 01:33:54 +02:00
< / section >
<!-- Footer -->
< footer class = "footer" >
< section class = "container" >
2019-06-10 01:35:31 +02:00
< p > Made by < a href = "https://fwdekker.com/" > Felix W. Dekker< / a > . Licensed under the < a href = "https://git.fwdekker.com/FWDekker/dice/src/branch/master/LICENSE" > MIT License< / a > . Source code available on < a href = "https://git.fwdekker.com/FWDekker/dice/" > git< / a > .< / p >
2019-06-10 01:33:54 +02:00
< / section >
< / footer >
< / main >
2019-06-08 03:57:10 +02:00
<!-- Scripts -->
< script src = "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"
integrity="sha256-oSgtFCCmHWRPQ/JmR4OoZ3Xke1Pw4v50uh6pLcu+fIc=" crossorigin="anonymous">< / script >
< script >
//////
///
/// Helper functions
///
//////
2019-06-10 01:15:12 +02:00
const $ = query => document.querySelector(query);
2019-06-08 03:57:10 +02:00
const doAfterLoad = fun => {
const oldOnLoad = window.onload || (() => {
});
window.onload = (() => {
oldOnLoad();
fun();
});
};
const repeat = (value, length) => {
const zeroArray = [];
for (let i = 0; i < length ; i + + )
zeroArray.push(value);
return zeroArray;
};
const rangeExclusive = (from, to) => {
const rangeArray = [];
for (let i = from; i < to ; i + + )
rangeArray.push(i);
return rangeArray;
};
const rangeInclusive = (from, to) => {
const rangeArray = rangeExclusive(from, to);
rangeArray.push(to);
return rangeArray;
};
const iterateNodeList = (nodeList, fun) => {
for (let i = 0; i < nodeList.length ; i + + ) {
const node = nodeList.item(i);
fun(node);
}
};
//////
///
/// Input
///
//////
const inputTable = {};
// Functions
2019-06-10 01:15:12 +02:00
inputTable.getTable = () => $("#dieSettings tbody");
inputTable.dieRowCount = () => inputTable.getTable().querySelectorAll(".dieEyes").length;
2019-06-08 03:57:10 +02:00
inputTable.highestDieRowIndex = () => {
const table = inputTable.getTable();
let highestDieRowIndex = -1;
iterateNodeList(table.getElementsByTagName("tr"), (node) => {
2019-06-10 01:15:12 +02:00
if ("index" in node.dataset)
highestDieRowIndex = Math.max(highestDieRowIndex, +node.dataset.index);
2019-06-08 03:57:10 +02:00
});
return highestDieRowIndex;
};
inputTable.addDieRow = () => {
const createNumberInput = (index, className, value) => {
const input = document.createElement("input");
input.id = className + index;
input.className = className;
input.type = "number";
2019-06-10 01:15:12 +02:00
input.min = "1";
input.step = "1";
2019-06-08 03:57:10 +02:00
input.value = value;
input.addEventListener("keypress", (e) => {
if (e.key === "Enter")
outputChart.updateProbGraph();
});
input.focus();
return input;
};
const createRemoveLink = (index, className) => {
const link = document.createElement("button");
link.id = className + index;
link.className = className + " button-clear";
link.type = "button";
link.innerHTML = "Remove";
link.onclick = (() => inputTable.removeDieRow(index));
return link;
};
const table = inputTable.getTable();
const newIndex = inputTable.highestDieRowIndex() + 1;
2019-06-10 01:15:12 +02:00
const row = table.insertRow(inputTable.dieRowCount());
2019-06-08 03:57:10 +02:00
row.dataset.index = newIndex;
row.insertCell().appendChild(createNumberInput(newIndex, "dieEyes", 6));
row.insertCell().appendChild(createNumberInput(newIndex, "dieCount", 2));
row.insertCell().appendChild(createRemoveLink(newIndex, "dieRemove"));
};
inputTable.removeDieRow = index => {
if (inputTable.highestDieRowIndex() > 0) {
const table = inputTable.getTable();
const row = table.querySelector("tr[data-index=\"" + index + "\"]");
row.parentElement.removeChild(row);
}
};
inputTable.getDice = () => {
const dice = [];
const eyesInputs = document.getElementsByClassName("dieEyes");
const countInputs = document.getElementsByClassName("dieCount");
for (let i = 0; i < eyesInputs.length ; i + + ) {
const count = parseInt(countInputs.item(i).value);
for (let j = 0; j < count ; j + + )
dice.push(parseInt(eyesInputs.item(i).value));
}
return dice;
};
// Handlers
2019-06-10 01:15:12 +02:00
$("#addDieRowButton").onclick = inputTable.addDieRow;
2019-06-08 03:57:10 +02:00
// Init
2019-06-10 01:15:12 +02:00
doAfterLoad(() => $("#addDieRowButton").click());
2019-06-08 03:57:10 +02:00
//////
///
/// Output
///
//////
const outputChart = {};
// Functions
outputChart.calculateDiceFrequencies = dice => {
if (dice.length === 0)
return [];
// Roll dice
let rollFreqs = [0].concat(repeat(1, dice[0]));
dice.slice(1).forEach(die => {
const dieRollFreqs = rollFreqs.concat(repeat(0, die));
rollFreqs = repeat(0, dieRollFreqs.length);
rangeInclusive(1, die).forEach(throwValue => {
rangeExclusive(1, dieRollFreqs.length - die).forEach(i => {
rollFreqs[throwValue + i] += dieRollFreqs[i];
});
});
});
rollFreqs.shift();
// Calculate frequencies
const totalRolls = rollFreqs.reduce((a, b) => a + b, 0);
rangeExclusive(0, rollFreqs.length).forEach(roll => {
rollFreqs[roll] = rollFreqs[roll] / totalRolls;
});
return rollFreqs;
};
outputChart.updateProbGraph = () => {
const dice = inputTable.getDice();
const rollFreqs = outputChart.calculateDiceFrequencies(dice);
probChart.data.labels = rangeInclusive(1, rollFreqs.length);
probChart.data.datasets = [{
data: rollFreqs,
backgroundColor: "rgb(155, 77, 202, 0.4)"
}];
probChart.update();
};
2019-06-08 03:38:52 +02:00
2019-06-08 03:57:10 +02:00
// Handlers
2019-06-10 01:15:12 +02:00
$("#submit").onclick = outputChart.updateProbGraph;
2019-06-08 03:38:52 +02:00
2019-06-08 03:57:10 +02:00
// Init
2019-06-10 01:15:12 +02:00
const ctx = $("#probChart").getContext("2d");
2019-06-08 03:57:10 +02:00
const probChart = new Chart(ctx, {
type: "line",
data: {},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
display: true,
ticks: {
suggestedMin: 0
}
}]
}
}
});
2019-06-08 03:38:52 +02:00
2019-06-10 01:15:12 +02:00
doAfterLoad(() => $("#submit").click());
2019-06-08 03:57:10 +02:00
< / script >
2019-06-08 03:38:52 +02:00
< / body >
< / html >