Rename Test to check, etc., restructure calculation code.

This commit is contained in:
Oliver Rümpelein 2021-01-04 19:38:26 +01:00
parent c26e4bab9f
commit fc88ce6c52
3 changed files with 52 additions and 39 deletions

View file

@ -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); const rollProvider = mockSingleThrow(20);
expect(rollCheckSingleDie(20, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( 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]); const rollProvider = mockMultipleThrows([15, 1, 8]);
expect(rollCheckMultipleDice(46, {}, rollProvider)).toEqual( 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]); const rollProvider = mockMultipleThrows([1, 8]);
expect(rollCheckMultipleDice(24, {}, rollProvider)).toEqual( 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]); const rollProvider = mockMultipleThrows([1, 19]);
expect(rollCheckMultipleDice(38, {}, rollProvider)).toEqual( expect(rollCheckMultipleDice(38, {}, rollProvider)).toEqual(

View file

@ -1,18 +1,19 @@
import { DefaultRollOptions, RollOptions, RollResult, RollResultStatus } from "./roll-data"; import { DefaultRollOptions, RollOptions, RollResult, RollResultStatus } from "./roll-data";
import { DS4RollProvider, RollProvider } from "./roll-provider"; 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<RollOptions> = {}): RollResult { export function ds4roll(checkTargetValue: number, rollOptions: Partial<RollOptions> = {}): RollResult {
const finalRollValue = testValue; // TODO: Add CTN modifiers from options.
if (finalRollValue <= 20) { const finalTargetValue = checkTargetValue;
return rollCheckSingleDie(finalRollValue, rollOptions); if (finalTargetValue <= 20) {
return rollCheckSingleDie(finalTargetValue, rollOptions);
} else { } else {
return rollCheckMultipleDice(finalRollValue, rollOptions); return rollCheckMultipleDice(finalTargetValue, rollOptions);
} }
} }
export function rollCheckSingleDie( export function rollCheckSingleDie(
testValue: number, checkTargetValue: number,
rollOptions: Partial<RollOptions>, rollOptions: Partial<RollOptions>,
provider: RollProvider = new DS4RollProvider(), provider: RollProvider = new DS4RollProvider(),
): RollResult { ): RollResult {
@ -21,11 +22,11 @@ export function rollCheckSingleDie(
const dice = [roll]; const dice = [roll];
if (roll <= usedOptions.maxCritSucc) { 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)) { } else if (roll >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
} else { } else {
if (roll <= testValue) { if (roll <= checkTargetValue) {
return new RollResult(roll, RollResultStatus.SUCCESS, dice); return new RollResult(roll, RollResultStatus.SUCCESS, dice);
} else { } else {
return new RollResult(0, RollResultStatus.FAILURE, dice); return new RollResult(0, RollResultStatus.FAILURE, dice);
@ -51,13 +52,13 @@ function separateCriticalHits(dice: Array<number>, usedOptions: RollOptions): [A
} }
export function rollCheckMultipleDice( export function rollCheckMultipleDice(
testValue: number, targetValue: number,
rollOptions: Partial<RollOptions>, rollOptions: Partial<RollOptions>,
provider: RollProvider = new DS4RollProvider(), provider: RollProvider = new DS4RollProvider(),
): RollResult { ): RollResult {
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
const finalCheck = testValue % 20; const remainderTargetValue = targetValue % 20;
const numberOfDice = Math.ceil(testValue / 20); const numberOfDice = Math.ceil(targetValue / 20);
const dice = provider.getNextRolls(numberOfDice); const dice = provider.getNextRolls(numberOfDice);
@ -71,7 +72,7 @@ export function rollCheckMultipleDice(
const [critSuccesses, otherRolls] = separateCriticalHits(dice, usedOptions); const [critSuccesses, otherRolls] = separateCriticalHits(dice, usedOptions);
const swapLastWithCrit: boolean = isDiceSwapNecessary(critSuccesses, otherRolls, finalCheck); const swapLastWithCrit: boolean = isDiceSwapNecessary(critSuccesses, otherRolls, remainderTargetValue);
let sortedRollResults: Array<number>; let sortedRollResults: Array<number>;
@ -83,25 +84,7 @@ export function rollCheckMultipleDice(
sortedRollResults = critSuccesses.concat(otherRolls); sortedRollResults = critSuccesses.concat(otherRolls);
} }
const evaluationResult = sortedRollResults const evaluationResult = calculateRollResult(sortedRollResults, remainderTargetValue, usedOptions);
.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);
if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) { if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) {
return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults); return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults);

View file

@ -3,20 +3,50 @@ import { RollOptions } from "./roll-data";
export function isDiceSwapNecessary( export function isDiceSwapNecessary(
critSuccesses: Array<number>, critSuccesses: Array<number>,
otherRolls: Array<number>, otherRolls: Array<number>,
lastTestValue: number, remainingTargetValue: number,
): boolean { ): boolean {
if (critSuccesses.length == 0 || otherRolls.length == 0) { if (critSuccesses.length == 0 || otherRolls.length == 0) {
return false; return false;
} }
const amountOfOtherRolls = otherRolls.length; const amountOfOtherRolls = otherRolls.length;
const lastDice = otherRolls[amountOfOtherRolls - 1]; const lastDice = otherRolls[amountOfOtherRolls - 1];
if (lastDice <= lastTestValue) { if (lastDice <= remainingTargetValue) {
return false; return false;
} }
return lastDice + lastTestValue > 20; return lastDice + remainingTargetValue > 20;
} }
export function isSlayingDiceRepetition(opts: RollOptions): boolean { export function isSlayingDiceRepetition(opts: RollOptions): boolean {
return opts.useSlayingDice && opts.slayingDiceRepetition; return opts.useSlayingDice && opts.slayingDiceRepetition;
} }
export function calculateRollResult(
assignedRollResults: Array<number>,
remainderTargetValue: number,
rollOptions: RollOptions,
): number {
const numberOfDice = assignedRollResults.length;
const maxResultPerDie: Array<number> = 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<T, U>(a1: Array<T>, a2: Array<U>): 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]);
}
}