diff --git a/spec/support/ds4rolls/executor.spec.ts b/spec/support/ds4rolls/executor.spec.ts index e141b215..3417e6e6 100644 --- a/spec/support/ds4rolls/executor.spec.ts +++ b/spec/support/ds4rolls/executor.spec.ts @@ -93,7 +93,7 @@ describe("DS4 Rolls with one die and slaying dice, followup throw.", () => { ); }); - it("Should do a regular success on `20` with a test value of 20", () => { + it("Should do a regular success on `20` with a CTN of 20", () => { const rollProvider = mockSingleThrow(20); expect(rollCheckSingleDie(20, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( @@ -209,7 +209,7 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { ); }); - it("Should maximize on 'lowest dice higher than last test and crit success thrown'-Edge case, no change required.", () => { + it("Should maximize on 'lowest dice higher than last CTN and crit success thrown'-Edge case, no change required.", () => { const rollProvider = mockMultipleThrows([15, 1, 8]); expect(rollCheckMultipleDice(46, {}, rollProvider)).toEqual( @@ -217,7 +217,7 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { ); }); - it("Should maximize on 2-dice 'lowest dice higher than last test and crit success thrown'-Edge case, no change required.", () => { + it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, no change required.", () => { const rollProvider = mockMultipleThrows([1, 8]); expect(rollCheckMultipleDice(24, {}, rollProvider)).toEqual( @@ -225,7 +225,7 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { ); }); - it("Should maximize on 2-dice 'lowest dice higher than last test and crit success thrown'-Edge case, change required.", () => { + it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, change required.", () => { const rollProvider = mockMultipleThrows([1, 19]); expect(rollCheckMultipleDice(38, {}, rollProvider)).toEqual( diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index 233950ea..896629a2 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -1,18 +1,19 @@ import { DefaultRollOptions, RollOptions, RollResult, RollResultStatus } from "./roll-data"; import { DS4RollProvider, RollProvider } from "./roll-provider"; -import { isDiceSwapNecessary, isSlayingDiceRepetition } from "./roll-utils"; +import { calculateRollResult, isDiceSwapNecessary, isSlayingDiceRepetition } from "./roll-utils"; -export function ds4test(testValue: number, rollOptions: Partial = {}): RollResult { - const finalRollValue = testValue; - if (finalRollValue <= 20) { - return rollCheckSingleDie(finalRollValue, rollOptions); +export function ds4roll(checkTargetValue: number, rollOptions: Partial = {}): RollResult { + // TODO: Add CTN modifiers from options. + const finalTargetValue = checkTargetValue; + if (finalTargetValue <= 20) { + return rollCheckSingleDie(finalTargetValue, rollOptions); } else { - return rollCheckMultipleDice(finalRollValue, rollOptions); + return rollCheckMultipleDice(finalTargetValue, rollOptions); } } export function rollCheckSingleDie( - testValue: number, + checkTargetValue: number, rollOptions: Partial, provider: RollProvider = new DS4RollProvider(), ): RollResult { @@ -21,11 +22,11 @@ export function rollCheckSingleDie( const dice = [roll]; if (roll <= usedOptions.maxCritSucc) { - return new RollResult(testValue, RollResultStatus.CRITICAL_SUCCESS, dice); + return new RollResult(checkTargetValue, RollResultStatus.CRITICAL_SUCCESS, dice); } else if (roll >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) { return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); } else { - if (roll <= testValue) { + if (roll <= checkTargetValue) { return new RollResult(roll, RollResultStatus.SUCCESS, dice); } else { return new RollResult(0, RollResultStatus.FAILURE, dice); @@ -51,13 +52,13 @@ function separateCriticalHits(dice: Array, usedOptions: RollOptions): [A } export function rollCheckMultipleDice( - testValue: number, + targetValue: number, rollOptions: Partial, provider: RollProvider = new DS4RollProvider(), ): RollResult { const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); - const finalCheck = testValue % 20; - const numberOfDice = Math.ceil(testValue / 20); + const remainderTargetValue = targetValue % 20; + const numberOfDice = Math.ceil(targetValue / 20); const dice = provider.getNextRolls(numberOfDice); @@ -71,7 +72,7 @@ export function rollCheckMultipleDice( const [critSuccesses, otherRolls] = separateCriticalHits(dice, usedOptions); - const swapLastWithCrit: boolean = isDiceSwapNecessary(critSuccesses, otherRolls, finalCheck); + const swapLastWithCrit: boolean = isDiceSwapNecessary(critSuccesses, otherRolls, remainderTargetValue); let sortedRollResults: Array; @@ -83,25 +84,7 @@ export function rollCheckMultipleDice( sortedRollResults = critSuccesses.concat(otherRolls); } - const evaluationResult = sortedRollResults - .map((value, index) => { - if (index == numberOfDice - 1) { - if (value <= usedOptions.maxCritSucc) { - return finalCheck; - } else if (value <= finalCheck) { - return value; - } else { - return 0; - } - } else { - if (value <= usedOptions.maxCritSucc) { - return 20; - } else { - return value; - } - } - }) - .reduce((a, b) => a + b); + const evaluationResult = calculateRollResult(sortedRollResults, remainderTargetValue, usedOptions); if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) { return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults); diff --git a/src/module/rolls/roll-utils.ts b/src/module/rolls/roll-utils.ts index 14e95f01..5df493e2 100644 --- a/src/module/rolls/roll-utils.ts +++ b/src/module/rolls/roll-utils.ts @@ -3,20 +3,50 @@ import { RollOptions } from "./roll-data"; export function isDiceSwapNecessary( critSuccesses: Array, otherRolls: Array, - lastTestValue: number, + remainingTargetValue: number, ): boolean { if (critSuccesses.length == 0 || otherRolls.length == 0) { return false; } const amountOfOtherRolls = otherRolls.length; const lastDice = otherRolls[amountOfOtherRolls - 1]; - if (lastDice <= lastTestValue) { + if (lastDice <= remainingTargetValue) { return false; } - return lastDice + lastTestValue > 20; + return lastDice + remainingTargetValue > 20; } export function isSlayingDiceRepetition(opts: RollOptions): boolean { return opts.useSlayingDice && opts.slayingDiceRepetition; } + +export function calculateRollResult( + assignedRollResults: Array, + remainderTargetValue: number, + rollOptions: RollOptions, +): number { + const numberOfDice = assignedRollResults.length; + + const maxResultPerDie: Array = Array(numberOfDice).fill(20); + maxResultPerDie[numberOfDice - 1] = remainderTargetValue; + + const rollsAndMaxValues = zip(assignedRollResults, maxResultPerDie); + + return rollsAndMaxValues + .map(([v, m]) => { + return v <= rollOptions.maxCritSucc ? [m, m] : [v, m]; + }) + .filter(([v, m]) => v <= m) + .map(([v]) => v) + .reduce((a, b) => a + b); +} + +// TODO: Move to generic utils method? +function zip(a1: Array, a2: Array): Array<[T, U]> { + if (a1.length <= a2.length) { + return a1.map((e1, i) => [e1, a2[i]]); + } else { + return a2.map((e2, i) => [a1[i], e2]); + } +}