From 289f60d8c8b465614ee21f229451fa547fcec104 Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Sat, 17 Aug 2024 03:15:20 +0700 Subject: [PATCH] BLADEBURNER: Add API to calculate max upgrade count of skill (#1475) --- markdown/bitburner.bladeburnerformulas.md | 20 ++++ ...ladeburnerformulas.skillmaxupgradecount.md | 28 ++++++ markdown/bitburner.formulas.bladeburner.md | 13 +++ markdown/bitburner.formulas.md | 1 + markdown/bitburner.md | 1 + src/Bladeburner/Skill.ts | 66 +++++++++++-- src/Infiltration/ui/BackwardGame.tsx | 4 +- src/Infiltration/ui/BracketGame.tsx | 4 +- src/Infiltration/ui/CheatCodeGame.tsx | 5 +- src/Infiltration/ui/WireCuttingGame.tsx | 4 +- src/Infiltration/utils.ts | 4 - src/Netscript/NetscriptHelpers.tsx | 1 + src/Netscript/RamCostGenerator.ts | 3 + src/NetscriptFunctions/Formulas.ts | 17 ++++ src/ScriptEditor/NetscriptDefinitions.d.ts | 18 ++++ src/utils/helpers/getRandomArbitrary.ts | 7 ++ test/jest/Netscript/Bladeburner.test.ts | 97 +++++++++++++++++++ 17 files changed, 274 insertions(+), 19 deletions(-) create mode 100644 markdown/bitburner.bladeburnerformulas.md create mode 100644 markdown/bitburner.bladeburnerformulas.skillmaxupgradecount.md create mode 100644 markdown/bitburner.formulas.bladeburner.md create mode 100644 src/utils/helpers/getRandomArbitrary.ts create mode 100644 test/jest/Netscript/Bladeburner.test.ts diff --git a/markdown/bitburner.bladeburnerformulas.md b/markdown/bitburner.bladeburnerformulas.md new file mode 100644 index 000000000..cd9c53985 --- /dev/null +++ b/markdown/bitburner.bladeburnerformulas.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BladeburnerFormulas](./bitburner.bladeburnerformulas.md) + +## BladeburnerFormulas interface + +Bladeburner formulas + +**Signature:** + +```typescript +interface BladeburnerFormulas +``` + +## Methods + +| Method | Description | +| --- | --- | +| [skillMaxUpgradeCount(name, level, skillPoints)](./bitburner.bladeburnerformulas.skillmaxupgradecount.md) | Calculate the number of times that you can upgrade a skill. | + diff --git a/markdown/bitburner.bladeburnerformulas.skillmaxupgradecount.md b/markdown/bitburner.bladeburnerformulas.skillmaxupgradecount.md new file mode 100644 index 000000000..5ca4a707a --- /dev/null +++ b/markdown/bitburner.bladeburnerformulas.skillmaxupgradecount.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [BladeburnerFormulas](./bitburner.bladeburnerformulas.md) > [skillMaxUpgradeCount](./bitburner.bladeburnerformulas.skillmaxupgradecount.md) + +## BladeburnerFormulas.skillMaxUpgradeCount() method + +Calculate the number of times that you can upgrade a skill. + +**Signature:** + +```typescript +skillMaxUpgradeCount(name: string, level: number, skillPoints: number): number; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | Skill name. It's case-sensitive and must be an exact match. | +| level | number | Skill level. It must be a non-negative number. | +| skillPoints | number | Number of skill points to upgrade the skill. It must be a positive number. | + +**Returns:** + +number + +Number of times that you can upgrade the skill. + diff --git a/markdown/bitburner.formulas.bladeburner.md b/markdown/bitburner.formulas.bladeburner.md new file mode 100644 index 000000000..4bd3a4116 --- /dev/null +++ b/markdown/bitburner.formulas.bladeburner.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Formulas](./bitburner.formulas.md) > [bladeburner](./bitburner.formulas.bladeburner.md) + +## Formulas.bladeburner property + +Bladeburner formulas + +**Signature:** + +```typescript +bladeburner: BladeburnerFormulas; +``` diff --git a/markdown/bitburner.formulas.md b/markdown/bitburner.formulas.md index 66aac71ce..435639c57 100644 --- a/markdown/bitburner.formulas.md +++ b/markdown/bitburner.formulas.md @@ -20,6 +20,7 @@ You need Formulas.exe on your home computer to use this API. | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [bladeburner](./bitburner.formulas.bladeburner.md) | | [BladeburnerFormulas](./bitburner.bladeburnerformulas.md) | Bladeburner formulas | | [gang](./bitburner.formulas.gang.md) | | [GangFormulas](./bitburner.gangformulas.md) | Gang formulas | | [hacking](./bitburner.formulas.hacking.md) | | [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas | | [hacknetNodes](./bitburner.formulas.hacknetnodes.md) | | [HacknetNodesFormulas](./bitburner.hacknetnodesformulas.md) | Hacknet Nodes formulas | diff --git a/markdown/bitburner.md b/markdown/bitburner.md index 6de6b5d64..41de47a50 100644 --- a/markdown/bitburner.md +++ b/markdown/bitburner.md @@ -36,6 +36,7 @@ | [BitNodeRequirement](./bitburner.bitnoderequirement.md) | Player must be located in this BitNode. | | [Bladeburner](./bitburner.bladeburner.md) | Bladeburner API | | [BladeburnerCurAction](./bitburner.bladeburnercuraction.md) | Bladeburner current action. | +| [BladeburnerFormulas](./bitburner.bladeburnerformulas.md) | Bladeburner formulas | | [BladeburnerRankRequirement](./bitburner.bladeburnerrankrequirement.md) | Player must have at least this rank in the Bladeburner Division. | | [CityRequirement](./bitburner.cityrequirement.md) | Player must be located in this city. | | [CodingContract](./bitburner.codingcontract.md) | Coding Contract API | diff --git a/src/Bladeburner/Skill.ts b/src/Bladeburner/Skill.ts index 6750ca869..54c1ca806 100644 --- a/src/Bladeburner/Skill.ts +++ b/src/Bladeburner/Skill.ts @@ -3,7 +3,7 @@ import type { BladeMultName, BladeSkillName } from "@enums"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { Bladeburner } from "./Bladeburner"; import { Availability } from "./Types"; -import { PositiveInteger, isPositiveInteger } from "../types"; +import { PositiveInteger, PositiveNumber, isPositiveInteger } from "../types"; import { PartialRecord, getRecordEntries } from "../Types/Record"; interface SkillParams { @@ -40,9 +40,6 @@ export class Skill { * The cost of the next level: (baseCost + currentLevel * costInc) * mult. The cost needs to be an integer, so we * need to use Math.floor or Math.round. * - * Note: there is no notation for Math.round, so I use \lceil and \rceil as alternatives for non-existent \lround - * and \rround. When you see \lceil and \rceil, it means Math.round, not Math.ceil. - * * In order to calculate the cost of "count" levels, we need to run a loop. "count" can be a big number, so it's * infeasible to calculate the cost in that way. We need to find the closed forms of: * @@ -52,7 +49,7 @@ export class Skill { * Or: * * [2]: - * $$Cost = \sum_{i = CurrentLevel}^{CurrentLevel+Count-1}\lceil ((BaseCost + i \ast CostInc) \ast Mult) \rceil$$ + * $$Cost = \sum_{i = CurrentLevel}^{CurrentLevel+Count-1} \mathrm{Round}((BaseCost + i \ast CostInc) \ast Mult)$$ * * It's really hard to find the closed forms of those two equations, so we switch to these equations: * @@ -62,7 +59,7 @@ export class Skill { * Or * * [4]: - * $$Cost = \lceil\sum_{i = CurrentLevel}^{CurrentLevel+Count-1} ((BaseCost + i \ast CostInc) \ast Mult) \rceil$$ + * $$Cost = \mathrm{Round}(\sum_{i = CurrentLevel}^{CurrentLevel+Count-1} ((BaseCost + i \ast CostInc) \ast Mult))$$ * * This means that we do the flooring/rounding at the end instead of each iterative step. * @@ -73,7 +70,7 @@ export class Skill { * * The closed form of [4]: * - * $$Cost = \lceil Count \ast Mult \ast (BaseCost + (CostInc \ast (CurrentLevel + \frac{Count - 1}{2}))) \rceil$$ + * $$Cost = \mathrm{Round}(Count \ast Mult \ast (BaseCost + (CostInc \ast (CurrentLevel + \frac{Count - 1}{2}))))$$ * */ return Math.round( @@ -83,6 +80,61 @@ export class Skill { ); } + calculateMaxUpgradeCount(currentLevel: number, cost: PositiveNumber): number { + /** + * Define: + * - x = count + * - a = currentNodeMults.BladeburnerSkillCost + * - b = this.baseCost + * - c = this.costInc + * - d = currentLevel + * - y = cost + * + * We have: + * + * $$ y = \mathrm{Round}(x \ast a \ast (b + c \ast (d + \frac{x - 1}{2})))$$ + * + * To simplify the calculation, let's ignore the Math.round part: + * + * $$ y = x \ast a \ast (b + c \ast (d + \frac{x - 1}{2}))$$ + * + * Solve for x in terms of y: + * + * Define: + * + * $$ m = -b - c \ast d + \frac{c}{2} $$ + * + * $$ Delta = \sqrt{{m ^ 2} + \frac{2 \ast c \ast y}{a}} $$ + * + * Solutions: + * + * $$ x_1 = \frac{m + Delta}{c} $$ + * + * $$ x_2 = \frac{m - Delta}{c} $$ + * + * $a$, $c$ and $y$ are always greater than 0, so $x_2$ is always less than 0. Therefore, $x_1$ is the only + * solution. + */ + const m = -this.baseCost - this.costInc * currentLevel + this.costInc / 2; + const delta = Math.sqrt(m * m + (2 * this.costInc * cost) / currentNodeMults.BladeburnerSkillCost); + const result = Math.round((m + delta) / this.costInc); + /** + * Due to floating-point rounding and edge-cases, we cannot ensure that rounding x_1 will give us the correct + * integer. In other words, we cannot be sure that x_1 is within 0.5 of the integer value we want. However, we can + * be sure that it is within 1 of the value we want, which means that checking the numbers above and below the + * rounded value are sufficient to find our correct integer. + */ + const costOfResultPlus1 = this.calculateCost(currentLevel, (result + 1) as PositiveInteger); + if (costOfResultPlus1 <= cost) { + return result + 1; + } + const costOfResult = this.calculateCost(currentLevel, result as PositiveInteger); + if (costOfResult <= cost) { + return result; + } + return result - 1; + } + canUpgrade(bladeburner: Bladeburner, count = 1): Availability<{ cost: number }> { const currentLevel = bladeburner.skills[this.name] ?? 0; if (!isPositiveInteger(count)) { diff --git a/src/Infiltration/ui/BackwardGame.tsx b/src/Infiltration/ui/BackwardGame.tsx index 6f8fafa87..9a2e32765 100644 --- a/src/Infiltration/ui/BackwardGame.tsx +++ b/src/Infiltration/ui/BackwardGame.tsx @@ -3,12 +3,12 @@ import React, { useState } from "react"; import { AugmentationName } from "@enums"; import { Player } from "@player"; import { KEY } from "../../utils/helpers/keyCodes"; -import { random } from "../utils"; import { BlinkingCursor } from "./BlinkingCursor"; import { interpolate } from "./Difficulty"; import { GameTimer } from "./GameTimer"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; +import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary"; interface Difficulty { [key: string]: number; @@ -67,7 +67,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement { } function makeAnswer(difficulty: Difficulty): string { - const length = random(difficulty.min, difficulty.max); + const length = getRandomArbitrary(difficulty.min, difficulty.max); let answer = ""; for (let i = 0; i < length; i++) { if (i > 0) answer += " "; diff --git a/src/Infiltration/ui/BracketGame.tsx b/src/Infiltration/ui/BracketGame.tsx index 120883c16..3866eddfc 100644 --- a/src/Infiltration/ui/BracketGame.tsx +++ b/src/Infiltration/ui/BracketGame.tsx @@ -3,12 +3,12 @@ import React, { useState } from "react"; import { AugmentationName } from "@enums"; import { Player } from "@player"; import { KEY } from "../../utils/helpers/keyCodes"; -import { random } from "../utils"; import { BlinkingCursor } from "./BlinkingCursor"; import { interpolate } from "./Difficulty"; import { GameTimer } from "./GameTimer"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; +import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary"; interface Difficulty { [key: string]: number; @@ -35,7 +35,7 @@ function generateLeftSide(difficulty: Difficulty): string { if (Player.hasAugmentation(AugmentationName.WisdomOfAthena, true)) { options.splice(0, 1); } - const length = random(difficulty.min, difficulty.max); + const length = getRandomArbitrary(difficulty.min, difficulty.max); for (let i = 0; i < length; i++) { str += options[Math.floor(Math.random() * options.length)]; } diff --git a/src/Infiltration/ui/CheatCodeGame.tsx b/src/Infiltration/ui/CheatCodeGame.tsx index a7c0518ad..27378fa34 100644 --- a/src/Infiltration/ui/CheatCodeGame.tsx +++ b/src/Infiltration/ui/CheatCodeGame.tsx @@ -2,11 +2,12 @@ import { Paper, Typography } from "@mui/material"; import React, { useState } from "react"; import { AugmentationName } from "@enums"; import { Player } from "@player"; -import { Arrow, downArrowSymbol, getArrow, leftArrowSymbol, random, rightArrowSymbol, upArrowSymbol } from "../utils"; +import { Arrow, downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils"; import { interpolate } from "./Difficulty"; import { GameTimer } from "./GameTimer"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; +import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary"; interface Difficulty { [key: string]: number; @@ -77,7 +78,7 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement { function generateCode(difficulty: Difficulty): Arrow[] { const arrows: Arrow[] = [leftArrowSymbol, rightArrowSymbol, upArrowSymbol, downArrowSymbol]; const code: Arrow[] = []; - for (let i = 0; i < random(difficulty.min, difficulty.max); i++) { + for (let i = 0; i < getRandomArbitrary(difficulty.min, difficulty.max); i++) { let arrow = arrows[Math.floor(4 * Math.random())]; while (arrow === code[code.length - 1]) arrow = arrows[Math.floor(4 * Math.random())]; code.push(arrow); diff --git a/src/Infiltration/ui/WireCuttingGame.tsx b/src/Infiltration/ui/WireCuttingGame.tsx index 425773ea6..7a556d502 100644 --- a/src/Infiltration/ui/WireCuttingGame.tsx +++ b/src/Infiltration/ui/WireCuttingGame.tsx @@ -4,12 +4,12 @@ import { Box, Paper, Typography } from "@mui/material"; import { AugmentationName } from "@enums"; import { Player } from "@player"; import { Settings } from "../../Settings/Settings"; -import { random } from "../utils"; import { interpolate } from "./Difficulty"; import { GameTimer } from "./GameTimer"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { isPositiveInteger } from "../../types"; +import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary"; interface Difficulty { [key: string]: number; @@ -200,7 +200,7 @@ function generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] { function generateWires(difficulty: Difficulty): Wire[] { const wires = []; - const numWires = random(difficulty.wiresmin, difficulty.wiresmax); + const numWires = getRandomArbitrary(difficulty.wiresmin, difficulty.wiresmax); for (let i = 0; i < numWires; i++) { const wireColors = [colors[Math.floor(Math.random() * colors.length)]]; if (Math.random() < 0.15) { diff --git a/src/Infiltration/utils.ts b/src/Infiltration/utils.ts index 724e3a585..6893f482f 100644 --- a/src/Infiltration/utils.ts +++ b/src/Infiltration/utils.ts @@ -2,10 +2,6 @@ import { KEY } from "../utils/helpers/keyCodes"; import { Player } from "@player"; import { AugmentationName } from "@enums"; -export function random(min: number, max: number): number { - return Math.random() * (max - min) + min; -} - export const upArrowSymbol = "↑"; export const downArrowSymbol = "↓"; export const leftArrowSymbol = "←"; diff --git a/src/Netscript/NetscriptHelpers.tsx b/src/Netscript/NetscriptHelpers.tsx index 342601314..2737b6330 100644 --- a/src/Netscript/NetscriptHelpers.tsx +++ b/src/Netscript/NetscriptHelpers.tsx @@ -67,6 +67,7 @@ export const helpers = { number, positiveInteger, positiveSafeInteger, + positiveNumber, scriptArgs, runOptions, spawnOptions, diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index b57979d65..71cdf2449 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -669,6 +669,9 @@ export const RamCosts: RamCostTree = { factionGains: 0, companyGains: 0, }, + bladeburner: { + skillMaxUpgradeCount: 0, + }, }, } as const; diff --git a/src/NetscriptFunctions/Formulas.ts b/src/NetscriptFunctions/Formulas.ts index 9287d101b..10a59857b 100644 --- a/src/NetscriptFunctions/Formulas.ts +++ b/src/NetscriptFunctions/Formulas.ts @@ -51,6 +51,7 @@ import { findEnumMember } from "../utils/helpers/enum"; import { getEnumHelper } from "../utils/EnumHelper"; import { CompanyPositions } from "../Company/CompanyPositions"; import { findCrime } from "../Crime/CrimeHelpers"; +import { Skills } from "../Bladeburner/data/Skills"; export function NetscriptFormulas(): InternalAPI { const checkFormulasAccess = function (ctx: NetscriptContext): void { @@ -427,6 +428,22 @@ export function NetscriptFormulas(): InternalAPI { return calculateCompanyWorkStats(person, company, position, favor); }, }, + bladeburner: { + skillMaxUpgradeCount: (ctx) => (_name, _level, _skillPoints) => { + checkFormulasAccess(ctx); + const name = getEnumHelper("BladeSkillName").nsGetMember(ctx, _name, "name"); + const level = helpers.number(ctx, "level", _level); + if (level < 0) { + throw new Error(`Level must be a non-negative number.`); + } + const skillPoints = helpers.positiveNumber(ctx, "skillPoints", _skillPoints); + const skill = Skills[name]; + if (level >= skill.maxLvl) { + return 0; + } + return skill.calculateMaxUpgradeCount(level, skillPoints); + }, + }, }; // Removed functions diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index f52f84f9a..810048c41 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -5110,6 +5110,22 @@ interface GangFormulas { ascensionMultiplier(points: number): number; } +/** + * Bladeburner formulas + * @public + */ +interface BladeburnerFormulas { + /** + * Calculate the number of times that you can upgrade a skill. + * + * @param name - Skill name. It's case-sensitive and must be an exact match. + * @param level - Skill level. It must be a non-negative number. + * @param skillPoints - Number of skill points to upgrade the skill. It must be a positive number. + * @returns Number of times that you can upgrade the skill. + */ + skillMaxUpgradeCount(name: string, level: number, skillPoints: number): number; +} + /** * Formulas API * @remarks @@ -5134,6 +5150,8 @@ export interface Formulas { gang: GangFormulas; /** Work formulas */ work: WorkFormulas; + /** Bladeburner formulas */ + bladeburner: BladeburnerFormulas; } /** @public */ diff --git a/src/utils/helpers/getRandomArbitrary.ts b/src/utils/helpers/getRandomArbitrary.ts new file mode 100644 index 000000000..5f18107cc --- /dev/null +++ b/src/utils/helpers/getRandomArbitrary.ts @@ -0,0 +1,7 @@ +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#getting_a_random_number_between_two_values +export function getRandomArbitrary(min: number, max: number): number { + if (min > max) { + throw new Error(`Min is greater than max. Min: ${min}. Max: ${max}.`); + } + return Math.random() * (max - min) + min; +} diff --git a/test/jest/Netscript/Bladeburner.test.ts b/test/jest/Netscript/Bladeburner.test.ts new file mode 100644 index 000000000..16e8a5aa2 --- /dev/null +++ b/test/jest/Netscript/Bladeburner.test.ts @@ -0,0 +1,97 @@ +import { currentNodeMults } from "../../../src/BitNode/BitNodeMultipliers"; +import { Skill } from "../../../src/Bladeburner/Skill"; +import { BladeSkillName } from "../../../src/Enums"; +import { PositiveInteger, isPositiveInteger, isPositiveNumber } from "../../../src/types"; +import { getRandomArbitrary } from "../../../src/utils/helpers/getRandomArbitrary"; +import { getRandomIntInclusive } from "../../../src/utils/helpers/getRandomIntInclusive"; + +const skill = new Skill({ + name: BladeSkillName.hyperdrive, + desc: "", + baseCost: 1, + costInc: 1, + mults: {}, +}); + +describe("Test calculateMaxUpgradeCount", function () { + test("errorCount", () => { + let testCaseCount = 0; + let errorCount = 0; + const test1Errors = []; + const test2Errors = []; + for (let i = 0; i < 10; ++i) { + skill.baseCost = getRandomIntInclusive(1, 1000); + for (let j = 0; j < 10; ++j) { + skill.costInc = getRandomArbitrary(1, 1000); + for (let k = 0; k < 10; ++k) { + currentNodeMults.BladeburnerSkillCost = getRandomArbitrary(1, 1000); + for (let m = 0; m < 1e4; ++m) { + const currentLevel = getRandomIntInclusive(0, 1e9); + let count = 0; + let cost = 0; + + // Test 1 + ++testCaseCount; + const expectedCount = getRandomIntInclusive(1, 1e9); + if (!isPositiveInteger(expectedCount)) { + throw new Error(`Invalid expectedCount: ${expectedCount}`); + } + cost = skill.calculateCost(currentLevel, expectedCount); + if (!isPositiveNumber(cost)) { + throw new Error(`Invalid cost: ${cost}`); + } + count = skill.calculateMaxUpgradeCount(currentLevel, cost); + if (expectedCount !== count) { + ++errorCount; + test1Errors.push({ + baseCost: skill.baseCost, + costInc: skill.costInc, + mult: currentNodeMults.BladeburnerSkillCost, + currentLevel, + cost, + count, + expectedCount, + }); + } + + // Test 2 + ++testCaseCount; + const budget = getRandomArbitrary(1e9, 1e50); + if (!isPositiveNumber(budget)) { + throw new Error(`Invalid budget: ${budget}`); + } + count = skill.calculateMaxUpgradeCount(currentLevel, budget); + if (!isPositiveInteger(count)) { + throw new Error(`Invalid count: ${count}`); + } + cost = skill.calculateCost(currentLevel, count); + const costOfCountPlus1 = skill.calculateCost(currentLevel, (count + 1) as PositiveInteger); + if (count !== count + 1 && (budget < cost || budget >= costOfCountPlus1)) { + ++errorCount; + test2Errors.push({ + baseCost: skill.baseCost, + costInc: skill.costInc, + mult: currentNodeMults.BladeburnerSkillCost, + currentLevel, + count, + budget, + cost, + costOfCountPlus1, + }); + } + } + } + } + } + if (errorCount !== 0) { + // There may be hundreds of thousands or even millions of failed test cases, so we only show a limited number of them. + console.error( + `testCaseCount: ${testCaseCount}. errorCount: ${errorCount}. test1Errors: ${JSON.stringify( + test1Errors.slice(0, 1000), + )}. test2Errors: ${JSON.stringify(test2Errors.slice(0, 1000))}`, + ); + } + + expect(errorCount).toBe(0); + }); +});