ds4/src/dice/check-evaluation.ts

137 lines
4.1 KiB
TypeScript
Raw Normal View History

2021-06-26 22:02:00 +02:00
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
//
// SPDX-License-Identifier: MIT
2022-11-04 21:47:18 +01:00
import { getGame } from "../utils/utils";
2021-07-07 19:22:35 +02:00
2022-11-04 21:47:18 +01:00
export function evaluateCheck(
2023-07-10 22:23:13 +02:00
dice: number[],
checkTargetNumber: number,
{
maximumCoupResult = 1,
minimumFumbleResult = 20,
canFumble = true,
}: { maximumCoupResult?: number; minimumFumbleResult?: number; canFumble?: boolean } = {},
2021-03-13 18:53:26 +01:00
): SubCheckResult[] {
2023-07-10 22:23:13 +02:00
const diceWithSubChecks = assignSubChecksToDice(dice, checkTargetNumber, {
maximumCoupResult: maximumCoupResult,
});
return evaluateDiceWithSubChecks(diceWithSubChecks, {
maximumCoupResult: maximumCoupResult,
minimumFumbleResult: minimumFumbleResult,
canFumble: canFumble,
});
}
2021-03-13 18:43:23 +01:00
interface DieWithSubCheck {
2023-07-10 22:23:13 +02:00
result: number;
checkTargetNumber: number;
2021-03-13 18:43:23 +01:00
}
function assignSubChecksToDice(
2023-07-10 22:23:13 +02:00
dice: number[],
checkTargetNumber: number,
{
maximumCoupResult = 1,
}: {
maximumCoupResult?: number;
} = {},
): DieWithSubCheck[] {
2023-07-10 22:23:13 +02:00
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
2023-07-10 22:23:13 +02:00
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
}
2023-07-10 22:23:13 +02:00
const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
2023-07-10 22:23:13 +02:00
const indexOfSmallestNonCoup = findIndexOfSmallestNonCoup(dice, maximumCoupResult);
const indexOfFirstCoup = dice.findIndex((die) => die <= maximumCoupResult);
const indexForLastSubCheck = shouldUseCoupForLastSubCheck(
indexOfSmallestNonCoup,
indexOfFirstCoup,
dice,
checkTargetNumberForLastSubCheck,
)
? indexOfFirstCoup
: indexOfSmallestNonCoup;
2023-07-10 22:23:13 +02:00
return dice.map((die, index) => ({
result: die,
checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20,
}));
}
function findIndexOfSmallestNonCoup(dice: number[], maximumCoupResult: number): number {
2023-07-10 22:23:13 +02:00
return dice
.map((die, index): [number, number] => [die, index])
.filter((indexedDie) => indexedDie[0] > maximumCoupResult)
.reduce(
(smallestIndexedDie, indexedDie) => (indexedDie[0] < smallestIndexedDie[0] ? indexedDie : smallestIndexedDie),
[Infinity, -1],
)[1];
}
function shouldUseCoupForLastSubCheck(
2023-07-10 22:23:13 +02:00
indexOfSmallestNonCoup: number,
indexOfFirstCoup: number,
dice: readonly number[],
checkTargetNumberForLastSubCheck: number,
) {
2023-07-10 22:23:13 +02:00
if (indexOfFirstCoup !== -1 && indexOfSmallestNonCoup === -1) {
return true;
}
const smallestNonCoup = dice[indexOfSmallestNonCoup];
if (
!Number.isInteger(indexOfFirstCoup) ||
indexOfFirstCoup < -1 ||
!Number.isInteger(indexOfSmallestNonCoup) ||
smallestNonCoup === undefined
) {
throw new Error("Received an invalid value for the parameter indexOfSmallestNonCoup or indexOfFirstCoup,");
}
return (
indexOfFirstCoup !== -1 &&
smallestNonCoup > checkTargetNumberForLastSubCheck &&
smallestNonCoup + checkTargetNumberForLastSubCheck > 20
);
}
interface SubCheckResult extends DieWithSubCheck {
2023-07-10 22:23:13 +02:00
active?: boolean;
discarded?: boolean;
success?: boolean;
failure?: boolean;
count?: number;
}
function evaluateDiceWithSubChecks(
2023-07-10 22:23:13 +02:00
results: DieWithSubCheck[],
{
maximumCoupResult,
minimumFumbleResult,
canFumble,
}: { maximumCoupResult: number; minimumFumbleResult: number; canFumble: boolean },
2021-03-13 18:53:26 +01:00
): SubCheckResult[] {
2023-07-10 22:23:13 +02:00
return results.map((dieWithSubCheck, index) => {
const result: SubCheckResult = {
...dieWithSubCheck,
active: dieWithSubCheck.result <= dieWithSubCheck.checkTargetNumber,
discarded: dieWithSubCheck.result > dieWithSubCheck.checkTargetNumber,
};
if (result.result <= maximumCoupResult) {
result.success = true;
result.count = result.checkTargetNumber;
result.active = true;
result.discarded = false;
}
if (index === 0 && canFumble && result.result >= minimumFumbleResult) result.failure = true;
return result;
});
}
export function getRequiredNumberOfDice(checkTargetNumber: number): number {
2023-07-10 22:23:13 +02:00
return Math.max(Math.ceil(checkTargetNumber / 20), 1);
}