import { TestCase } from "./framework/test_case.js"; import { buildingBuildCost, buildingRefund, buildingUpkeep, buildingEffectValue, governmentChangeCost, prerequisitesMet, defenseFactors, defenseMultiplier, unitDefense, productionFactors, productionFromFactors, } from "../shared/rules.js"; import { effectValue, TECHNOLOGIES, TERRAIN_TILES } from "../shared/data.js"; function proto(overrides = {}) { return { baseCost: 0, costAlpha: 1.5, refundFraction: 0.5, effects: [], ...overrides, }; } function terrain(type) { return TERRAIN_TILES.find((t) => t.terrainType === type); } export class RulesTest extends TestCase { test_build_cost_grows_with_level_and_ignores_gdp() { const building = proto({ baseCost: 100, costAlpha: 2.0 }); this.assertApprox(buildingBuildCost(building, 0), 100); this.assertApprox(buildingBuildCost(building, 1), 200); this.assertApprox(buildingBuildCost(building, 2), 400); this.assertApprox(buildingBuildCost(building, 3), 100 * Math.pow(2.0, 3)); } test_upkeep_and_refund_follow_the_build_cost() { const building = proto({ baseCost: 100, costAlpha: 2.0 }); this.assertEqual(buildingUpkeep(building, 0), 0); this.assertApprox(buildingUpkeep(building, 1), 1); this.assertApprox(buildingUpkeep(building, 2), 2); this.assertApprox(buildingUpkeep(building, 3), 4); const refunding = proto({ baseCost: 100, refundFraction: 0.5 }); this.assertApprox(buildingRefund(refunding, 1), 50); this.assertApprox(buildingRefund(refunding, 2), 75); } test_effect_is_linear_or_sublinear() { this.assertApprox(effectValue({ baseValue: 10, exponent: 1 }, 3), 30); this.assertEqual(effectValue({ baseValue: 10, exponent: 1 }, 0), 0); this.assertApprox(effectValue({ baseValue: 10, exponent: 0.5 }, 4), 20); this.assertLess( effectValue({ baseValue: 10, exponent: 0.5 }, 4), effectValue({ baseValue: 10, exponent: 1 }, 4) ); } test_building_effect_value_sums_matching_stats() { const building = proto({ effects: [ { stat: "production", baseValue: 0.04, exponent: 0.75 }, { stat: "production", baseValue: 0.01, exponent: 1.0 }, { stat: "culture", baseValue: 1.0, exponent: 1.0 }, ], }); this.assertApprox(buildingEffectValue(building, "production", 1), 0.05); this.assertApprox(buildingEffectValue(building, "culture", 1), 1.0); this.assertEqual(buildingEffectValue(building, "research", 1), 0); this.assertEqual(buildingEffectValue(building, "production", 0), 0); } test_government_change_cost() { const government = { baseCost: 100, costGdpFraction: 0.01 }; this.assertApprox(governmentChangeCost(government, 1000), 110); this.assertApprox(governmentChangeCost(government, 10000), 200); this.assertApprox(governmentChangeCost(government, -50), 100); } test_prerequisites_met() { const root = TECHNOLOGIES.find((t) => t.id === "industrial_automation"); this.assertEmpty(root.prerequisites); this.assertTrue(prerequisitesMet(root, new Set(), TECHNOLOGIES), "a root tech needs nothing"); const space = TECHNOLOGIES.find((t) => t.id === "space_program"); const index = (id) => TECHNOLOGIES.findIndex((t) => t.id === id); this.assertFalse(prerequisitesMet(space, new Set(), TECHNOLOGIES), "missing all prerequisites"); this.assertFalse( prerequisitesMet(space, new Set([index("industrial_automation")]), TECHNOLOGIES), "still missing one" ); this.assertTrue( prerequisitesMet( space, new Set([index("industrial_automation"), index("quantum_computing")]), TECHNOLOGIES ), "all prerequisites met" ); } test_defense_factors_and_multiplier() { const mountain = terrain("Mountain"); const plains = terrain("Land"); this.assertSize(defenseFactors(plains, false), 0, "open land defends normally"); this.assertSize(defenseFactors(null, false), 0, "no tile, no factor"); const mountainFactors = defenseFactors(mountain, false); this.assertSize(mountainFactors, 1); this.assertEqual(mountainFactors[0].label, "Mountain"); this.assertApprox(mountainFactors[0].bonus, 1.0); const cityFactors = defenseFactors(mountain, true); this.assertSize(cityFactors, 2); this.assertEqual(cityFactors[1].label, "City"); this.assertApprox(cityFactors[1].bonus, 0.5); this.assertApprox(defenseMultiplier(plains, false), 1.0); this.assertApprox(defenseMultiplier(terrain("Forest"), false), 1.33); this.assertApprox(defenseMultiplier(mountain, false), 2.0); this.assertApprox(defenseMultiplier(mountain, true), 2.5); this.assertApprox(unitDefense(100, mountain, false), 200); this.assertApprox(unitDefense(100, mountain, true), 250); this.assertApprox(unitDefense(100, null, false), 100); this.assertApprox(terrain("Forest").defenseBonus, 0.33); this.assertApprox(terrain("Hills").defenseBonus, 0.33); this.assertApprox(mountain.defenseBonus, 1.0); this.assertApprox(plains.defenseBonus, 0); } test_production_factors_and_application() { const plains = terrain("Land"); const base = productionFactors(plains); this.assertSize(base, 1); this.assertEqual(base[0].label, "Land"); this.assertApprox(base[0].multiplier, 1); const city = productionFactors(plains, { cityKind: "city" }); this.assertSize(city, 2); this.assertEqual(city[1].label, "City"); this.assertApprox(city[1].multiplier, 2); this.assertApprox(productionFactors(plains, { cityKind: "capital" })[1].multiplier, 2.5); const near = productionFactors(plains, { nextToCity: true }); this.assertEqual(near[1].label, "Near city"); this.assertApprox(near[1].multiplier, 1.3); const war = productionFactors(plains, { pillagePenalty: 0.7, battleDeficit: 500 }); this.assertEqual(war[1].label, "Pillaging"); this.assertApprox(war[1].multiplier, 0.7); this.assertEqual(war[2].label, "Battle damage"); this.assertApprox(war[2].amount, -500); this.assertApprox( productionFromFactors(10000, [{ multiplier: 2 }, { amount: -500 }]), 19500 ); this.assertEqual(productionFromFactors(100, [{ amount: -500 }]), 0); } }