#include "../s2ga.hpp" #include #include #include #include #include #include #include #include using namespace Catch; using namespace s2ga; TEST_CASE("(dummy)", "[test]") { enum State { HUNGRY, HAS_FOOD, HAS_HOUSE, HAS_CLOTHES, HAS_WOOD, HAS_TOOLS, HAS_FABRIC, HAS_ANIMAL_SKIN, HAS_RAW_FOOD, IS_WET, HAS_FIRE, IS_RAINING }; action_list actions = { { .name = "COLLECT FRUITS", .cost = 20.0f, .positive_effects = bm(HAS_FOOD), .negative_preconds = bm(IS_RAINING), }, { .name = "EAT", .cost = 0.1f, .negative_effects = bm(HUNGRY, HAS_FOOD), .positive_preconds = bm(HAS_FOOD) }, { .name = "TIME TO FISH", .cost = 0.5f, .positive_effects = bm(HAS_RAW_FOOD), .positive_preconds = bm(HAS_TOOLS), .negative_preconds = bm(IS_WET), }, { .name = "COOK", .cost = 0.25f, .positive_effects = bm(HAS_FOOD), .negative_effects = bm(HAS_RAW_FOOD), .positive_preconds = bm(HAS_RAW_FOOD) }, { .name = "BUILD HOUSE", .cost = 8.0f, .positive_effects = bm(HAS_HOUSE), .negative_effects = bm(HAS_WOOD, IS_WET), .positive_preconds = bm(HAS_TOOLS, HAS_WOOD), .negative_preconds = bm(HAS_HOUSE), }, { .name = "CRAFT TOOLS", .cost = 5.0f, .positive_effects = bm(HAS_TOOLS), .negative_effects = bm(HAS_WOOD), .positive_preconds = bm(HAS_WOOD), }, { .name = "MAKE CLOTHES", .cost = 4.0f, .positive_effects = bm(HAS_CLOTHES), .negative_effects = bm(HAS_FABRIC), .positive_preconds = bm(HAS_FABRIC), .negative_preconds = bm(IS_WET), }, { .name = "HUNT ANIMAL", .cost = 6.0f, .positive_effects = bm(HAS_ANIMAL_SKIN, HAS_RAW_FOOD), .positive_preconds = bm(HAS_TOOLS), .negative_preconds = bm(IS_RAINING), }, { .name = "MAKE FIRE", .cost = 2.0f, .positive_effects = bm(HAS_FIRE), .negative_effects = bm(HAS_WOOD, IS_WET), .positive_preconds = bm(HAS_WOOD), }, { .name = "COLLECT WOOD", .cost = 2.0f, .positive_effects = bm(HAS_WOOD), .negative_preconds = bm(IS_RAINING), }, { .name = "WAIT OUT RAIN", .cost = 5.0f, .negative_effects = bm(IS_RAINING), .positive_preconds = bm(IS_RAINING), }, { .name = "PROCESS ANIMAL SKIN", .cost = 6.0f, .positive_effects = bm(HAS_FABRIC), .negative_effects = bm(HAS_ANIMAL_SKIN), .positive_preconds = bm(HAS_ANIMAL_SKIN) }, }; BENCHMARK("GOAPERS") { goal goal; goal.negative_goals = bm(HUNGRY, IS_WET); goal.positive_goals = bm(HAS_HOUSE, HAS_CLOTHES); genetic_goap goap; goap.set_initial_state(bm(HUNGRY, IS_WET, IS_RAINING)); goap.set_possible_actions(actions); goap.set_goal(goal); for(int i = 0; i < 32; ++i) { auto plan = goap.plan(100, 4); (void)plan; } }; goal goal; goal.negative_goals = bm(HUNGRY, IS_WET); goal.positive_goals = bm(HAS_HOUSE, HAS_CLOTHES); genetic_goap goap; goap.set_initial_state(bm(HUNGRY, IS_WET, IS_RAINING)); goap.set_possible_actions(actions); goap.set_goal(goal); auto plan = goap.plan(100, 4); std::cout << "\n" << std::endl; float cost = 0.0f; for(auto& i: plan) { i.print(); cost += i.cost; } std::cout << std::format("[Total Cost: {}]", cost) << std::endl; if(plan.empty()) { std::cout << "SAD" << std::endl; } /* [WAIT OUT RAIN, 5] Effects: -IS_RAINING Preconditions: +IS_RAINING [COLLECT WOOD, 2] Effects: +HAS_WOOD Preconditions: -IS_RAINING [CRAFT TOOLS, 5] Effects: -HAS_WOOD +HAS_TOOLS Preconditions: +HAS_WOOD [COLLECT WOOD, 2] Effects: +HAS_WOOD Preconditions: -IS_RAINING [HUNT ANIMAL, 6] Effects: +HAS_ANIMAL_SKIN +HAS_RAW_FOOD Preconditions: +HAS_TOOLS -IS_RAINING [PROCESS ANIMAL SKIN, 6] Effects: +HAS_FABRIC -HAS_ANIMAL_SKIN Preconditions: +HAS_ANIMAL_SKIN [BUILD HOUSE, 8] Effects: +HAS_HOUSE -HAS_WOOD -IS_WET Preconditions: -HAS_HOUSE +HAS_WOOD +HAS_TOOLS [MAKE CLOTHES, 4] Effects: +HAS_CLOTHES -HAS_FABRIC Preconditions: +HAS_FABRIC -IS_WET [COOK, 0.25] Effects: +HAS_FOOD -HAS_RAW_FOOD Preconditions: +HAS_RAW_FOOD [EAT, 0.1] Effects: -HUNGRY -HAS_FOOD Preconditions: +HAS_FOOD [Total Cost: 38.35] */ } TEST_CASE("lehmer64 rng", "[test]") { s2ga::lehmer64 rng(0); REQUIRE(rng() != 0); const int N = 100; { std::vector ivalues; rng.seed(4); for(int i = 0; i < N; ++i) { ivalues.emplace_back(rng.random(0, 256)); } rng.seed(4); for(int i = 0; i < N; ++i) { REQUIRE(ivalues[i] == rng.random(0, 256)); } } { std::vector fvalues; rng.seed(5); for(int i = 0; i < N; ++i) { fvalues.emplace_back(rng.random(-256.0f, 256.0f)); } rng.seed(5); for(int i = 0; i < N; ++i) { REQUIRE(fvalues[i] == rng.random(-256.0f, 256.0f)); } } } TEST_CASE("uniform distribution tests", "[test]") { s2ga::lehmer64 rng(42); // Fixed seed for reproducibility SECTION("integer uniformity - chi-squared test") { const int min = 0; const int max = 9; const int num_samples = 1'000'000; const int num_bins = max - min + 1; const double expected = num_samples / static_cast(num_bins); const double critical_value = 16.92; // χ²(0.05, 9) std::vector counts(num_bins, 0); for(int i = 0; i < num_samples; ++i) { int val = rng.random(min, max); ++counts[val - min]; } double chi_sq = 0.0; for(int count: counts) { double diff = count - expected; chi_sq += (diff * diff) / expected; } CHECK(chi_sq < critical_value); } SECTION("floating point uniformity - Kolmogorov-Smirnov test") { const double min = 0.0; const double max = 1.0; const int num_samples = 100'000; const double ks_critical = 1.36 / std::sqrt(num_samples); std::vector samples(num_samples); std::generate(samples.begin(), samples.end(), [&] { return rng.random(min, max); }); std::sort(samples.begin(), samples.end()); double d_plus = 0.0; double d_minus = 0.0; for(size_t i = 0; i < samples.size(); ++i) { double fn = (i + 1.0) / num_samples; double f = samples[i]; d_plus = std::max(d_plus, fn - f); d_minus = std::max(d_minus, f - (i / (num_samples - 1.0))); } double d_stat = std::max(d_plus, d_minus); CHECK(d_stat < ks_critical); } SECTION("edge case coverage") { const int iterations = 10'000; // Test minimum and maximum inclusion bool hit_min = false; bool hit_max = false; for(int i = 0; i < iterations; ++i) { int val = rng.random(1, 10); if(val == 1) hit_min = true; if(val == 10) hit_max = true; } CHECK(hit_min); CHECK(hit_max); // Floating point bounds check for(int i = 0; i < iterations; ++i) { double val = rng.random(0.0, 1.0); CHECK(val >= 0.0); CHECK(val < 1.0); } } } TEST_CASE("random benchmarking", "[benchmark]") { s2ga::lehmer64 rng(0); const int N = 100'000'000; std::mt19937 std_rng(4); std::uniform_int_distribution dist(-512, 512); BENCHMARK("mt19937 100'000'000") { for(int i = 0; i < N; ++i) { (void)dist(std_rng); } }; BENCHMARK("lehmer64 100'000'000") { for(int i = 0; i < N; ++i) { (void)dist(rng); } }; }