Oathsworn Optimal Dice Calculator
Calculate Optimal Dice
Running simulations… This may take a few seconds.
Optimal Strategy
Best choice: Roll dice
Success rate:
Auto-fail rate:
Average total:
Detailed Results
| Dice |
Success Rate |
Auto-Fail Rate |
Avg Total |
Oathsworn Dice Mechanics
- Each die has 6 faces: 2 Blanks, 2 ones, 1 regular two, and 1 critical two.
- Critical twos trigger an additional roll of the same die, adding to your total.
- Rolling two or more blanks results in an automatic failure.
- With reroll tokens, you can reroll dice after seeing your initial result.
.oathsworn-calculator {
font-family: -apple-system, BlinkMacSystemFont, ‘Segoe UI’, Roboto, Oxygen, Ubuntu, Cantarell, ‘Open Sans’, ‘Helvetica Neue’, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.oathsworn-calculator h1 {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.oathsworn-calculator h2 {
font-size: 20px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.oathsworn-calculator .input-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 600px) {
.oathsworn-calculator .input-grid {
grid-template-columns: 1fr;
}
}
.oathsworn-calculator .input-container {
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
}
.oathsworn-calculator label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.oathsworn-calculator input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
margin-top: 5px;
}
.oathsworn-calculator button {
padding: 10px 16px;
background-color: #0066cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
margin-bottom: 20px;
}
.oathsworn-calculator button:hover {
background-color: #0055b3;
}
.oathsworn-calculator button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.oathsworn-calculator .results-container {
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
margin-bottom: 20px;
}
.oathsworn-calculator .optimal-result {
background-color: #e6f7e6;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}
.oathsworn-calculator .optimal-number {
color: #2e7d32;
font-weight: bold;
}
.oathsworn-calculator table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
}
.oathsworn-calculator th, .oathsworn-calculator td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
.oathsworn-calculator th {
background-color: #f2f2f2;
font-weight: 500;
}
.oathsworn-calculator tr.optimal-row {
background-color: #e6f7e6;
}
.oathsworn-calculator .info-box {
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
margin-top: 20px;
}
.oathsworn-calculator .info-box ul {
padding-left: 20px;
margin-top: 10px;
}
document.addEventListener(‘DOMContentLoaded’, function() {
// Get DOM elements
const difficultyInput = document.getElementById(‘difficulty-input’);
const rerollTokensInput = document.getElementById(‘reroll-tokens-input’);
const calculateButton = document.getElementById(‘calculate-button’);
const calculatingMessage = document.getElementById(‘calculating-message’);
const optimalResult = document.getElementById(‘optimal-result’);
const optimalDice = document.getElementById(‘optimal-dice’);
const optimalSuccessRate = document.getElementById(‘optimal-success-rate’);
const optimalFailRate = document.getElementById(‘optimal-fail-rate’);
const optimalAverage = document.getElementById(‘optimal-average’);
const resultsContainer = document.getElementById(‘results-container’);
const resultsBody = document.getElementById(‘results-body’);
// Dice face constants
const BLANK = 0;
const ONE = 1;
const TWO = 2;
const CRIT = 3; // Special marker for critical 2
// Die face distribution
const dieFaces = [BLANK, BLANK, ONE, ONE, TWO, CRIT];
// Function to simulate rolling a single die, considering critical hits
function rollDie() {
let total = 0;
let face = dieFaces[Math.floor(Math.random() * dieFaces.length)];
// If it’s a critical, keep rolling and adding 2s
while (face === CRIT) {
total += 2;
face = dieFaces[Math.floor(Math.random() * dieFaces.length)];
}
// Add the final non-critical value
if (face === ONE) total += 1;
if (face === TWO) total += 2;
return { total, face };
}
// Function to simulate a full roll of n dice
function simulateRoll(numDice, difficulty, numRerollTokens = 0) {
// Roll all dice
let rolls = Array(numDice).fill().map(() => rollDie());
// Check for automatic failure (2+ blanks)
let blankCount = rolls.filter(r => r.face === BLANK).length;
if (blankCount >= 2) {
return { success: false, total: 0, autoFail: true };
}
// Calculate total
let total = rolls.reduce((sum, roll) => sum + roll.total, 0);
// Apply rerolls if available and beneficial
let tokensRemaining = numRerollTokens;
while (tokensRemaining > 0) {
// Find the lowest value die (prioritizing blanks)
let lowestRollIndex = -1;
let lowestValue = Infinity;
for (let i = 0; i < rolls.length; i++) {
const roll = rolls[i];
if (roll.face === BLANK) {
lowestRollIndex = i;
break;
} else if (roll.total < lowestValue) {
lowestValue = roll.total;
lowestRollIndex = i;
}
}
if (lowestRollIndex === -1) break;
const lowestRoll = rolls[lowestRollIndex];
// Only reroll if it would be beneficial (blank or 1)
if (lowestRoll && (lowestRoll.face === BLANK || lowestRoll.total sum + roll.total, 0);
// Recalculate blank count
blankCount = rolls.filter(r => r.face === BLANK).length;
// Check if the reroll created a second blank (auto fail)
if (blankCount >= 2) {
return { success: false, total: 0, autoFail: true };
}
// Use up a token
tokensRemaining–;
} else {
// No beneficial rerolls left
break;
}
}
return { success: total >= difficulty, total, autoFail: false };
}
// Monte Carlo simulation to estimate success probability
function calculateProbability(numDice, difficulty, rerollTokens, numTrials = 5000) {
let successes = 0;
let autoFails = 0;
let totalSum = 0;
for (let i = 0; i b.successRate – a.successRate)[0];
}
// Handle calculation button click
calculateButton.addEventListener(‘click’, function() {
const difficulty = parseInt(difficultyInput.value) || 5;
const rerollTokens = parseInt(rerollTokensInput.value) || 0;
// Show calculating message
calculateButton.disabled = true;
calculateButton.textContent = ‘Calculating…’;
calculatingMessage.style.display = ‘block’;
optimalResult.style.display = ‘none’;
resultsContainer.style.display = ‘none’;
// We’ll analyze from 1 to a reasonable upper limit
// The upper limit is adaptive based on the difficulty
const upperLimit = Math.max(10, Math.ceil(difficulty * 1.2));
// Use setTimeout to avoid freezing the UI
setTimeout(function() {
const allResults = [];
for (let i = 1; i 3 &&
allResults[i-3].successRate > allResults[i-2].successRate &&
allResults[i-2].successRate > allResults[i-1].successRate) {
break;
}
if (i > 5 &&
result.autoFailRate > 0.4 &&
allResults[i-2].successRate > allResults[i-1].successRate) {
break;
}
}
// Display results
displayResults(allResults);
// Re-enable the button
calculateButton.disabled = false;
calculateButton.textContent = ‘Calculate Optimal Dice’;
calculatingMessage.style.display = ‘none’;
}, 50);
});
// Function to display results
function displayResults(results) {
// Find optimal dice count
const optimal = getOptimalDice(results);
if (!optimal) return;
// Display optimal result
optimalDice.textContent = optimal.numDice;
optimalSuccessRate.textContent = (optimal.successRate * 100).toFixed(2) + ‘%’;
optimalFailRate.textContent = (optimal.autoFailRate * 100).toFixed(2) + ‘%’;
optimalAverage.textContent = optimal.averageTotal.toFixed(2);
optimalResult.style.display = ‘block’;
// Clear and populate results table
resultsBody.innerHTML = ”;
results.forEach(function(result) {
const row = document.createElement(‘tr’);
if (result.numDice === optimal.numDice) {
row.className = ‘optimal-row’;
}
row.innerHTML = `
${result.numDice} |
${(result.successRate * 100).toFixed(2)}% |
${(result.autoFailRate * 100).toFixed(2)}% |
${result.averageTotal.toFixed(2)} |
`;
resultsBody.appendChild(row);
});
resultsContainer.style.display = ‘block’;
}
});