@randsum/dice
Version:
A flexible, type-safe dice roller for tabletop RPGs, game development, and probability simulations
167 lines (166 loc) • 5.53 kB
JavaScript
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)}`);
}
}