diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index c5e6cb88..3e0b61af 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -29,14 +29,14 @@ const { roll } = require('randsum') ```ts import { roll } from 'randsum' -const result = roll() +const result = roll(20) console.log(result.total) // a random number between 1 and 20 ``` ### The Roll Result -`roll()` returns a `RandsumRollResult` object. This has plenty of helpful keys, but the big ones are `total` and `result`. +`roll(arg)` returns a `RandsumRollResult` object. This has plenty of helpful keys, but the big ones are `total` and `result`. `total` returns the combined total of all your rolls, whereas `result` is an `Array` of `Array`s, each one representing the _set_ of different roll results for each pool of dice you rolled. @@ -67,7 +67,7 @@ roll('4d20H+2') // Roll 4 20 sided die, drop highest, add 2 --- -You can pass in a `RandsumRollOptions` as the first argument. While rolling standard numerical die, `sides` is the only required value, representing the number of distinct sides of the die. +You can pass in a `RandsumRollOptions` as the first argument. While rolling numerical die, `sides` is the only required value, representing the number of distinct sides of the die. ```ts roll({ sides: 20 }) // Roll a single 20 sided die @@ -94,15 +94,11 @@ roll({ `roll` accepts an array of options in varying different formats! ```ts -roll([ - 20, - '2d4', - { +roll(20, '2d4', { sides: 20, quantity: 4, modifiers: { drop: { highest: true } }, { plus: 2 } - } - ]) + }) // Roll 1 d20, 2 d4, and 4 d20 (dropping the highest and adding 2) ``` diff --git a/RANDSUM_DICE_NOTATION.md b/RANDSUM_DICE_NOTATION.md index 51dbbe1f..38446c11 100644 --- a/RANDSUM_DICE_NOTATION.md +++ b/RANDSUM_DICE_NOTATION.md @@ -31,7 +31,6 @@ In `randsum` : ```js // Roll 1 twenty-sided die -roll() roll(20) roll('1d20') roll({ @@ -87,6 +86,8 @@ roll({ }) ``` +Note: When using custom sides with Randsum Dice Notation, we can only mark sides as single characters. When using full options, you can pass in strings of any length! + #### Custom Sides Caveats and Gotchas - Whenever _any_ dice pool leverages custom dice, the `total` of the `RandsumRollResult` will be `0`. diff --git a/src/D.ts b/src/D.ts index 4ab75fae..9df9820f 100644 --- a/src/D.ts +++ b/src/D.ts @@ -1,7 +1,7 @@ -import { isCustomSides } from '~guards' +import { isCustomSidesArg } from '~guards' import { DicePoolType } from '~types' -type Type = T extends string[] ? DicePoolType.custom : DicePoolType.standard +type Type = T extends string[] ? DicePoolType.custom : DicePoolType.numerical type Faces = T extends string[] ? T : number[] type Result = F extends number[] ? number : string @@ -11,14 +11,14 @@ class D { type: Type constructor(sides: Sides) { - if (isCustomSides(sides)) { + if (isCustomSidesArg(sides)) { this.sides = sides.length this.type = DicePoolType.custom as Type this.faces = sides as Faces return } this.sides = sides - this.type = DicePoolType.standard as Type + this.type = DicePoolType.numerical as Type this.faces = Array.from( { length: Number(sides) }, (_, index) => index + 1 @@ -26,10 +26,10 @@ class D { } roll(): Result> { - return this.faces[this.rawRoll()] as Result> + return this.faces[this._rawRoll()] as Result> } - protected rawRoll(): number { + protected _rawRoll(): number { return Math.floor(Math.random() * Number(this.sides)) } } diff --git a/src/guards.ts b/src/guards.ts index 6e6cdd3b..41c97666 100644 --- a/src/guards.ts +++ b/src/guards.ts @@ -4,10 +4,14 @@ import { RandsumRollOptions, RandsumRollParameters, Modifiers, - RandsumRollArgument + RandsumRollArgument, + DicePoolType } from '~types' +import { D } from './D' -export function isDiceNotation(argument: unknown): argument is RandsumNotation { +export function isDiceNotationArg( + argument: unknown +): argument is RandsumNotation { const notAString = typeof argument !== 'string' const basicTest = !!coreNotationPattern.test(String(argument)) if (!basicTest || notAString) return false @@ -17,8 +21,8 @@ export function isDiceNotation(argument: unknown): argument is RandsumNotation { return cleanArg.replace(completeRollPattern, '').length === 0 } -export function isCustomSides( - argument: RandsumRollArgument | undefined +export function isCustomSidesArg( + argument: RandsumRollArgument ): argument is string[] { return ( Array.isArray(argument) && argument.every((arg) => typeof arg === 'string') @@ -49,3 +53,11 @@ export function isCustomParameters( export function isFullNumArray(arr: unknown[]): arr is number[] { return arr.every((item) => typeof item === 'number') } + +export function isD(arg: unknown): arg is D { + return arg instanceof D +} + +export function isCustomSidesD(arg: D): arg is D { + return arg.type === DicePoolType.custom +} diff --git a/src/utils/parseNotation/index.ts b/src/models/NotationModel/index.ts similarity index 79% rename from src/utils/parseNotation/index.ts rename to src/models/NotationModel/index.ts index 7cec1bbc..2e887aa6 100644 --- a/src/utils/parseNotation/index.ts +++ b/src/models/NotationModel/index.ts @@ -1,8 +1,8 @@ import { coreNotationPattern } from '~patterns' import { RandsumNotation, RandsumRollOptions } from '~types' -import { parseCoreNotation, parseModifiers } from './parseModifiers' +import { parseCoreNotation, parseModifiers } from './optionsParsers' -function parseNotation( +function toOptions( notationString: RandsumNotation ): RandsumRollOptions { const coreNotationMatch = notationString.match(coreNotationPattern)!.at(0) @@ -14,4 +14,4 @@ function parseNotation( } } -export { parseNotation } +export default { toOptions } diff --git a/src/utils/parseNotation/parseModifiers.ts b/src/models/NotationModel/optionsParsers.ts similarity index 100% rename from src/utils/parseNotation/parseModifiers.ts rename to src/models/NotationModel/optionsParsers.ts diff --git a/src/models/OptionsModel/index.ts b/src/models/OptionsModel/index.ts new file mode 100644 index 00000000..a1e7a8b6 --- /dev/null +++ b/src/models/OptionsModel/index.ts @@ -0,0 +1,22 @@ +import { RandsumNotation, RandsumRollOptions } from '~types' +import { + formatCoreNotation, + formatModifierNotation +} from './notationFormatters' +import { + formatCoreDescriptions, + formatModifierDescriptions +} from './stringFormatters' + +function toNotation(options: RandsumRollOptions): RandsumNotation { + return `${formatCoreNotation(options)}${formatModifierNotation(options)}` as RandsumNotation +} + +function toDescription(options: RandsumRollOptions) { + return [ + formatCoreDescriptions(options), + ...formatModifierDescriptions(options) + ] +} + +export default { toNotation, toDescription } diff --git a/src/utils/formatNotation/notationFormatters.ts b/src/models/OptionsModel/notationFormatters.ts similarity index 66% rename from src/utils/formatNotation/notationFormatters.ts rename to src/models/OptionsModel/notationFormatters.ts index 51db3f5d..db93e0d0 100644 --- a/src/utils/formatNotation/notationFormatters.ts +++ b/src/models/OptionsModel/notationFormatters.ts @@ -1,6 +1,9 @@ +import { isValidModifier } from '~guards' import { DropOptions, GreaterLessOptions, + RandsumNotation, + RandsumRollOptions, ReplaceOptions, RerollOptions, UniqueOptions @@ -100,3 +103,33 @@ function singleReplaceNotation(replace: ReplaceOptions) { : formatGreaterLess(replace.from).join(',') return `${fromValue}=${replace.to}` } + +export function formatModifierNotation({ + modifiers +}: RandsumRollOptions): string { + if (!isValidModifier(modifiers)) return '' + + const modifierStrings = [] + + if (modifiers.cap) modifierStrings.push(capNotation(modifiers.cap)) + if (modifiers.drop) modifierStrings.push(dropNotation(modifiers.drop)) + if (modifiers.replace) + modifierStrings.push(replaceNotation(modifiers.replace)) + if (modifiers.reroll) modifierStrings.push(rerollNotation(modifiers.reroll)) + if (modifiers.explode) modifierStrings.push(explodeNotation()) + if (modifiers.unique) modifierStrings.push(uniqueNotation(modifiers.unique)) + if (modifiers.plus) modifierStrings.push(plusNotation(modifiers.plus)) + if (modifiers.minus) modifierStrings.push(minusNotation(modifiers.minus)) + + return modifierStrings.join('') +} + +export function formatCoreNotation({ + quantity = 1, + sides +}: RandsumRollOptions): RandsumNotation { + const formattedSides = Array.isArray(sides) + ? `{${sides.map((s) => (s === '' ? ' ' : s)).join('')}}` + : sides + return `${quantity}d${formattedSides}` as RandsumNotation +} diff --git a/src/utils/formatDescription/stringFormatters.ts b/src/models/OptionsModel/stringFormatters.ts similarity index 65% rename from src/utils/formatDescription/stringFormatters.ts rename to src/models/OptionsModel/stringFormatters.ts index 03f9cbaa..96c854d5 100644 --- a/src/utils/formatDescription/stringFormatters.ts +++ b/src/models/OptionsModel/stringFormatters.ts @@ -1,9 +1,11 @@ +import { isValidModifier } from '~guards' import { GreaterLessOptions, DropOptions, ReplaceOptions, RerollOptions, - UniqueOptions + UniqueOptions, + RandsumRollOptions } from '~types' function formatHumanList(list: (string | number)[]) { @@ -105,3 +107,42 @@ function extractFromValue(from: number | GreaterLessOptions) { function singleReplaceString(replace: ReplaceOptions) { return `Replace ${extractFromValue(replace.from)} with [${replace.to}]` } + +export function formatCoreDescriptions({ + sides, + quantity +}: RandsumRollOptions) { + const base = `Roll ${quantity}` + const descriptor = (quantity || 1) > 1 ? 'dice' : 'die' + if (Array.isArray(sides)) { + const formattedSides = `${descriptor} with the following sides: (${sides + .map((s) => (s === '' ? ' ' : s)) + .join(',')})` + return `${base} ${formattedSides}` + } + + return `${base} ${sides}-sided ${descriptor}` +} + +export function formatModifierDescriptions({ + modifiers +}: RandsumRollOptions): string[] { + if (!isValidModifier(modifiers)) return [] + + const modifierStrings = [] + + if (modifiers.cap) + capString(modifiers.cap).forEach((str) => modifierStrings.push(str)) + if (modifiers.drop) + dropString(modifiers.drop).forEach((str) => modifierStrings.push(str)) + if (modifiers.replace) + replaceString(modifiers.replace).forEach((str) => modifierStrings.push(str)) + if (modifiers.reroll) + rerollString(modifiers.reroll).forEach((str) => modifierStrings.push(str)) + if (modifiers.explode) modifierStrings.push(explodeString()) + if (modifiers.unique) modifierStrings.push(uniqueString(modifiers.unique)) + if (modifiers.plus) modifierStrings.push(plusString(modifiers.plus)) + if (modifiers.minus) modifierStrings.push(minusString(modifiers.minus)) + + return modifierStrings +} diff --git a/src/parameterizeRollArgument/index.ts b/src/parameterizeRollArgument/index.ts index 1ca0a363..d125d97b 100644 --- a/src/parameterizeRollArgument/index.ts +++ b/src/parameterizeRollArgument/index.ts @@ -1,20 +1,19 @@ -import { CoreRollArgument, RandsumRollParameters } from '~types' +import { RandsumRollArgument, RandsumRollParameters } from '~types' import { D } from '~src/D' -import { formatDescription } from '~utils/formatDescription' -import { formatNotation } from '~utils/formatNotation' -import { parseDiceOptions } from './parseDiceOptions' +import { normalizeArgument } from './normalizeArgument' +import OptionsModel from '~models/OptionsModel' function parameterizeRollArgument( - argument: CoreRollArgument | undefined + argument: RandsumRollArgument ): RandsumRollParameters { - const options = parseDiceOptions(argument) + const options = normalizeArgument(argument) const die = new D(options.sides) return { options, argument, die, - notation: formatNotation(options), - description: formatDescription(options) + notation: OptionsModel.toNotation(options), + description: OptionsModel.toDescription(options) } } diff --git a/src/parameterizeRollArgument/normalizeArgument.ts b/src/parameterizeRollArgument/normalizeArgument.ts new file mode 100644 index 00000000..3d1ca4dd --- /dev/null +++ b/src/parameterizeRollArgument/normalizeArgument.ts @@ -0,0 +1,39 @@ +import { + isDicePoolOptions, + isDiceNotationArg, + isD, + isCustomSidesD +} from '~guards' +import { RandsumRollArgument, RandsumRollOptions } from '~types' +import NotationModel from '~models/NotationModel' + +function normalizeArgument(argument: RandsumRollArgument): RandsumRollOptions { + if (isD(argument)) { + return { + quantity: 1, + sides: isCustomSidesD(argument) ? argument.faces : argument.sides + } + } + + if (isDicePoolOptions(argument)) { + return argument + } + + if (isDiceNotationArg(argument)) { + return NotationModel.toOptions(argument) + } + + if (Array.isArray(argument)) { + return { + quantity: 1, + sides: argument.map(String) + } + } + + return { + quantity: 1, + sides: Number(argument || 20) + } +} + +export { normalizeArgument } diff --git a/src/parameterizeRollArgument/parseDiceOptions.ts b/src/parameterizeRollArgument/parseDiceOptions.ts deleted file mode 100644 index 171d17be..00000000 --- a/src/parameterizeRollArgument/parseDiceOptions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { isDicePoolOptions, isDiceNotation } from '~guards' -import { CoreRollArgument, RandsumRollOptions } from '~types' -import { parseNotation } from '~utils/parseNotation' - -function parseDiceOptions( - argument: CoreRollArgument | undefined -): RandsumRollOptions { - if (isDicePoolOptions(argument)) { - return argument - } - - if (isDiceNotation(argument)) { - return parseNotation(argument) - } - - const defaultQuantity = 1 - - if (Array.isArray(argument)) { - return { - quantity: defaultQuantity, - sides: argument.map(String) - } - } - - return { - quantity: defaultQuantity, - sides: Number(argument || 20) - } -} - -export { parseDiceOptions } diff --git a/src/roll/formDicePools.ts b/src/roll/formDicePools.ts index 612decad..9192d2c5 100644 --- a/src/roll/formDicePools.ts +++ b/src/roll/formDicePools.ts @@ -1,34 +1,14 @@ -import { CoreRollArgument, RandsumRollArgument, DicePools } from '~types' -import { isCustomSides } from '~guards' +import { RandsumRollArgument, DicePools } from '~types' import { uuidv7 as uuid } from 'uuidv7' import { parameterizeRollArgument } from '~src/parameterizeRollArgument' -function formDicePools(argument: RandsumRollArgument): DicePools { - const arrayArgs = argsToArray(argument) +function formDicePools(args: RandsumRollArgument[]): DicePools { return { - dicePools: arrayArgs.reduce( + dicePools: args.reduce( (acc, arg) => ({ ...acc, [uuid()]: parameterizeRollArgument(arg) }), {} ) } } -function argsToArray( - argument: RandsumRollArgument | undefined -): CoreRollArgument[] | undefined[] { - if (!argument) { - return [undefined] - } - - if (isCustomSides(argument)) { - return [argument] - } - - if (Array.isArray(argument)) { - return argument - } - - return [argument].flat() -} - export { formDicePools } diff --git a/src/roll/generateRollResult/index.ts b/src/roll/generateRollResult/index.ts index 0f3d25bc..e644cfa4 100644 --- a/src/roll/generateRollResult/index.ts +++ b/src/roll/generateRollResult/index.ts @@ -13,7 +13,7 @@ function calculateType(dicePools: DicePools['dicePools']): DicePoolType { case Object.values(dicePools).every( (pool) => typeof pool.options.sides === 'number' ): - return DicePoolType.standard + return DicePoolType.numerical case Object.values(dicePools).every((pool) => Array.isArray(pool.options.sides) diff --git a/src/roll/index.ts b/src/roll/index.ts index ceaa718e..61ab8643 100644 --- a/src/roll/index.ts +++ b/src/roll/index.ts @@ -2,8 +2,8 @@ import { RandsumRollArgument, RandsumRollResult } from '~types' import { generateRollResult } from './generateRollResult' import { formDicePools } from './formDicePools' -function roll(arg?: RandsumRollArgument): RandsumRollResult { - const dicePools = formDicePools(arg) +function roll(...args: RandsumRollArgument[]): RandsumRollResult { + const dicePools = formDicePools(args) return generateRollResult(dicePools) } diff --git a/src/types.ts b/src/types.ts index 7d05fcd6..494f1a6f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ export type RandsumNotation = : DiceNotationWithCustomSides export enum DicePoolType { - standard = 'standard', + numerical = 'numerical', custom = 'custom', mixed = 'mixed' } @@ -72,17 +72,13 @@ export type RequiredCoreDiceParameters< // Arguments -export type CoreRollArgument = - | string +export type RandsumRollArgument = + | `${number}` | number + | D | RandsumRollOptions | RandsumNotation - | (number | string)[] - -export type RandsumRollArgument = - | CoreRollArgument - | CoreRollArgument[] - | undefined + | string[] // Parameters @@ -95,6 +91,7 @@ export interface RandsumRollParameters< notation: RandsumNotation description: string[] } + export interface DicePools { dicePools: { [key: string]: RandsumRollParameters @@ -121,7 +118,7 @@ export interface RandsumRollResult extends DicePools { export interface RandsumNotationValidationResult { valid: boolean - type?: DicePoolType.custom | DicePoolType.standard + type?: DicePoolType.custom | DicePoolType.numerical digested?: RandsumRollOptions notation?: RandsumNotation description: string[] diff --git a/src/utils/formatDescription/index.ts b/src/utils/formatDescription/index.ts deleted file mode 100644 index f4378eb3..00000000 --- a/src/utils/formatDescription/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { RandsumRollOptions } from '~types' -import { - rerollString, - explodeString, - uniqueString, - plusString, - minusString, - replaceString, - dropString, - capString -} from './stringFormatters' -import { isValidModifier } from '~guards' - -function formatModifierDescriptions({ - modifiers -}: RandsumRollOptions): string[] { - if (!isValidModifier(modifiers)) return [] - - const modifierStrings = [] - - if (modifiers.cap) - capString(modifiers.cap).forEach((str) => modifierStrings.push(str)) - if (modifiers.drop) - dropString(modifiers.drop).forEach((str) => modifierStrings.push(str)) - if (modifiers.replace) - replaceString(modifiers.replace).forEach((str) => modifierStrings.push(str)) - if (modifiers.reroll) - rerollString(modifiers.reroll).forEach((str) => modifierStrings.push(str)) - if (modifiers.explode) modifierStrings.push(explodeString()) - if (modifiers.unique) modifierStrings.push(uniqueString(modifiers.unique)) - if (modifiers.plus) modifierStrings.push(plusString(modifiers.plus)) - if (modifiers.minus) modifierStrings.push(minusString(modifiers.minus)) - - return modifierStrings -} - -function formatCoreDescriptions({ - sides, - quantity -}: RandsumRollOptions) { - const base = `Roll ${quantity}` - const descriptor = (quantity || 1) > 1 ? 'dice' : 'die' - if (Array.isArray(sides)) { - const formattedSides = `${descriptor} with the following sides: (${sides - .map((s) => (s === '' ? ' ' : s)) - .join(',')})` - return `${base} ${formattedSides}` - } - - return `${base} ${sides}-sided ${descriptor}` -} - -function formatDescription(options: RandsumRollOptions) { - return [ - formatCoreDescriptions(options), - ...formatModifierDescriptions(options) - ] -} - -export { formatDescription } diff --git a/src/utils/formatNotation/index.ts b/src/utils/formatNotation/index.ts deleted file mode 100644 index 91669063..00000000 --- a/src/utils/formatNotation/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { RandsumNotation, RandsumRollOptions } from '~types' -import { - capNotation, - dropNotation, - explodeNotation, - minusNotation, - plusNotation, - replaceNotation, - rerollNotation, - uniqueNotation -} from './notationFormatters' -import { isValidModifier } from '~guards' - -function formatModifierNotation({ modifiers }: RandsumRollOptions): string { - if (!isValidModifier(modifiers)) return '' - - const modifierStrings = [] - - if (modifiers.cap) modifierStrings.push(capNotation(modifiers.cap)) - if (modifiers.drop) modifierStrings.push(dropNotation(modifiers.drop)) - if (modifiers.replace) - modifierStrings.push(replaceNotation(modifiers.replace)) - if (modifiers.reroll) modifierStrings.push(rerollNotation(modifiers.reroll)) - if (modifiers.explode) modifierStrings.push(explodeNotation()) - if (modifiers.unique) modifierStrings.push(uniqueNotation(modifiers.unique)) - if (modifiers.plus) modifierStrings.push(plusNotation(modifiers.plus)) - if (modifiers.minus) modifierStrings.push(minusNotation(modifiers.minus)) - - return modifierStrings.join('') -} - -function formatCoreNotation({ - quantity = 1, - sides -}: RandsumRollOptions): RandsumNotation { - const formattedSides = Array.isArray(sides) - ? `{${sides.map((s) => (s === '' ? ' ' : s)).join('')}}` - : sides - return `${quantity}d${formattedSides}` as RandsumNotation -} - -function formatNotation(options: RandsumRollOptions): RandsumNotation { - return `${formatCoreNotation(options)}${formatModifierNotation(options)}` as RandsumNotation -} - -export { formatNotation } diff --git a/src/validateDiceNotation/index.ts b/src/validateDiceNotation/index.ts index 53215ede..a28bebbf 100644 --- a/src/validateDiceNotation/index.ts +++ b/src/validateDiceNotation/index.ts @@ -1,32 +1,28 @@ import { DicePoolType, RandsumNotationValidationResult } from '~types' -import { isDiceNotation } from '~guards' -import { formatNotation } from '~utils/formatNotation' -import { parseNotation } from '~utils/parseNotation' -import { formatDescription } from '~utils/formatDescription' +import { isCustomSidesArg, isDiceNotationArg } from '~guards' +import NotationModel from '~models/NotationModel' +import OptionsModel from '~models/OptionsModel' function validateDiceNotation( notation: string ): RandsumNotationValidationResult { - if (!isDiceNotation(notation)) { + if (!isDiceNotationArg(notation)) { return { valid: false, description: [] } } - const digested = parseNotation(notation) - const description = formatDescription(digested) - const parsedNotation = formatNotation(digested) - const type = Array.isArray(digested.sides) - ? DicePoolType.custom - : DicePoolType.standard + const digested = NotationModel.toOptions(notation) return { valid: true, - notation: parsedNotation, - type, digested, - description + notation: OptionsModel.toNotation(digested), + type: isCustomSidesArg(digested.sides) + ? DicePoolType.custom + : DicePoolType.numerical, + description: OptionsModel.toDescription(digested) } } diff --git a/tests/formDicePools.test.ts b/tests/formDicePools.test.ts index 2219295e..50bc08fb 100644 --- a/tests/formDicePools.test.ts +++ b/tests/formDicePools.test.ts @@ -10,14 +10,14 @@ const extractDicePoolValues = (params: DicePools): RandsumRollParameters[] => { describe('formDicePools', () => { describe('given a single argument', () => { - const argument = undefined + const argument = 20 test('returns DicePools with one key', () => { - const params = formDicePools(argument) + const params = formDicePools([argument]) const dicePools = extractDicePoolValues(params) expect(dicePools.length).toBe(1) - expect(dicePools[0].argument).toBeUndefined() + expect(dicePools[0].argument).toBe(argument) }) }) diff --git a/tests/generateResult.test.ts b/tests/generateResult.test.ts index cf7026c5..6414bb66 100644 --- a/tests/generateResult.test.ts +++ b/tests/generateResult.test.ts @@ -24,7 +24,7 @@ describe('generateRollResult', () => { const coreParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: '6d4', options: { sides: 6, quantity: testRollSet.length }, die: mockStandardDie, notation: '6d4', @@ -41,7 +41,7 @@ describe('generateRollResult', () => { expect(generateRollResult(coreParameters)).toMatchObject({ ...coreParameters, rawRolls: coreRawRolls, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 10, result: [[1, 2, 3, 4]], rawResult: [[1, 2, 3, 4]] @@ -56,7 +56,7 @@ describe('generateRollResult', () => { dicePools: { 'test-roll-id': { die: mockStandardDie, - argument: undefined, + argument: 1, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -83,7 +83,7 @@ describe('generateRollResult', () => { total: 206 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 206, result: [[1, 200, 2, 3]], rawResult: [[1, 1, 2, 3]] @@ -95,10 +95,10 @@ describe('generateRollResult', () => { dicePools: { 'test-roll-id': { die: mockStandardDie, - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], - type: DicePoolType.standard, + type: DicePoolType.numerical, options: { sides: 4, quantity: uniqueRolls.length, @@ -125,7 +125,7 @@ describe('generateRollResult', () => { total: 7 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 7, result: [[1, 1, 2, 3]] }) @@ -141,8 +141,8 @@ describe('generateRollResult', () => { die: mockStandardDie, notation: '1d1' as RandsumNotation, description: ['foo'], - argument: undefined, - type: DicePoolType.standard, + argument: 20, + type: DicePoolType.numerical, options: { sides: 6, quantity: overflowRollTotals.length, @@ -177,7 +177,7 @@ describe('generateRollResult', () => { die: mockCustomSidesDie, notation: '1d1' as RandsumNotation<'string'>, description: ['foo'], - argument: undefined, + argument: 20, options: { sides: faces, quantity: 4 @@ -216,7 +216,7 @@ describe('generateRollResult', () => { 'test-roll-id': { notation: '1d1' as RandsumNotation, description: ['foo'], - argument: undefined, + argument: 20, die: mockStandardDie, options: { sides: 10, @@ -250,7 +250,7 @@ describe('generateRollResult', () => { total: 17 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 17, result: [[4, 6, 7]] }) @@ -262,7 +262,7 @@ describe('generateRollResult', () => { const dropParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], die: mockStandardDie, @@ -283,7 +283,7 @@ describe('generateRollResult', () => { expect(generateRollResult(dropParameters)).toMatchObject({ ...dropParameters, rawRolls: coreRawRolls, - type: DicePoolType.standard, + type: DicePoolType.numerical, modifiedRolls: { 'test-roll-id': { rolls: [2, 2, 3, 4], @@ -300,7 +300,7 @@ describe('generateRollResult', () => { const dropParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], die: mockStandardDie, @@ -325,7 +325,7 @@ describe('generateRollResult', () => { expect(generateRollResult(dropParameters)).toMatchObject({ ...dropParameters, - type: DicePoolType.standard, + type: DicePoolType.numerical, rawRolls: coreRawRolls, modifiedRolls: { 'test-roll-id': { @@ -346,7 +346,7 @@ describe('generateRollResult', () => { const explodeParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], die: mockStandardDie, @@ -368,7 +368,7 @@ describe('generateRollResult', () => { expect(generateRollResult(explodeParameters)).toMatchObject({ ...explodeParameters, rawRolls, - type: DicePoolType.standard, + type: DicePoolType.numerical, modifiedRolls: { 'test-roll-id': { rolls: [1, 2, 3, 6, 200], @@ -386,7 +386,7 @@ describe('generateRollResult', () => { const reDicePools: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -412,7 +412,7 @@ describe('generateRollResult', () => { total: 206 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 206, result: [[1, 2, 3, 200]] }) @@ -423,7 +423,7 @@ describe('generateRollResult', () => { const reDicePools: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -452,7 +452,7 @@ describe('generateRollResult', () => { } }, total: 404, - type: DicePoolType.standard, + type: DicePoolType.numerical, result: [[1, 200, 3, 200]] }) }) @@ -462,7 +462,7 @@ describe('generateRollResult', () => { const reDicePools: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -490,7 +490,7 @@ describe('generateRollResult', () => { total: 406 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 406, result: [[200, 2, 200, 4]] }) @@ -504,7 +504,7 @@ describe('generateRollResult', () => { 'test-roll-id': { notation: '1d1' as RandsumNotation, description: ['foo'], - argument: undefined, + argument: 20, options: { sides: 6, quantity: testRollSet.length, @@ -528,7 +528,7 @@ describe('generateRollResult', () => { total: 10 } }, - type: DicePoolType.standard, + type: DicePoolType.numerical, total: 10, result: [[2, 2, 3, 3]] }) @@ -539,7 +539,7 @@ describe('generateRollResult', () => { const dropParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -575,7 +575,7 @@ describe('generateRollResult', () => { const dropParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, notation: '1d1' as RandsumNotation, description: ['foo'], options: { @@ -613,14 +613,14 @@ describe('generateRollResult', () => { 'test-roll-id': { notation: '1d1' as RandsumNotation, description: ['foo'], - argument: undefined, + argument: 20, options: { sides: 6, quantity: testRollSet.length }, die: mockStandardDie }, 'test-roll-id-2': { notation: '1d1' as RandsumNotation, description: ['foo'], - argument: undefined, + argument: 20, options: { sides: 6, quantity: testRollSet.length }, die: mockStandardDie } @@ -660,7 +660,7 @@ describe('generateRollResult', () => { const dropParameters: DicePools = { dicePools: { 'test-roll-id': { - argument: undefined, + argument: 20, options: { sides: 6, quantity: testRollSet.length, diff --git a/tests/parameterizeRollArgument.test.ts b/tests/parameterizeRollArgument.test.ts index eda4c9b6..db6720d4 100644 --- a/tests/parameterizeRollArgument.test.ts +++ b/tests/parameterizeRollArgument.test.ts @@ -5,22 +5,6 @@ import { parameterizeRollArgument } from '~src/parameterizeRollArgument' import { RandsumNotation } from '~types' describe('parameterizeRollArgument', () => { - describe('given undefined', () => { - const argument = undefined - - test('returns a RollParameter matching the argument', () => { - const params = parameterizeRollArgument(argument) - - expect(params).toMatchObject({ - argument, - options: { quantity: 1, sides: 20 }, - die: new D(20), - notation: '1d20', - description: ['Roll 1 20-sided die'] - }) - }) - }) - describe('given a number', () => { const argument = 2 @@ -173,6 +157,46 @@ describe('parameterizeRollArgument', () => { }) }) + describe('Given a D() object', () => { + describe('simple', () => { + const argument = new D(6) + + test('returns a RollParameter matching the argument', () => { + const params = parameterizeRollArgument(argument) + + expect(params).toMatchObject({ + argument, + options: { + sides: argument.sides, + quantity: 1 + }, + die: argument, + notation: '1d6', + description: ['Roll 1 6-sided die'] + }) + }) + }) + + describe('custom sides', () => { + const argument = new D(['r', 'a', 'n', 'd', 's', 'u', 'm']) + + test('returns a RollParameter matching the argument', () => { + const params = parameterizeRollArgument(argument) + + expect(params).toMatchObject({ + argument, + options: { + sides: argument.faces, + quantity: 1 + }, + die: argument, + notation: '1d{randsum}', + description: ['Roll 1 die with the following sides: (r,a,n,d,s,u,m)'] + }) + }) + }) + }) + describe('given RandsumNotation', () => { const coreTestString: RandsumNotation = '4d6' const coreDicePools = { sides: 6, quantity: 4 } diff --git a/tests/roll.test.ts b/tests/roll.test.ts index 3564691e..0a7da7c8 100644 --- a/tests/roll.test.ts +++ b/tests/roll.test.ts @@ -1,16 +1,44 @@ import { describe, expect, test } from 'bun:test' +import { D } from '~src/D' import { roll } from '~src/roll' +const loops = 9999 + describe('roll', () => { describe('Stress Test', () => { - const loops = 9999 - const dummyArray = Array.from({ length: loops }, () => roll().total) + describe('numerical dice', () => { + const dummyArray = Array.from( + { length: loops }, + () => roll(20, { sides: 20 }, new D(20), '1d20').total + ) + + test('it never goes outside of the bounds of the roll', () => { + dummyArray.forEach((individualRoll) => { + expect(individualRoll).toBeLessThanOrEqual(80) + expect(individualRoll).toBeGreaterThan(0) + }) + }) + }) + + describe('custom dice', () => { + const dummyArray = Array.from( + { length: loops }, + () => + roll(['h', 't'], { sides: ['h', 't'] }, new D(['h', 't']), '1d{ht}') + .result + ) + + test('it never goes outside of the bounds of the roll', () => { + dummyArray.forEach((individualRoll) => { + expect(individualRoll).toHaveLength(4) + const uniqueRolls = individualRoll + .flat() + .filter((value, index, array) => array.indexOf(value) === index) - test('it never goes outside of the bounds of the roll', () => { - dummyArray.forEach((individualRoll) => { - expect(individualRoll).toBeLessThanOrEqual(20) - expect(individualRoll).toBeGreaterThan(0) + expect(uniqueRolls.length).toBeLessThan(3) + expect(uniqueRolls.length).toBeGreaterThan(0) + }) }) }) }) diff --git a/tests/validateNotation.test.ts b/tests/validateNotation.test.ts index 60ed3630..a3f52ec0 100644 --- a/tests/validateNotation.test.ts +++ b/tests/validateNotation.test.ts @@ -63,7 +63,7 @@ describe('validateDiceNotation', () => { }) }) - describe('when given a standard notation', () => { + describe('when given a numerical notation', () => { it('returns a valid result', () => { const notation = '2d6' const result = validateDiceNotation(notation) @@ -71,7 +71,7 @@ describe('validateDiceNotation', () => { expect(result).toEqual({ valid: true, notation: '2d6', - type: DicePoolType.standard, + type: DicePoolType.numerical, digested: { sides: 6, quantity: 2, modifiers: {} }, description: ['Roll 2 6-sided dice'] }) @@ -82,7 +82,7 @@ describe('validateDiceNotation', () => { expect(result2).toEqual({ valid: true, notation: '1d20', - type: DicePoolType.standard, + type: DicePoolType.numerical, digested: { sides: 20, quantity: 1, modifiers: {} }, description: ['Roll 1 20-sided die'] }) diff --git a/tsconfig.json b/tsconfig.json index c7a9586b..a96953cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "paths": { - "~utils/*": ["./src/utils/*"], + "~models/*": ["./src/models/*"], "~patterns": ["./src/patterns.ts"], "~guards": ["./src/guards.ts"], "~types": ["./src/types.ts"],