diff --git a/src/test/ArtifactWinConditionTest.cpp b/src/test/ArtifactWinConditionTest.cpp new file mode 100644 index 0000000..3d60c9f --- /dev/null +++ b/src/test/ArtifactWinConditionTest.cpp @@ -0,0 +1,253 @@ +#include + +#include "catch.hpp" + +#include "ConfigLoader.h" +#include "FactionComponent.h" +#include "Formula.h" +#include "GameConfig.h" +#include "HealthComponent.h" +#include "SchematicChoiceOption.h" +#include "Simulation.h" +#include "StationBodyComponent.h" + +static GameConfig loadConfig() +{ + return ConfigLoader::loadFromDirectory(CONFIG_DIR); +} + +static void killEnemyStations(Simulation& sim) +{ + sim.admin().forEach( + [](entt::entity, StationBodyComponent&, FactionComponent& faction, HealthComponent& health) + { + if (faction.isEnemy) + { + health.hp = 0.0f; + } + }); + sim.tick(); +} + +// Returns the index of the Artifact option in the pending choices, or -1 if absent. +static int findArtifactChoiceIndex(const Simulation& sim) +{ + const auto& choices = sim.getPendingSchematicChoices(); + for (int i = 0; i < static_cast(choices.size()); ++i) + { + if (choices[static_cast(i)].type == SchematicType::Artifact) + { + return i; + } + } + return -1; +} + +// --------------------------------------------------------------------------- +// Config: new artifact fields are parsed correctly (REQ-WIN-ARTIFACT-COUNT) +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: artifact_chance_formula and artifact_win_count are loaded", + "[artifact_win]") +{ + const GameConfig cfg = loadConfig(); + CHECK(cfg.world.artifacts.artifactWinCount == 3); + // 0.05 * x at x=2 should be 0.1 + CHECK(cfg.world.artifacts.artifactChanceFormula.evaluate(2.0) == Approx(0.1)); +} + +// --------------------------------------------------------------------------- +// Simulation initial state +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: artifact count is 0 and isWon is false at game start", + "[artifact_win]") +{ + const Simulation sim(loadConfig()); + CHECK(sim.artifactCount() == 0); + CHECK_FALSE(sim.isWon()); +} + +// --------------------------------------------------------------------------- +// Artifact option presence in schematic choices +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: artifact option appears when chance formula returns 1", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + + REQUIRE(sim.hasSchematicChoicesPending()); + const auto& choices = sim.getPendingSchematicChoices(); + + const long artifactCount = std::count_if(choices.begin(), choices.end(), + [](const SchematicChoiceOption& opt) { return opt.type == SchematicType::Artifact; }); + CHECK(artifactCount == 1); + // Exactly 2 schematic picks + 1 artifact (or fewer if pool was small) + CHECK(choices.size() <= 3); +} + +TEST_CASE("ArtifactWinCondition: at most 2 schematic options accompany the artifact", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + + REQUIRE(sim.hasSchematicChoicesPending()); + const auto& choices = sim.getPendingSchematicChoices(); + + const long schematicCount = std::count_if(choices.begin(), choices.end(), + [](const SchematicChoiceOption& opt) { return opt.type != SchematicType::Artifact; }); + CHECK(schematicCount <= 2); +} + +TEST_CASE("ArtifactWinCondition: no artifact option when chance formula returns 0", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("0"); + Simulation sim(std::move(cfg)); + + for (int i = 0; i < 20; ++i) + { + killEnemyStations(sim); + if (!sim.hasSchematicChoicesPending()) { continue; } + + CHECK(findArtifactChoiceIndex(sim) == -1); + sim.applySchematicChoice(0); + } +} + +// --------------------------------------------------------------------------- +// Selecting the artifact option +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: selecting artifact increments artifact count", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + + const int index = findArtifactChoiceIndex(sim); + REQUIRE(index >= 0); + + sim.applySchematicChoice(index); + + CHECK(sim.artifactCount() == 1); +} + +TEST_CASE("ArtifactWinCondition: selecting a non-artifact option does not increment artifact count", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + + // Pick the first non-artifact choice. + const auto& choices = sim.getPendingSchematicChoices(); + const auto it = std::find_if(choices.begin(), choices.end(), + [](const SchematicChoiceOption& opt) { return opt.type != SchematicType::Artifact; }); + REQUIRE(it != choices.end()); + + sim.applySchematicChoice(static_cast(it - choices.begin())); + + CHECK(sim.artifactCount() == 0); +} + +// --------------------------------------------------------------------------- +// Win condition (REQ-WIN-ARTIFACT-COUNT) +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: isWon becomes true when artifact count reaches win count", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + cfg.world.artifacts.artifactWinCount = 1; + Simulation sim(std::move(cfg)); + + CHECK_FALSE(sim.isWon()); + + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + + const int index = findArtifactChoiceIndex(sim); + REQUIRE(index >= 0); + sim.applySchematicChoice(index); + + CHECK(sim.isWon()); +} + +TEST_CASE("ArtifactWinCondition: isWon stays false when artifact count is below win count", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + cfg.world.artifacts.artifactWinCount = 2; + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + sim.applySchematicChoice(findArtifactChoiceIndex(sim)); + + CHECK(sim.artifactCount() == 1); + CHECK_FALSE(sim.isWon()); +} + +TEST_CASE("ArtifactWinCondition: isWon becomes true after collecting required number of artifacts", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + cfg.world.artifacts.artifactWinCount = 2; + Simulation sim(std::move(cfg)); + + for (int i = 0; i < 2; ++i) + { + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + const int index = findArtifactChoiceIndex(sim); + REQUIRE(index >= 0); + sim.applySchematicChoice(index); + } + + CHECK(sim.artifactCount() == 2); + CHECK(sim.isWon()); +} + +// --------------------------------------------------------------------------- +// reset() restores initial state (REQ-CFG-RELOAD) +// --------------------------------------------------------------------------- + +TEST_CASE("ArtifactWinCondition: reset clears artifact count and win state", + "[artifact_win]") +{ + GameConfig cfg = loadConfig(); + cfg.world.artifacts.artifactChanceFormula = Formula::compile("1"); + cfg.world.artifacts.artifactWinCount = 1; + Simulation sim(std::move(cfg)); + + killEnemyStations(sim); + REQUIRE(sim.hasSchematicChoicesPending()); + sim.applySchematicChoice(findArtifactChoiceIndex(sim)); + REQUIRE(sim.isWon()); + + sim.reset(); + + CHECK(sim.artifactCount() == 0); + CHECK_FALSE(sim.isWon()); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 79c0bdd..2887bac 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -21,4 +21,5 @@ add_files( ShipModuleTest.cpp ThreatCostCalculatorTest.cpp RecipeSchematicTest.cpp + ArtifactWinConditionTest.cpp )