Homegrown Oathsworn Tool

Oathsworn Optimal Dice Calculator

Difficulty (Target Sum)
Reroll Tokens Available
Calculate Optimal Dice

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’; } });

Leave a comment