143 lines
5.2 KiB
TypeScript
143 lines
5.2 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
completability,
|
|
generateContractBoard,
|
|
isCompletable,
|
|
type BoardInput,
|
|
type FleetMember,
|
|
} from "../src/index";
|
|
|
|
const { config } = content;
|
|
const loc = (id: string) => content.location(id)!;
|
|
const allIds = new Set(content.locations.map((l) => l.id));
|
|
const maxed = Object.fromEntries(content.skills.map((k) => [k.id, k.maxLevel]));
|
|
const avatarCourier: FleetMember = { modelId: "courier", skills: {} };
|
|
|
|
/** Assorted fleets: lone ships, mixed crews, upgrades, skills. */
|
|
const FLEETS: FleetMember[][] = [
|
|
[avatarCourier],
|
|
[avatarCourier, { modelId: "clipper", skills: { "expensive-cargo": 1 } }],
|
|
[{ modelId: "lighter", skills: { "heavy-cargo": 2 }, upgrades: { "cargo-bay": 2 } }],
|
|
[{ modelId: "freighter", skills: { "long-haul": 2, "dangerous-cargo": 1 } }],
|
|
[{ modelId: "bulk-hauler", skills: maxed }, avatarCourier],
|
|
];
|
|
|
|
describe("boards keep only completable jobs (Step 19.2, decision 0.5.114)", () => {
|
|
it("never offers a job the fleet can't complete, over 1,000 boards", () => {
|
|
let boards = 0;
|
|
const origins = ["earth", "moon", "mars", "ceres-station", "jupiter-cloud-platform"];
|
|
for (const [f, fleet] of FLEETS.entries()) {
|
|
for (const origin of origins) {
|
|
for (let periodIndex = 0; periodIndex < 40; periodIndex++) {
|
|
const discoveredIds = periodIndex % 2 ? allIds : new Set(["earth", "moon", "mars"]);
|
|
const input: BoardInput = {
|
|
location: loc(origin),
|
|
discoveredIds,
|
|
periodIndex,
|
|
worldSeed: 1000 + f,
|
|
fleet,
|
|
};
|
|
for (const c of generateContractBoard(input, content)) {
|
|
const { completable, failures } = completability(c, fleet, discoveredIds, content);
|
|
expect(completable, `${c.id} ${JSON.stringify(failures)}`).toBe(true);
|
|
}
|
|
boards++;
|
|
}
|
|
}
|
|
}
|
|
expect(boards).toBeGreaterThanOrEqual(1000);
|
|
});
|
|
|
|
it("gives a level-0 avatar in a Courier at Earth full boards of Standard Moon jobs it fits", () => {
|
|
for (let periodIndex = 0; periodIndex < 200; periodIndex++) {
|
|
const board = generateContractBoard(
|
|
{
|
|
location: loc("earth"),
|
|
discoveredIds: allIds,
|
|
periodIndex,
|
|
worldSeed: 7,
|
|
fleet: [avatarCourier],
|
|
},
|
|
content,
|
|
);
|
|
expect(board).toHaveLength(config.contractBoardSize);
|
|
for (const c of board) {
|
|
expect(c.cargoTypeId).toBe("standard");
|
|
expect(c.massTonnes).toBeLessThanOrEqual(content.shipModel("courier")!.cargoCapacityTonnes);
|
|
expect(c.destinationId).toBe("moon");
|
|
}
|
|
}
|
|
});
|
|
|
|
it("stops at the attempt limit with a smaller board when the fleet can do almost nothing", () => {
|
|
// From Mars a Courier reaches nowhere: nothing is completable.
|
|
const nothing = generateContractBoard(
|
|
{
|
|
location: loc("mars"),
|
|
discoveredIds: allIds,
|
|
periodIndex: 3,
|
|
worldSeed: 7,
|
|
fleet: [avatarCourier],
|
|
},
|
|
content,
|
|
);
|
|
expect(nothing).toEqual([]);
|
|
// Only Earth known: a Courier can take nothing but the odd first contact to the
|
|
// Moon, which random candidates hit only sometimes, so some boards come up short.
|
|
const known = new Set(["earth"]);
|
|
let short = 0;
|
|
for (let periodIndex = 0; periodIndex < 50; periodIndex++) {
|
|
const board = generateContractBoard(
|
|
{
|
|
location: loc("earth"),
|
|
discoveredIds: known,
|
|
periodIndex,
|
|
worldSeed: 7,
|
|
fleet: [avatarCourier],
|
|
},
|
|
content,
|
|
);
|
|
expect(board.length).toBeLessThanOrEqual(config.contractBoardSize);
|
|
if (board.length < config.contractBoardSize) short++;
|
|
for (const c of board) {
|
|
expect(c.destinationId).toBe("moon");
|
|
expect(isCompletable(c, [avatarCourier], known, content)).toBe(true);
|
|
}
|
|
}
|
|
expect(short).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("gives identical boards for identical inputs", () => {
|
|
for (const fleet of FLEETS) {
|
|
const input: BoardInput = {
|
|
location: loc("earth"),
|
|
discoveredIds: allIds,
|
|
periodIndex: 12,
|
|
worldSeed: 99,
|
|
fleet,
|
|
};
|
|
expect(generateContractBoard(input, content)).toEqual(generateContractBoard(input, content));
|
|
}
|
|
});
|
|
|
|
it("filters first contacts the same way", () => {
|
|
// Only Earth and the Moon known: a Clipper may explore Mars or Venus, a Courier nowhere.
|
|
const known = new Set(["earth", "moon"]);
|
|
let clipperFirstContacts = 0;
|
|
for (let periodIndex = 0; periodIndex < 100; periodIndex++) {
|
|
const input = { location: loc("earth"), discoveredIds: known, periodIndex, worldSeed: 5 };
|
|
const courierBoard = generateContractBoard({ ...input, fleet: [avatarCourier] }, content);
|
|
expect(courierBoard.some((c) => c.firstContact)).toBe(false);
|
|
const clipper: FleetMember = { modelId: "clipper", skills: { "long-haul": 1 } };
|
|
for (const c of generateContractBoard({ ...input, fleet: [clipper] }, content)) {
|
|
if (c.firstContact) {
|
|
clipperFirstContacts++;
|
|
expect(isCompletable(c, [clipper], known, content)).toBe(true);
|
|
}
|
|
}
|
|
}
|
|
expect(clipperFirstContacts).toBeGreaterThan(0);
|
|
});
|
|
});
|