// SPDX-FileCopyrightText: 2021 Johannes Loher // SPDX-FileCopyrightText: 2021 Oliver Rümpelein // // SPDX-License-Identifier: MIT import { getGame } from "../helpers"; export default function evaluateCheck( dice: number[], checkTargetNumber: number, { maximumCoupResult = 1, minimumFumbleResult = 20, canFumble = true, }: { maximumCoupResult?: number; minimumFumbleResult?: number; canFumble?: boolean } = {}, ): SubCheckResult[] { const diceWithSubChecks = assignSubChecksToDice(dice, checkTargetNumber, { maximumCoupResult: maximumCoupResult, }); return evaluateDiceWithSubChecks(diceWithSubChecks, { maximumCoupResult: maximumCoupResult, minimumFumbleResult: minimumFumbleResult, canFumble: canFumble, }); } interface DieWithSubCheck { result: number; checkTargetNumber: number; } function assignSubChecksToDice( dice: number[], checkTargetNumber: number, { maximumCoupResult = 1, }: { maximumCoupResult?: number; } = {}, ): DieWithSubCheck[] { const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber); if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) { throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice")); } const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1); const indexOfSmallestNonCoup = findIndexOfSmallestNonCoup(dice, maximumCoupResult); const indexOfFirstCoup = dice.findIndex((die) => die <= maximumCoupResult); const indexForLastSubCheck = shouldUseCoupForLastSubCheck( indexOfSmallestNonCoup, indexOfFirstCoup, dice, checkTargetNumberForLastSubCheck, ) ? indexOfFirstCoup : indexOfSmallestNonCoup; return dice.map((die, index) => ({ result: die, checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20, })); } function findIndexOfSmallestNonCoup(dice: number[], maximumCoupResult: number): number { return dice .map((die, index) => [die, index]) .filter((indexedDie) => indexedDie[0] > maximumCoupResult) .reduce( (smallestIndexedDie, indexedDie) => indexedDie[0] < smallestIndexedDie[0] ? indexedDie : smallestIndexedDie, [Infinity, -1], )[1]; } function shouldUseCoupForLastSubCheck( indexOfSmallestNonCoup: number, indexOfFirstCoup: number, dice: number[], checkTargetNumberForLastSubCheck: number, ) { return ( indexOfFirstCoup !== -1 && (indexOfSmallestNonCoup === -1 || (dice[indexOfSmallestNonCoup] > checkTargetNumberForLastSubCheck && dice[indexOfSmallestNonCoup] + checkTargetNumberForLastSubCheck > 20)) ); } interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {} function evaluateDiceWithSubChecks( results: DieWithSubCheck[], { maximumCoupResult, minimumFumbleResult, canFumble, }: { maximumCoupResult: number; minimumFumbleResult: number; canFumble: boolean }, ): SubCheckResult[] { 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 { return Math.max(Math.ceil(checkTargetNumber / 20), 1); }