Files
Battle-for-Tismo/tests/game_state_technology_test.js
T
adrien e7ee5dbf0e Added the resource economy, its works and their icons
- 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.
2026-09-22 05:27:11 +02:00

200 lines
8.7 KiB
JavaScript

import { TestCase } from "./framework/test_case.js";
import { smallState, stripPorts } from "./framework/helpers.js";
import { TECHNOLOGIES, EFFECT_INFANTRY_ATTACK, EFFECT_CONSTRUCTION_SPEED } from "../shared/data.js";
import { NEWS_TECHNOLOGY } from "../shared/data/relations.js";
export class GameStateTechnologyTest extends TestCase {
index(state, id) {
return state.technologies.findIndex((t) => t.id === id);
}
// Focuses `id` and pours exactly its cost of points into it, the way a nation
// would over time; returns the index.
complete(state, id, civ = 0) {
const index = this.index(state, id);
this.assertTrue(state.requestFocusTechnology(civ, index), `focusing ${id}`);
state._addResearch(civ, state.getTechnologyCost(civ, index));
return index;
}
test_catalogue_links_prerequisites() {
const method = TECHNOLOGIES.find((t) => t.id === "scientific_method");
this.assertEmpty(method.prerequisites);
this.assertGreater(method.cost, 0);
const automation = TECHNOLOGIES.find((t) => t.id === "industrial_automation");
this.assertEqual(automation.prerequisites, ["scientific_method"]);
}
test_every_civilisation_starts_with_nothing_researched() {
const state = smallState();
this.assertSize(state.technologies, TECHNOLOGIES.length);
this.assertFalse(state.isResearched(0, 0));
this.assertEmpty(state.getResearchedIndices(0));
}
test_research_points_fill_the_focused_technology() {
const state = smallState();
const index = this.index(state, "scientific_method");
this.assertTrue(state.requestFocusTechnology(0, index), "the focus is accepted");
const cost = state.getTechnologyCost(0, index);
// Points flow in as they are created; just short of the cost leaves it in
// progress rather than in a bank.
state._addResearch(0, cost - 1);
this.assertFalse(state.isResearched(0, index), "still researching");
this.assertApprox(state.getTechnologyProgress(0), cost - 1, 1e-6);
state._addResearch(0, 1);
this.assertTrue(state.isResearched(0, index), "the technology is discovered");
this.assertEqual(state.getFocusedTechnology(0), -1, "the focus clears");
}
test_switching_focus_keeps_committed_progress() {
const state = smallState();
this.complete(state, "scientific_method");
const weapons = this.index(state, "infantry_weapons");
const armour = this.index(state, "body_armour");
this.assertTrue(state.requestFocusTechnology(0, weapons));
state._addResearch(0, 7);
this.assertApprox(state.getTechnologyProgress(0, weapons), 7, 1e-9);
// Switch focus: the weapons points stay committed.
this.assertTrue(state.requestFocusTechnology(0, armour));
this.assertApprox(state.getTechnologyProgress(0, weapons), 7, 1e-9, "weapons kept its work");
this.assertApprox(state.getTechnologyProgress(0, armour), 0, 1e-9);
state._addResearch(0, 5);
this.assertApprox(state.getTechnologyProgress(0, armour), 5, 1e-9);
// Switching back resumes from the seven points already committed.
this.assertTrue(state.requestFocusTechnology(0, weapons));
this.assertApprox(state.getTechnologyProgress(0, weapons), 7, 1e-9);
this.assertEqual(state.getTechnologyProgress(0), 7, "the focus reports its own progress");
this.assertApprox(state.snapshot(0).techProgress[0][weapons], 7, 1e-9, "progress is shipped");
}
test_nothing_is_researched_without_a_focus() {
const state = smallState();
const index = this.index(state, "scientific_method");
// Points created with nothing focused are simply not stored anywhere.
state._addResearch(0, state.getTechnologyCost(0, index) * 10);
this.assertFalse(state.isResearched(0, index));
this.assertApprox(state.getTechnologyProgress(0), 0, 1e-9);
}
test_focus_rejects_unknown_technology_and_civilisation() {
const state = smallState();
this.assertFalse(state.requestFocusTechnology(0, 999), "unknown technology");
this.assertFalse(state.requestFocusTechnology(999, 0), "unknown civilisation");
}
test_focus_rejects_when_prerequisites_are_missing() {
const state = smallState();
const space = this.index(state, "space_program");
this.assertFalse(state.canResearchTechnology(0, space));
this.assertFalse(state.requestFocusTechnology(0, space));
}
test_prerequisites_unlock_the_tree() {
const state = smallState();
const automation = this.index(state, "industrial_automation");
this.assertFalse(state.requestFocusTechnology(0, automation), "locked before its prerequisite");
this.complete(state, "scientific_method");
this.assertFalse(state.isResearched(0, automation), "a prerequisite is not auto-granted");
this.complete(state, "industrial_automation");
this.assertTrue(state.isResearched(0, automation));
this.complete(state, "quantum_computing");
this.assertTrue(state.requestFocusTechnology(0, this.index(state, "space_program")));
}
test_technology_effects_are_folded_into_gdp() {
const state = smallState();
stripPorts(state, 0);
const before = state.getPlayerGdp(0);
this.complete(state, "scientific_method");
this.assertApprox(state.getPlayerGdp(0), (before * 1.07) / 1.05, 0.01);
}
test_researched_technology_adds_its_research_rate() {
const withTech = smallState();
const without = smallState();
this.complete(withTech, "scientific_method");
this.assertApprox(
withTech.viewerStats(0).researchRate - without.viewerStats(0).researchRate,
1.0,
1e-9
);
}
test_snapshot_exposes_technologies_and_progress() {
const state = smallState();
const index = this.complete(state, "scientific_method");
const snapshot = state.snapshot(0);
this.assertSize(snapshot.technologies, state.technologies.length);
this.assertSize(snapshot.researched, state.civilisations.length);
this.assertTrue(snapshot.researched[0].includes(index));
this.assertEmpty(snapshot.researched[1]);
}
test_repeatable_research_costs_two_percent_more_each_level() {
const state = smallState();
state.researched.get(0).add(this.index(state, "scientific_method"));
const index = this.index(state, "infantry_weapons");
const base = state.getTechnologyCost(0, index);
this.assertTrue(state.requestFocusTechnology(0, index), "first level");
state._addResearch(0, base);
this.assertEqual(state.getTechnologyRepeatCount(0, index), 1);
this.assertApprox(
state.getTechnologyCost(0, index),
base * 1.02,
1e-6,
"the next level costs 2% more"
);
this.assertTrue(state.requestFocusTechnology(0, index), "second level");
state._addResearch(0, state.getTechnologyCost(0, index));
this.assertEqual(state.getTechnologyRepeatCount(0, index), 2);
this.assertApprox(state.getTechnologyCost(0, index), base * 1.02 * 1.02, 1e-6);
}
test_repeatable_effects_stack_with_each_level() {
const state = smallState();
state.researched.get(0).add(this.index(state, "scientific_method"));
const weapons = this.index(state, "infantry_weapons");
const construction = this.index(state, "construction_technology");
for (let level = 0; level < 2; level++) {
state.requestFocusTechnology(0, weapons);
state._addResearch(0, state.getTechnologyCost(0, weapons));
}
state.requestFocusTechnology(0, construction);
state._addResearch(0, state.getTechnologyCost(0, construction));
const modifiers = state.getCivModifiers(0);
this.assertApprox(modifiers[EFFECT_INFANTRY_ATTACK], 0.02, 1e-9, "two levels stack");
this.assertApprox(modifiers[EFFECT_CONSTRUCTION_SPEED], 0.01, 1e-9);
this.assertApprox(state.constructionSpeedMultiplier(0), 1 / 1.01, 1e-9);
}
test_repeatable_research_notifies_without_news() {
const state = smallState();
this.complete(state, "scientific_method");
this.complete(state, "body_armour");
const notices = state.news.filter((entry) => entry.type === NEWS_TECHNOLOGY);
const normalNotice = notices.find((entry) => entry.technology === "scientific_method");
const repeatNotice = notices.find((entry) => entry.technology === "body_armour");
this.assertNotNull(normalNotice);
this.assertFalse(!!normalNotice.repeatable, "a discovery is a real news event");
this.assertNotNull(repeatNotice);
this.assertTrue(repeatNotice.repeatable, "a repeat level is only a notice");
}
test_repeatable_technologies_have_a_theme() {
for (const proto of TECHNOLOGIES) {
this.assertTrue(
typeof proto.theme === "string" && proto.theme.length > 0,
`${proto.id} is filed under a theme`
);
}
}
test_research_costs_a_hundred_times_the_catalogue_price() {
const state = smallState();
const index = this.index(state, "scientific_method");
const proto = TECHNOLOGIES[index];
this.assertEqual(state.getTechnologyCost(0, index), proto.cost * 100);
}
}