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 17:43:48 +01:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
} = {},
|
2021-03-13 17:43:48 +01:00
|
|
|
): DieWithSubCheck[] {
|
2023-07-10 22:23:13 +02:00
|
|
|
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
|
2021-03-13 17:43:48 +01:00
|
|
|
|
2023-07-10 22:23:13 +02:00
|
|
|
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
|
|
|
|
throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
|
|
|
}
|
2021-03-13 02:27:09 +01:00
|
|
|
|
2023-07-10 22:23:13 +02:00
|
|
|
const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
|
2021-03-13 02:27:09 +01:00
|
|
|
|
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;
|
2021-03-13 02:27:09 +01:00
|
|
|
|
2023-07-10 22:23:13 +02:00
|
|
|
return dice.map((die, index) => ({
|
|
|
|
result: die,
|
|
|
|
checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20,
|
|
|
|
}));
|
2021-03-13 02:27:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
2021-03-13 02:27:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function shouldUseCoupForLastSubCheck(
|
2023-07-10 22:23:13 +02:00
|
|
|
indexOfSmallestNonCoup: number,
|
|
|
|
indexOfFirstCoup: number,
|
|
|
|
dice: readonly number[],
|
|
|
|
checkTargetNumberForLastSubCheck: number,
|
2021-03-13 02:27:09 +01:00
|
|
|
) {
|
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
|
|
|
|
);
|
2021-03-13 02:27:09 +01:00
|
|
|
}
|
2021-03-13 17:43:48 +01:00
|
|
|
|
2022-11-17 00:12:29 +01:00
|
|
|
interface SubCheckResult extends DieWithSubCheck {
|
2023-07-10 22:23:13 +02:00
|
|
|
active?: boolean;
|
|
|
|
discarded?: boolean;
|
|
|
|
success?: boolean;
|
|
|
|
failure?: boolean;
|
|
|
|
count?: number;
|
2022-11-17 00:12:29 +01:00
|
|
|
}
|
2021-03-13 17:43:48 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
});
|
2021-03-13 17:43:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getRequiredNumberOfDice(checkTargetNumber: number): number {
|
2023-07-10 22:23:13 +02:00
|
|
|
return Math.max(Math.ceil(checkTargetNumber / 20), 1);
|
2021-03-13 17:43:48 +01:00
|
|
|
}
|