// Mythic Oracle — Dice: pool builder, custom roll, percentile, ability score
// Pure frontend — no backend calls.
const dicePool = { 4: 0, 6: 0, 8: 0, 10: 0, 12: 0, 20: 0, 2: 0 };
const POOL_MAX = 25;
const DIE_ORDER = [4, 6, 8, 10, 12, 20, 2];
function addDie(sides) {
const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0);
if (total >= POOL_MAX) return;
dicePool[sides]++;
updatePoolDisplay();
}
function clearPool() {
DIE_ORDER.forEach((d) => {
dicePool[d] = 0;
});
updatePoolDisplay();
const box = document.getElementById('poolResult');
box.className = 'dice-result-box';
box.innerHTML = 'Build a pool and roll.';
}
function updatePoolDisplay() {
const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0);
const display = document.getElementById('poolDisplay');
const capMsg = document.getElementById('poolCapMsg');
const parts = DIE_ORDER.filter((d) => dicePool[d] > 0).map((d) => `${dicePool[d]}d${d}`);
if (parts.length === 0) {
display.innerHTML = 'No dice in pool — click above to add.';
} else {
display.textContent = parts.join(' + ');
}
const capped = total >= POOL_MAX;
capMsg.style.display = capped ? '' : 'none';
document.querySelectorAll('#diceGrid .die-btn').forEach((btn) => {
btn.disabled = capped;
});
}
function rollPool() {
const total = DIE_ORDER.reduce((s, d) => s + dicePool[d], 0);
const box = document.getElementById('poolResult');
if (total === 0) {
box.className = 'dice-result-box animate';
box.innerHTML = 'Add dice to the pool first.';
void box.offsetWidth;
return;
}
const results = {};
let grandTotal = 0;
DIE_ORDER.forEach((sides) => {
if (dicePool[sides] > 0) {
results[sides] = [];
for (let i = 0; i < dicePool[sides]; i++) {
const roll = Math.floor(Math.random() * sides) + 1;
results[sides].push(roll);
grandTotal += roll;
}
}
});
const activeDice = DIE_ORDER.filter((s) => results[s]);
const groupedHTML = activeDice
.map((sides) => {
const rolls = results[sides];
const sum = rolls.reduce((a, b) => a + b, 0);
const rollStr = rolls.length > 1 ? `${rolls.join(' + ')} = ${sum}` : `${sum}`;
return `
d${sides}: ${rollStr}
`;
})
.join('');
box.className = 'dice-result-box animate';
box.innerHTML = `
${grandTotal}
${groupedHTML}
`;
void box.offsetWidth;
}
function validateCustomRoll() {
const qty = document.getElementById('customQty').value;
const sides = document.getElementById('customSides').value;
const btn = document.getElementById('customRollBtn');
const isPosInt = (v) => v !== '' && Number.isInteger(Number(v)) && Number(v) > 0;
const valid = isPosInt(qty) && Number(qty) <= 25 && isPosInt(sides);
btn.disabled = !valid;
}
function rollCustom() {
const qty = Number(document.getElementById('customQty').value);
const sides = Number(document.getElementById('customSides').value);
const box = document.getElementById('customResult');
const rolls = [];
let total = 0;
for (let i = 0; i < qty; i++) {
const roll = Math.floor(Math.random() * sides) + 1;
rolls.push(roll);
total += roll;
}
box.className = 'dice-result-box animate';
box.innerHTML = `
${total}
${qty}d${sides} [${rolls.join(', ')}]
`;
void box.offsetWidth;
}
function clearCustomRoll() {
const box = document.getElementById('customResult');
box.className = 'dice-result-box';
box.innerHTML = 'Enter a quantity and die size.';
}
function quickRoll(sides, qty, mod, label, boxId) {
const rolls = [];
for (let i = 0; i < qty; i++) {
rolls.push(Math.floor(Math.random() * sides) + 1);
}
let total = rolls.reduce((a, b) => a + b, 0) + mod;
if (label.includes('drop lowest')) {
const sorted = [...rolls].sort((a, b) => a - b);
sorted.shift();
total = sorted.reduce((a, b) => a + b, 0);
}
const box = document.getElementById(boxId);
box.classList.add('animate');
box.innerHTML = `
${total}
${label} [${rolls.join(', ')}]
`;
void box.offsetWidth;
}
function rollAbilityArray() {
const lines = [];
for (let i = 0; i < 6; i++) {
const rolls = [];
for (let j = 0; j < 4; j++) {
rolls.push(Math.floor(Math.random() * 6) + 1);
}
const sorted = [...rolls].sort((a, b) => a - b);
sorted.shift();
const total = sorted.reduce((a, b) => a + b, 0);
lines.push(`${rolls.join(', ')} → ${total}`);
}
const box = document.getElementById('abilityResult');
box.className = 'dice-result-box animate';
box.innerHTML = `${lines.join('
')}
`;
void box.offsetWidth;
}
function init() {
document.querySelectorAll('#diceGrid .die-btn').forEach((btn) => {
btn.addEventListener('click', () => addDie(Number(btn.dataset.sides)));
});
document.getElementById('poolRollBtn').addEventListener('click', rollPool);
document.getElementById('poolClearBtn').addEventListener('click', clearPool);
document.getElementById('customQty').addEventListener('input', validateCustomRoll);
document.getElementById('customSides').addEventListener('input', validateCustomRoll);
document.getElementById('customRollBtn').addEventListener('click', rollCustom);
document.getElementById('customClearBtn').addEventListener('click', clearCustomRoll);
document.getElementById('percentileTensBtn').addEventListener('click', () => {
quickRoll(10, 1, 0, 'Percentile (tens)', 'percentileResult');
});
document.getElementById('percentileOnesBtn').addEventListener('click', () => {
quickRoll(10, 1, 0, 'Percentile (ones)', 'percentileResult');
});
document.getElementById('ability4d6Btn').addEventListener('click', () => {
quickRoll(6, 4, -4, '4d6 drop lowest (approx)', 'abilityResult');
});
document.getElementById('abilityArrayBtn').addEventListener('click', rollAbilityArray);
}
document.addEventListener('DOMContentLoaded', init);