UNPKG

@randsum/dice

Version:

A flexible, type-safe dice roller for tabletop RPGs, game development, and probability simulations

167 lines (166 loc) 5.53 kB
import { isNumericRollOptions } from '@randsum/core'; import { CapModifier, DropModifier, ExplodeModifier, ReplaceModifier, RerollModifier, UniqueModifier } from '@randsum/core'; import { coreSpreadRolls } from './coreSpreadRolls'; import { isCustomRollParams } from '../guards/isCustomRollParams'; import { calculateTotal } from './calculateTotal'; import { coreRandom } from './coreRandom'; import { isNumericRollParams } from '../guards/isNumericRollParams'; function generateRoll(parameters) { const rawRolls = generateRawRolls(parameters); const modifiedRolls = generateModifiedRolls(parameters, rawRolls); const rawResult = calculateTotal(rawRolls); if (rawRolls.every((n) => typeof n === 'number') && modifiedRolls.rolls.every((n) => typeof n === 'number') && typeof rawResult === 'number' && isNumericRollParams(parameters)) { return { parameters, rawResult, rawRolls, modifiedRolls, total: modifiedRolls.total, type: 'numeric' }; } if (rawRolls.every((n) => typeof n === 'string') && modifiedRolls.rolls.every((n) => typeof n === 'string') && typeof rawResult === 'string' && isCustomRollParams(parameters)) { return { parameters, rawResult, rawRolls, modifiedRolls, total: calculateTotal(modifiedRolls.rolls), type: 'custom' }; } throw new Error('Mixed rolls are not supported yet'); } function generateRawRolls({ options }) { const quantity = options.quantity ?? 1; if (isNumericRollOptions(options)) { return coreSpreadRolls(quantity, options.sides); } else { return coreSpreadRolls(quantity, options.sides.length, options.sides); } } export { generateRoll }; function generateModifiedRolls(parameters, rolls) { if (isCustomRollParams(parameters) && rolls.every((n) => typeof n === 'string')) { return { total: calculateTotal(rolls), rolls }; } if (!rolls.every((n) => typeof n === 'number')) { throw new Error('Mixed rolls are not supported yet'); } const { sides, quantity = 1, modifiers = {} } = parameters.options; if (Object.keys(modifiers).length === 0) { return { total: calculateTotal(rolls), rolls: rolls.map((n) => Number(n)) }; } const rollOne = () => coreRandom(sides); const initialBonuses = { simpleMathModifier: 0, rolls: rolls.map((n) => Number(n)) }; let bonuses = initialBonuses; if (modifiers.reroll) { bonuses = applyModifier('reroll', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.replace) { bonuses = applyModifier('replace', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.cap) { bonuses = applyModifier('cap', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.explode) { bonuses = applyModifier('explode', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.unique) { bonuses = applyModifier('unique', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.drop) { bonuses = applyModifier('drop', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.plus) { bonuses = applyModifier('plus', modifiers, bonuses, { sides, quantity, rollOne }); } if (modifiers.minus) { bonuses = applyModifier('minus', modifiers, bonuses, { sides, quantity, rollOne }); } return { rolls: bonuses.rolls, total: calculateTotal(bonuses.rolls, bonuses.simpleMathModifier) }; } function applyModifier(key, modifiers, currentBonuses, rollParams) { const modifierValue = modifiers[key]; if (modifierValue === undefined) { return currentBonuses; } switch (key) { case 'plus': return { ...currentBonuses, simpleMathModifier: Number(modifierValue) }; case 'minus': return { ...currentBonuses, simpleMathModifier: -Number(modifierValue) }; case 'reroll': return new RerollModifier(modifiers.reroll).apply(currentBonuses, undefined, rollParams.rollOne); case 'unique': return new UniqueModifier(modifiers.unique).apply(currentBonuses, { sides: rollParams.sides, quantity: rollParams.quantity }, rollParams.rollOne); case 'replace': return new ReplaceModifier(modifiers.replace).apply(currentBonuses); case 'cap': return new CapModifier(modifiers.cap).apply(currentBonuses); case 'drop': return new DropModifier(modifiers.drop).apply(currentBonuses); case 'explode': return new ExplodeModifier(modifiers.explode).apply(currentBonuses, { sides: rollParams.sides, quantity: rollParams.quantity }, rollParams.rollOne); default: throw new Error(`Unknown modifier: ${String(key)}`); } }