mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-08 08:43:53 +01:00
BLADEBURNER: Add API to calculate max upgrade count of skill (#1475)
This commit is contained in:
parent
3d15413619
commit
289f60d8c8
20
markdown/bitburner.bladeburnerformulas.md
Normal file
20
markdown/bitburner.bladeburnerformulas.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[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. |
|
||||||
|
|
@ -0,0 +1,28 @@
|
|||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
13
markdown/bitburner.formulas.bladeburner.md
Normal file
13
markdown/bitburner.formulas.bladeburner.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||||
|
|
||||||
|
[Home](./index.md) > [bitburner](./bitburner.md) > [Formulas](./bitburner.formulas.md) > [bladeburner](./bitburner.formulas.bladeburner.md)
|
||||||
|
|
||||||
|
## Formulas.bladeburner property
|
||||||
|
|
||||||
|
Bladeburner formulas
|
||||||
|
|
||||||
|
**Signature:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
bladeburner: BladeburnerFormulas;
|
||||||
|
```
|
@ -20,6 +20,7 @@ You need Formulas.exe on your home computer to use this API.
|
|||||||
|
|
||||||
| Property | Modifiers | Type | Description |
|
| 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 |
|
| [gang](./bitburner.formulas.gang.md) | | [GangFormulas](./bitburner.gangformulas.md) | Gang formulas |
|
||||||
| [hacking](./bitburner.formulas.hacking.md) | | [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas |
|
| [hacking](./bitburner.formulas.hacking.md) | | [HackingFormulas](./bitburner.hackingformulas.md) | Hacking formulas |
|
||||||
| [hacknetNodes](./bitburner.formulas.hacknetnodes.md) | | [HacknetNodesFormulas](./bitburner.hacknetnodesformulas.md) | Hacknet Nodes formulas |
|
| [hacknetNodes](./bitburner.formulas.hacknetnodes.md) | | [HacknetNodesFormulas](./bitburner.hacknetnodesformulas.md) | Hacknet Nodes formulas |
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
| [BitNodeRequirement](./bitburner.bitnoderequirement.md) | Player must be located in this BitNode. |
|
| [BitNodeRequirement](./bitburner.bitnoderequirement.md) | Player must be located in this BitNode. |
|
||||||
| [Bladeburner](./bitburner.bladeburner.md) | Bladeburner API |
|
| [Bladeburner](./bitburner.bladeburner.md) | Bladeburner API |
|
||||||
| [BladeburnerCurAction](./bitburner.bladeburnercuraction.md) | Bladeburner current action. |
|
| [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. |
|
| [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. |
|
| [CityRequirement](./bitburner.cityrequirement.md) | Player must be located in this city. |
|
||||||
| [CodingContract](./bitburner.codingcontract.md) | Coding Contract API |
|
| [CodingContract](./bitburner.codingcontract.md) | Coding Contract API |
|
||||||
|
@ -3,7 +3,7 @@ import type { BladeMultName, BladeSkillName } from "@enums";
|
|||||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||||
import { Bladeburner } from "./Bladeburner";
|
import { Bladeburner } from "./Bladeburner";
|
||||||
import { Availability } from "./Types";
|
import { Availability } from "./Types";
|
||||||
import { PositiveInteger, isPositiveInteger } from "../types";
|
import { PositiveInteger, PositiveNumber, isPositiveInteger } from "../types";
|
||||||
import { PartialRecord, getRecordEntries } from "../Types/Record";
|
import { PartialRecord, getRecordEntries } from "../Types/Record";
|
||||||
|
|
||||||
interface SkillParams {
|
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
|
* 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.
|
* 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
|
* 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:
|
* infeasible to calculate the cost in that way. We need to find the closed forms of:
|
||||||
*
|
*
|
||||||
@ -52,7 +49,7 @@ export class Skill {
|
|||||||
* Or:
|
* Or:
|
||||||
*
|
*
|
||||||
* [2]:
|
* [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:
|
* 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
|
* Or
|
||||||
*
|
*
|
||||||
* [4]:
|
* [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.
|
* 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]:
|
* 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(
|
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 }> {
|
canUpgrade(bladeburner: Bladeburner, count = 1): Availability<{ cost: number }> {
|
||||||
const currentLevel = bladeburner.skills[this.name] ?? 0;
|
const currentLevel = bladeburner.skills[this.name] ?? 0;
|
||||||
if (!isPositiveInteger(count)) {
|
if (!isPositiveInteger(count)) {
|
||||||
|
@ -3,12 +3,12 @@ import React, { useState } from "react";
|
|||||||
import { AugmentationName } from "@enums";
|
import { AugmentationName } from "@enums";
|
||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { KEY } from "../../utils/helpers/keyCodes";
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
import { random } from "../utils";
|
|
||||||
import { BlinkingCursor } from "./BlinkingCursor";
|
import { BlinkingCursor } from "./BlinkingCursor";
|
||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import { GameTimer } from "./GameTimer";
|
import { GameTimer } from "./GameTimer";
|
||||||
import { IMinigameProps } from "./IMinigameProps";
|
import { IMinigameProps } from "./IMinigameProps";
|
||||||
import { KeyHandler } from "./KeyHandler";
|
import { KeyHandler } from "./KeyHandler";
|
||||||
|
import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -67,7 +67,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeAnswer(difficulty: Difficulty): string {
|
function makeAnswer(difficulty: Difficulty): string {
|
||||||
const length = random(difficulty.min, difficulty.max);
|
const length = getRandomArbitrary(difficulty.min, difficulty.max);
|
||||||
let answer = "";
|
let answer = "";
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
if (i > 0) answer += " ";
|
if (i > 0) answer += " ";
|
||||||
|
@ -3,12 +3,12 @@ import React, { useState } from "react";
|
|||||||
import { AugmentationName } from "@enums";
|
import { AugmentationName } from "@enums";
|
||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { KEY } from "../../utils/helpers/keyCodes";
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
import { random } from "../utils";
|
|
||||||
import { BlinkingCursor } from "./BlinkingCursor";
|
import { BlinkingCursor } from "./BlinkingCursor";
|
||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import { GameTimer } from "./GameTimer";
|
import { GameTimer } from "./GameTimer";
|
||||||
import { IMinigameProps } from "./IMinigameProps";
|
import { IMinigameProps } from "./IMinigameProps";
|
||||||
import { KeyHandler } from "./KeyHandler";
|
import { KeyHandler } from "./KeyHandler";
|
||||||
|
import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -35,7 +35,7 @@ function generateLeftSide(difficulty: Difficulty): string {
|
|||||||
if (Player.hasAugmentation(AugmentationName.WisdomOfAthena, true)) {
|
if (Player.hasAugmentation(AugmentationName.WisdomOfAthena, true)) {
|
||||||
options.splice(0, 1);
|
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++) {
|
for (let i = 0; i < length; i++) {
|
||||||
str += options[Math.floor(Math.random() * options.length)];
|
str += options[Math.floor(Math.random() * options.length)];
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ import { Paper, Typography } from "@mui/material";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { AugmentationName } from "@enums";
|
import { AugmentationName } from "@enums";
|
||||||
import { Player } from "@player";
|
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 { interpolate } from "./Difficulty";
|
||||||
import { GameTimer } from "./GameTimer";
|
import { GameTimer } from "./GameTimer";
|
||||||
import { IMinigameProps } from "./IMinigameProps";
|
import { IMinigameProps } from "./IMinigameProps";
|
||||||
import { KeyHandler } from "./KeyHandler";
|
import { KeyHandler } from "./KeyHandler";
|
||||||
|
import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -77,7 +78,7 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
|
|||||||
function generateCode(difficulty: Difficulty): Arrow[] {
|
function generateCode(difficulty: Difficulty): Arrow[] {
|
||||||
const arrows: Arrow[] = [leftArrowSymbol, rightArrowSymbol, upArrowSymbol, downArrowSymbol];
|
const arrows: Arrow[] = [leftArrowSymbol, rightArrowSymbol, upArrowSymbol, downArrowSymbol];
|
||||||
const code: Arrow[] = [];
|
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())];
|
let arrow = arrows[Math.floor(4 * Math.random())];
|
||||||
while (arrow === code[code.length - 1]) arrow = arrows[Math.floor(4 * Math.random())];
|
while (arrow === code[code.length - 1]) arrow = arrows[Math.floor(4 * Math.random())];
|
||||||
code.push(arrow);
|
code.push(arrow);
|
||||||
|
@ -4,12 +4,12 @@ import { Box, Paper, Typography } from "@mui/material";
|
|||||||
import { AugmentationName } from "@enums";
|
import { AugmentationName } from "@enums";
|
||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { Settings } from "../../Settings/Settings";
|
import { Settings } from "../../Settings/Settings";
|
||||||
import { random } from "../utils";
|
|
||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import { GameTimer } from "./GameTimer";
|
import { GameTimer } from "./GameTimer";
|
||||||
import { IMinigameProps } from "./IMinigameProps";
|
import { IMinigameProps } from "./IMinigameProps";
|
||||||
import { KeyHandler } from "./KeyHandler";
|
import { KeyHandler } from "./KeyHandler";
|
||||||
import { isPositiveInteger } from "../../types";
|
import { isPositiveInteger } from "../../types";
|
||||||
|
import { getRandomArbitrary } from "../../utils/helpers/getRandomArbitrary";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -200,7 +200,7 @@ function generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] {
|
|||||||
|
|
||||||
function generateWires(difficulty: Difficulty): Wire[] {
|
function generateWires(difficulty: Difficulty): Wire[] {
|
||||||
const wires = [];
|
const wires = [];
|
||||||
const numWires = random(difficulty.wiresmin, difficulty.wiresmax);
|
const numWires = getRandomArbitrary(difficulty.wiresmin, difficulty.wiresmax);
|
||||||
for (let i = 0; i < numWires; i++) {
|
for (let i = 0; i < numWires; i++) {
|
||||||
const wireColors = [colors[Math.floor(Math.random() * colors.length)]];
|
const wireColors = [colors[Math.floor(Math.random() * colors.length)]];
|
||||||
if (Math.random() < 0.15) {
|
if (Math.random() < 0.15) {
|
||||||
|
@ -2,10 +2,6 @@ import { KEY } from "../utils/helpers/keyCodes";
|
|||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { AugmentationName } from "@enums";
|
import { AugmentationName } from "@enums";
|
||||||
|
|
||||||
export function random(min: number, max: number): number {
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const upArrowSymbol = "↑";
|
export const upArrowSymbol = "↑";
|
||||||
export const downArrowSymbol = "↓";
|
export const downArrowSymbol = "↓";
|
||||||
export const leftArrowSymbol = "←";
|
export const leftArrowSymbol = "←";
|
||||||
|
@ -67,6 +67,7 @@ export const helpers = {
|
|||||||
number,
|
number,
|
||||||
positiveInteger,
|
positiveInteger,
|
||||||
positiveSafeInteger,
|
positiveSafeInteger,
|
||||||
|
positiveNumber,
|
||||||
scriptArgs,
|
scriptArgs,
|
||||||
runOptions,
|
runOptions,
|
||||||
spawnOptions,
|
spawnOptions,
|
||||||
|
@ -669,6 +669,9 @@ export const RamCosts: RamCostTree<NSFull> = {
|
|||||||
factionGains: 0,
|
factionGains: 0,
|
||||||
companyGains: 0,
|
companyGains: 0,
|
||||||
},
|
},
|
||||||
|
bladeburner: {
|
||||||
|
skillMaxUpgradeCount: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ import { findEnumMember } from "../utils/helpers/enum";
|
|||||||
import { getEnumHelper } from "../utils/EnumHelper";
|
import { getEnumHelper } from "../utils/EnumHelper";
|
||||||
import { CompanyPositions } from "../Company/CompanyPositions";
|
import { CompanyPositions } from "../Company/CompanyPositions";
|
||||||
import { findCrime } from "../Crime/CrimeHelpers";
|
import { findCrime } from "../Crime/CrimeHelpers";
|
||||||
|
import { Skills } from "../Bladeburner/data/Skills";
|
||||||
|
|
||||||
export function NetscriptFormulas(): InternalAPI<IFormulas> {
|
export function NetscriptFormulas(): InternalAPI<IFormulas> {
|
||||||
const checkFormulasAccess = function (ctx: NetscriptContext): void {
|
const checkFormulasAccess = function (ctx: NetscriptContext): void {
|
||||||
@ -427,6 +428,22 @@ export function NetscriptFormulas(): InternalAPI<IFormulas> {
|
|||||||
return calculateCompanyWorkStats(person, company, position, favor);
|
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
|
// Removed functions
|
||||||
|
18
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
18
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -5110,6 +5110,22 @@ interface GangFormulas {
|
|||||||
ascensionMultiplier(points: number): number;
|
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
|
* Formulas API
|
||||||
* @remarks
|
* @remarks
|
||||||
@ -5134,6 +5150,8 @@ export interface Formulas {
|
|||||||
gang: GangFormulas;
|
gang: GangFormulas;
|
||||||
/** Work formulas */
|
/** Work formulas */
|
||||||
work: WorkFormulas;
|
work: WorkFormulas;
|
||||||
|
/** Bladeburner formulas */
|
||||||
|
bladeburner: BladeburnerFormulas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
7
src/utils/helpers/getRandomArbitrary.ts
Normal file
7
src/utils/helpers/getRandomArbitrary.ts
Normal file
@ -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;
|
||||||
|
}
|
97
test/jest/Netscript/Bladeburner.test.ts
Normal file
97
test/jest/Netscript/Bladeburner.test.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user