- Five resources (energy, steel, food, luxury, high-tech) with per-city stores, a world market, delivery and material upkeep. - Resource-producing tile works built outside cities; every nation opens with the works its economy needs. - Real game-icons.net art replacing the dummy icons for the new resources and works, with the credits updated.
214 lines
8.0 KiB
JavaScript
214 lines
8.0 KiB
JavaScript
import { TestCase } from "./framework/test_case.js";
|
|
import {
|
|
buildingBuildCost,
|
|
buildingRefund,
|
|
buildingUpkeep,
|
|
buildingEffectValue,
|
|
governmentChangeCost,
|
|
governmentEffects,
|
|
technologyEffects,
|
|
prerequisitesMet,
|
|
defenseFactors,
|
|
defenseMultiplier,
|
|
unitDefense,
|
|
productionFactors,
|
|
productionFromFactors,
|
|
} from "../shared/rules.js";
|
|
import {
|
|
effectValue,
|
|
GOVERNMENTS,
|
|
TECHNOLOGIES,
|
|
TERRAIN_TILES,
|
|
EFFECT_PRODUCTION,
|
|
EFFECT_RESEARCH,
|
|
EFFECT_CULTURE,
|
|
} from "../shared/data.js";
|
|
|
|
function proto(overrides = {}) {
|
|
return {
|
|
baseCost: 0,
|
|
costAlpha: 1.5,
|
|
refundFraction: 0.5,
|
|
effects: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
export class RulesTest extends TestCase {
|
|
test_build_cost_grows_exponentially_with_level() {
|
|
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);
|
|
}
|
|
|
|
test_build_cost_is_fixed_and_independent_of_gdp() {
|
|
const building = proto({ baseCost: 100, costAlpha: 1.5 });
|
|
this.assertApprox(buildingBuildCost(building, 0), 100);
|
|
this.assertApprox(buildingBuildCost(building, 3), 100 * Math.pow(1.5, 3));
|
|
}
|
|
|
|
test_upkeep_is_one_percent_of_the_levels_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);
|
|
}
|
|
|
|
test_refund_is_a_fraction_of_the_removed_level() {
|
|
const building = proto({ baseCost: 100, refundFraction: 0.5 });
|
|
this.assertApprox(buildingRefund(building, 1), 50);
|
|
this.assertApprox(buildingRefund(building, 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_catalogue_effects() {
|
|
const monarchy = GOVERNMENTS.find((g) => g.id === "monarchy");
|
|
this.assertApprox(governmentEffects(monarchy)[EFFECT_PRODUCTION], 0.1);
|
|
this.assertApprox(governmentEffects(monarchy)[EFFECT_CULTURE], 1.0);
|
|
const technocracy = GOVERNMENTS.find((g) => g.id === "technocracy");
|
|
this.assertGreater(
|
|
governmentEffects(technocracy)[EFFECT_RESEARCH],
|
|
governmentEffects(monarchy)[EFFECT_RESEARCH] || 0
|
|
);
|
|
const method = TECHNOLOGIES.find((t) => t.id === "scientific_method");
|
|
this.assertApprox(technologyEffects(method)[EFFECT_RESEARCH], 1.0);
|
|
this.assertApprox(technologyEffects(method)[EFFECT_PRODUCTION], 0.02);
|
|
}
|
|
|
|
test_prerequisites_met() {
|
|
const space = TECHNOLOGIES.find((t) => t.id === "space_program");
|
|
this.assertFalse(prerequisitesMet(space, new Set(), TECHNOLOGIES));
|
|
const index = (id) => TECHNOLOGIES.findIndex((t) => t.id === id);
|
|
this.assertFalse(
|
|
prerequisitesMet(space, new Set([index("scientific_method"), index("industrial_automation")]), TECHNOLOGIES)
|
|
);
|
|
this.assertTrue(
|
|
prerequisitesMet(
|
|
space,
|
|
new Set([index("scientific_method"), index("industrial_automation"), index("quantum_computing")]),
|
|
TECHNOLOGIES
|
|
)
|
|
);
|
|
}
|
|
|
|
test_root_technology_has_no_prerequisites() {
|
|
const method = TECHNOLOGIES.find((t) => t.id === "scientific_method");
|
|
this.assertEmpty(method.prerequisites);
|
|
this.assertTrue(prerequisitesMet(method, new Set(), TECHNOLOGIES));
|
|
}
|
|
|
|
test_defense_factors_come_from_the_tile_and_its_city() {
|
|
const mountain = TERRAIN_TILES.find((t) => t.terrainType === "Mountain");
|
|
const plains = TERRAIN_TILES.find((t) => t.terrainType === "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);
|
|
}
|
|
|
|
test_defense_multiplier_adds_terrain_and_city_bonuses() {
|
|
const forest = TERRAIN_TILES.find((t) => t.terrainType === "Forest");
|
|
const mountain = TERRAIN_TILES.find((t) => t.terrainType === "Mountain");
|
|
const plains = TERRAIN_TILES.find((t) => t.terrainType === "Land");
|
|
this.assertApprox(defenseMultiplier(plains, false), 1.0);
|
|
this.assertApprox(defenseMultiplier(forest, false), 1.33);
|
|
this.assertApprox(defenseMultiplier(mountain, false), 2.0);
|
|
this.assertApprox(defenseMultiplier(mountain, true), 2.5);
|
|
}
|
|
|
|
test_unit_defense_scales_the_base_stat() {
|
|
const mountain = TERRAIN_TILES.find((t) => t.terrainType === "Mountain");
|
|
this.assertApprox(unitDefense(100, mountain, false), 200);
|
|
this.assertApprox(unitDefense(100, mountain, true), 250);
|
|
this.assertApprox(unitDefense(100, null, false), 100);
|
|
}
|
|
|
|
test_terrain_defense_bonuses_match_the_spec() {
|
|
const bonusOf = (type) => {
|
|
const tile = TERRAIN_TILES.find((t) => t.terrainType === type);
|
|
return tile ? tile.defenseBonus : 0;
|
|
};
|
|
this.assertApprox(bonusOf("Forest"), 0.33);
|
|
this.assertApprox(bonusOf("Hills"), 0.33);
|
|
this.assertApprox(bonusOf("Mountain"), 1.0);
|
|
this.assertApprox(bonusOf("Land"), 0);
|
|
}
|
|
|
|
test_production_factors_name_the_terrain_and_settlement() {
|
|
const plains = TERRAIN_TILES.find((t) => t.terrainType === "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);
|
|
|
|
const capital = productionFactors(plains, { cityKind: "capital" });
|
|
this.assertApprox(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);
|
|
}
|
|
|
|
test_production_factors_include_the_marks_of_war() {
|
|
const plains = TERRAIN_TILES.find((t) => t.terrainType === "Land");
|
|
const factors = productionFactors(plains, { pillagePenalty: 0.7, battleDeficit: 500 });
|
|
this.assertEqual(factors[1].label, "Pillaging");
|
|
this.assertApprox(factors[1].multiplier, 0.7);
|
|
this.assertEqual(factors[2].label, "Battle damage");
|
|
this.assertApprox(factors[2].amount, -500);
|
|
}
|
|
|
|
test_production_from_factors_applies_in_order_and_floors_at_zero() {
|
|
this.assertApprox(
|
|
productionFromFactors(10000, [{ multiplier: 2 }, { amount: -500 }]),
|
|
19500
|
|
);
|
|
this.assertEqual(productionFromFactors(100, [{ amount: -500 }]), 0);
|
|
}
|
|
}
|