@randsum/dice
Version:
A flexible, type-safe dice roller for tabletop RPGs, game development, and probability simulations
200 lines (199 loc) • 6.92 kB
JavaScript
import { CapModifier, DropModifier, ExplodeModifier, ReplaceModifier, RerollModifier, UniqueModifier } from '@randsum/core';
import { isNumericRollOptions } from '../guards/isNumericRollOptions';
import { calculateTotal } from './calculateTotal';
import { coreRandom } from './coreRandom';
import { coreSpreadRolls } from './coreSpreadRolls';
export function rollResultFromDicePools(dicePools) {
const rawRolls = generateRawRolls(dicePools.dicePools);
const modifiedRolls = generateModifiedRolls(dicePools, rawRolls);
const modifiedValues = Object.values(modifiedRolls);
return {
...dicePools,
rawRolls,
modifiedRolls,
rawResult: Object.values(rawRolls).flat(),
result: modifiedValues.map((pool) => pool.rolls).flat(),
type: calculateDicePoolType(dicePools.dicePools),
total: calculateTotal(modifiedValues.map((pool) => pool.total))
};
}
function calculateDicePoolType(dicePools) {
const pools = Object.values(dicePools);
if (pools.every((pool) => typeof pool.options.sides === 'number')) {
return 'numerical';
}
if (pools.every((pool) => Array.isArray(pool.options.sides))) {
return 'custom';
}
return 'mixed';
}
function isCustomParameters(poolParameters) {
return Array.isArray(poolParameters.options.sides);
}
/**
* Apply a modifier to the current roll bonuses
*
* This optimized implementation avoids unnecessary object creation and function calls
* by directly applying modifiers based on their type.
*
* @param key - The modifier key to apply
* @param modifiers - All available modifiers
* @param currentBonuses - The current roll bonuses to modify
* @param rollParams - Parameters for the roll
* @returns Modified roll bonuses
*/
function applyModifier(key, modifiers, currentBonuses, rollParams) {
const modifierValue = modifiers[key];
if (modifierValue === undefined) {
return currentBonuses;
}
switch (key) {
case 'plus':
return {
...currentBonuses,
simpleMathModifier: modifierValue
};
case 'minus':
return {
...currentBonuses,
simpleMathModifier: -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)}`);
}
}
/**
* Generate modified roll results by applying modifiers to raw rolls
*
* This optimized implementation reduces unnecessary object creation and improves
* performance by optimizing the modifier application order and using more efficient
* data structures.
*
* @param dicePools - The dice pools to process
* @param rawRolls - The raw roll results
* @returns Modified roll results
*/
function generateModifiedRolls(dicePools, rawRolls) {
const result = {};
for (const [key, params] of Object.entries(dicePools.dicePools)) {
const rolls = rawRolls[key] ?? [];
if (isCustomParameters(params)) {
result[key] = {
total: calculateTotal(rolls),
rolls
};
continue;
}
const { sides, quantity = 1, modifiers = {} } = params.options;
if (Object.keys(modifiers).length === 0) {
result[key] = {
total: calculateTotal(rolls),
rolls: rolls
};
continue;
}
const rollOne = () => coreRandom(sides);
const initialBonuses = {
simpleMathModifier: 0,
rolls: rolls
};
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
});
}
result[key] = {
rolls: bonuses.rolls,
total: calculateTotal(bonuses.rolls, bonuses.simpleMathModifier)
};
}
return result;
}
/**
* Generate raw roll results for all dice pools
*
* This optimized implementation reduces unnecessary object creation and improves
* performance by using direct object property assignment instead of creating
* intermediate arrays.
*
* @param dicePools - The dice pools to roll
* @returns Raw roll results
*/
function generateRawRolls(dicePools) {
const result = {};
for (const [key, pool] of Object.entries(dicePools)) {
const { options } = pool;
const quantity = options.quantity ?? 1;
if (isNumericRollOptions(options)) {
result[key] = coreSpreadRolls(quantity, options.sides);
}
else {
result[key] = coreSpreadRolls(quantity, options.sides.length, options.sides);
}
}
return result;
}