Rename Test
to check
, etc., restructure calculation code.
This commit is contained in:
parent
c26e4bab9f
commit
fc88ce6c52
3 changed files with 52 additions and 39 deletions
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue