mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-04 06:37:49 +02:00
336 lines
13 KiB
TypeScript
336 lines
13 KiB
TypeScript
import { PositiveInteger } from "../../src/types";
|
|
import { Corporation } from "../../src/Corporation/Corporation";
|
|
import { CorpUpgrades } from "../../src/Corporation/data/CorporationUpgrades";
|
|
import {
|
|
calculateMaxAffordableUpgrade,
|
|
calculateUpgradeCost,
|
|
calculateOfficeSizeUpgradeCost,
|
|
} from "../../src/Corporation/helpers";
|
|
import { Player } from "../../src/Player";
|
|
import {
|
|
acceptInvestmentOffer,
|
|
buyBackShares,
|
|
convertAmountString,
|
|
convertPriceString,
|
|
goPublic,
|
|
issueNewShares,
|
|
sellShares,
|
|
} from "../../src/Corporation/Actions";
|
|
import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "./Utilities";
|
|
import { enterBitNode } from "../../src/RedPill";
|
|
import { getDefaultBitNodeOptions } from "../../src/BitNode/BitNodeUtils";
|
|
import type { NSFull } from "../../src/NetscriptFunctions";
|
|
import { CityName, IndustryType } from "../../src/Enums";
|
|
|
|
initGameEnvironment();
|
|
|
|
let corporation: Corporation;
|
|
|
|
function getCorp() {
|
|
if (!Player.corporation) {
|
|
throw new Error("Corporation was not initialized");
|
|
}
|
|
return Player.corporation;
|
|
}
|
|
|
|
function getDivision(divisionName: string) {
|
|
const corp = getCorp();
|
|
const division = corp.divisions.get(divisionName);
|
|
if (!division) {
|
|
throw new Error(`Division ${divisionName} does not exist`);
|
|
}
|
|
return division;
|
|
}
|
|
|
|
function getOffice(divisionName: string, city: CityName) {
|
|
const division = getDivision(divisionName);
|
|
const office = division.offices[city];
|
|
if (!office) {
|
|
throw new Error(`Division ${divisionName} has not expanded to ${city}`);
|
|
}
|
|
return office;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
setupBasicTestingEnvironment();
|
|
enterBitNode(true, Player.bitNodeN, 3, getDefaultBitNodeOptions());
|
|
getNS().corporation.createCorporation("Test", false);
|
|
corporation = getCorp();
|
|
});
|
|
|
|
describe("Formulas", () => {
|
|
describe("helpers.calculateUpgradeCost", () => {
|
|
it("should have fixed formula", () => {
|
|
for (let currentUpgradeLevel = 0; currentUpgradeLevel < 5; currentUpgradeLevel++) {
|
|
Object.values(CorpUpgrades).forEach((upgrade) => {
|
|
corporation.upgrades[upgrade.name].level = currentUpgradeLevel;
|
|
|
|
for (let targetUpgradeLevel = currentUpgradeLevel + 1; targetUpgradeLevel < 6; targetUpgradeLevel++) {
|
|
const calculatedCost = calculateUpgradeCost(
|
|
upgrade.basePrice,
|
|
upgrade.priceMult,
|
|
currentUpgradeLevel,
|
|
targetUpgradeLevel as PositiveInteger,
|
|
);
|
|
expect(calculatedCost).toMatchSnapshot(
|
|
`${upgrade.name}: from ${currentUpgradeLevel} to ${targetUpgradeLevel}`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("helpers.calculateMaxAffordableUpgrade", () => {
|
|
it("should return zero for negative funds", () => {
|
|
corporation.funds = -1;
|
|
|
|
Object.values(CorpUpgrades).forEach((upgrade) => {
|
|
expect(calculateMaxAffordableUpgrade(corporation, upgrade)).toBe(0);
|
|
});
|
|
});
|
|
|
|
it("should return zero for zero funds", () => {
|
|
corporation.funds = 0;
|
|
|
|
Object.values(CorpUpgrades).forEach((upgrade) => {
|
|
expect(calculateMaxAffordableUpgrade(corporation, upgrade)).toBe(0);
|
|
});
|
|
});
|
|
|
|
it("should be in sync with 'calculateUpgradeCost'", () => {
|
|
for (let currentUpgradeLevel = 0; currentUpgradeLevel < 100; currentUpgradeLevel++) {
|
|
Object.values(CorpUpgrades).forEach((upgrade) => {
|
|
corporation.upgrades[upgrade.name].level = currentUpgradeLevel;
|
|
|
|
for (let targetUpgradeLevel = currentUpgradeLevel + 1; targetUpgradeLevel < 100; targetUpgradeLevel++) {
|
|
const calculatedCost = calculateUpgradeCost(
|
|
upgrade.basePrice,
|
|
upgrade.priceMult,
|
|
currentUpgradeLevel,
|
|
targetUpgradeLevel as PositiveInteger,
|
|
);
|
|
corporation.funds = calculatedCost + 1; // +1 for floating point accuracy issues
|
|
expect(calculateMaxAffordableUpgrade(corporation, upgrade)).toEqual(targetUpgradeLevel);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("helpers.calculateOfficeSizeUpgradeCost matches documented formula", () => {
|
|
// for discussion and computation of these test values, see:
|
|
// https://github.com/bitburner-official/bitburner-src/pull/1179#discussion_r1534948725
|
|
it.each([
|
|
{ fromSize: 3, increaseBy: 3, expectedCost: 4360000000.0 },
|
|
{ fromSize: 3, increaseBy: 15, expectedCost: 26093338259.6 },
|
|
{ fromSize: 3, increaseBy: 150, expectedCost: 3553764305895.24902 },
|
|
{ fromSize: 6, increaseBy: 3, expectedCost: 4752400000.0 },
|
|
{ fromSize: 6, increaseBy: 15, expectedCost: 28441738702.964 },
|
|
{ fromSize: 6, increaseBy: 150, expectedCost: 3873603093425.821 },
|
|
{ fromSize: 9, increaseBy: 3, expectedCost: 5180116000.0 },
|
|
{ fromSize: 9, increaseBy: 15, expectedCost: 31001495186.23076 },
|
|
{ fromSize: 9, increaseBy: 150, expectedCost: 4222227371834.145 },
|
|
])(
|
|
"should cost $expectedCost to upgrade office by $increaseBy from size $fromSize",
|
|
({ fromSize, increaseBy, expectedCost }) => {
|
|
expect(calculateOfficeSizeUpgradeCost(fromSize, increaseBy as PositiveInteger)).toBeCloseTo(expectedCost, 1);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("totalShares", () => {
|
|
function expectSharesToAddUp(corp: Corporation) {
|
|
expect(corp.totalShares).toEqual(corp.numShares + corp.investorShares + corp.issuedShares);
|
|
}
|
|
|
|
it("should equal the sum of each kind of shares", () => {
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
it("should be preserved by seed funding", () => {
|
|
const seedFunded = true;
|
|
Player.startCorporation("TestCorp", seedFunded);
|
|
if (!Player.corporation) {
|
|
throw new Error("Player.startCorporation failed to create a corporation.");
|
|
}
|
|
expectSharesToAddUp(Player.corporation);
|
|
});
|
|
it("should be preserved by acceptInvestmentOffer", () => {
|
|
acceptInvestmentOffer(corporation);
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
it("should be preserved by goPublic", () => {
|
|
const numShares = 1e8;
|
|
goPublic(corporation, numShares);
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
it("should be preserved by IssueNewShares", () => {
|
|
const numShares = 1e8;
|
|
goPublic(corporation, numShares);
|
|
corporation.issueNewSharesCooldown = 0;
|
|
issueNewShares(corporation, numShares);
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
it("should be preserved by BuyBackShares", () => {
|
|
const numShares = 1e8;
|
|
goPublic(corporation, numShares);
|
|
buyBackShares(corporation, numShares);
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
it("should be preserved by SellShares", () => {
|
|
const numShares = 1e8;
|
|
goPublic(corporation, numShares);
|
|
corporation.shareSaleCooldown = 0;
|
|
sellShares(corporation, numShares);
|
|
expectSharesToAddUp(corporation);
|
|
});
|
|
});
|
|
|
|
describe("String conversion", () => {
|
|
describe("convertPriceString", () => {
|
|
it("should pass normally", () => {
|
|
expect(convertPriceString("MP")).toStrictEqual("MP");
|
|
expect(convertPriceString("MP+1")).toStrictEqual("MP+1");
|
|
expect(convertPriceString("MP+MP")).toStrictEqual("MP+MP");
|
|
expect(convertPriceString("123")).toStrictEqual("123");
|
|
expect(convertPriceString("123+456")).toStrictEqual("123+456");
|
|
expect(convertPriceString("1e10")).toStrictEqual("1e10");
|
|
expect(convertPriceString("1E10")).toStrictEqual("1E10");
|
|
});
|
|
it("should throw errors", () => {
|
|
expect(() => convertPriceString("")).toThrow();
|
|
expect(() => convertPriceString("null")).toThrow();
|
|
expect(() => convertPriceString("undefined")).toThrow();
|
|
expect(() => convertPriceString("Infinity")).toThrow();
|
|
expect(() => convertPriceString("abc")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("convertAmountString", () => {
|
|
it("should pass normally", () => {
|
|
expect(convertAmountString("MAX")).toStrictEqual("MAX");
|
|
expect(convertAmountString("PROD")).toStrictEqual("PROD");
|
|
expect(convertAmountString("INV")).toStrictEqual("INV");
|
|
expect(convertAmountString("MAX+1")).toStrictEqual("MAX+1");
|
|
expect(convertAmountString("MAX+MAX")).toStrictEqual("MAX+MAX");
|
|
expect(convertAmountString("MAX+PROD+INV")).toStrictEqual("MAX+PROD+INV");
|
|
expect(convertAmountString("123")).toStrictEqual("123");
|
|
expect(convertAmountString("123+456")).toStrictEqual("123+456");
|
|
expect(convertAmountString("1e10")).toStrictEqual("1e10");
|
|
expect(convertAmountString("1E10")).toStrictEqual("1E10");
|
|
});
|
|
it("should throw errors", () => {
|
|
expect(() => convertAmountString("")).toThrow();
|
|
expect(() => convertAmountString("null")).toThrow();
|
|
expect(() => convertAmountString("undefined")).toThrow();
|
|
expect(() => convertAmountString("Infinity")).toThrow();
|
|
expect(() => convertAmountString("abc")).toThrow();
|
|
});
|
|
});
|
|
});
|
|
|
|
function setUpCorp(ns: NSFull): void {
|
|
const corp = getCorp();
|
|
corp.funds = 1e100;
|
|
corp.storedCycles = 1e10;
|
|
ns.corporation.purchaseUnlock("Smart Supply");
|
|
}
|
|
|
|
function setUpDivision(ns: NSFull, divisionName: string): void {
|
|
const division = getDivision(divisionName);
|
|
division.researchPoints = 1e6;
|
|
for (const researchName of ns.corporation.getConstants().researchNamesBase) {
|
|
ns.corporation.research(divisionName, researchName);
|
|
}
|
|
ns.corporation.hireAdVert(divisionName);
|
|
setUpOffice(ns, divisionName, CityName.Sector12);
|
|
for (const materialName of division.producedMaterials) {
|
|
ns.corporation.sellMaterial(divisionName, CityName.Sector12, materialName, "MAX", "MP");
|
|
}
|
|
}
|
|
|
|
function setUpOffice(ns: NSFull, divisionName: string, city: CityName): void {
|
|
ns.corporation.upgradeOfficeSize(divisionName, city, 4000 - 3);
|
|
for (let i = 0; i < 1000; ++i) {
|
|
ns.corporation.hireEmployee(divisionName, city, "Operations");
|
|
ns.corporation.hireEmployee(divisionName, city, "Engineer");
|
|
ns.corporation.hireEmployee(divisionName, city, "Business");
|
|
ns.corporation.hireEmployee(divisionName, city, "Management");
|
|
}
|
|
const office = getOffice(divisionName, city);
|
|
office.avgCharisma = 75;
|
|
office.avgCreativity = 75;
|
|
office.avgEfficiency = 75;
|
|
office.avgIntelligence = 75;
|
|
}
|
|
|
|
describe("production", () => {
|
|
test("limitMaterialProduction", () => {
|
|
const ns = getNS();
|
|
setUpCorp(ns);
|
|
for (const industry of Object.values(IndustryType)) {
|
|
if (!ns.corporation.getIndustryData(industry).makesMaterials) {
|
|
continue;
|
|
}
|
|
ns.corporation.expandIndustry(industry, industry);
|
|
setUpDivision(ns, industry);
|
|
}
|
|
const corp = getCorp();
|
|
// Process 1 market cycle to purchase input materials.
|
|
corp.process();
|
|
corp.process();
|
|
corp.process();
|
|
corp.process();
|
|
corp.process();
|
|
expect(corp.getNextState()).toStrictEqual("START");
|
|
corp.process();
|
|
corp.process();
|
|
expect(corp.getNextState()).toStrictEqual("PRODUCTION");
|
|
corp.process();
|
|
for (const division of corp.divisions.values()) {
|
|
for (const city of Object.values(CityName)) {
|
|
const warehouse = division.warehouses[city];
|
|
if (!warehouse) {
|
|
continue;
|
|
}
|
|
for (let i = 0; i < division.producedMaterials.length; ++i) {
|
|
const materialName = division.producedMaterials[i];
|
|
// Without a limit, the production amount should always be higher than this value.
|
|
expect(warehouse.materials[materialName].productionAmount).toBeGreaterThan(10);
|
|
// Set a deterministic production limit.
|
|
const productionLimit = i + 1;
|
|
ns.corporation.limitMaterialProduction(division.name, city, materialName, productionLimit);
|
|
expect(warehouse.materials[materialName].productionLimit).toStrictEqual(productionLimit);
|
|
}
|
|
}
|
|
}
|
|
corp.process();
|
|
corp.process();
|
|
corp.process();
|
|
corp.process();
|
|
expect(corp.getNextState()).toStrictEqual("PRODUCTION");
|
|
corp.process();
|
|
for (const division of corp.divisions.values()) {
|
|
for (const city of Object.values(CityName)) {
|
|
const warehouse = division.warehouses[city];
|
|
if (!warehouse) {
|
|
continue;
|
|
}
|
|
for (let i = 0; i < division.producedMaterials.length; ++i) {
|
|
const materialName = division.producedMaterials[i];
|
|
// Verify the deterministic production limit.
|
|
const productionLimit = i + 1;
|
|
expect(warehouse.materials[materialName].productionLimit).toStrictEqual(productionLimit);
|
|
// Verify the stored amount.
|
|
// productionLimit is per second, so we need to multiply it with 10. Due to floating-point imprecision, we
|
|
// need to check with toBeCloseTo instead of toStrictEqual.
|
|
expect(warehouse.materials[materialName].stored).toBeCloseTo(productionLimit * 10, 5);
|
|
// Verify the production amount.
|
|
expect(warehouse.materials[materialName].productionAmount).toStrictEqual(productionLimit);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|