Initial include of fuc2 to eventually replace catch2.
This commit is contained in:
parent
a58439a289
commit
4deda37665
5 changed files with 228 additions and 187 deletions
372
tests/ai.cpp
372
tests/ai.cpp
|
|
@ -1,209 +1,223 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "dbc.hpp"
|
||||
#include "ai/ai.hpp"
|
||||
#include "ai/ai_debug.hpp"
|
||||
#include <iostream>
|
||||
#include "game/rituals.hpp"
|
||||
#include <fuc2/testing.hpp>
|
||||
|
||||
using namespace dbc;
|
||||
using namespace nlohmann;
|
||||
using namespace fuc2;
|
||||
|
||||
TEST_CASE("state and actions work", "[ai]") {
|
||||
enum StateNames {
|
||||
ENEMY_IN_RANGE,
|
||||
ENEMY_DEAD
|
||||
};
|
||||
namespace ai_tests {
|
||||
void test_state_and_actions() {
|
||||
enum StateNames {
|
||||
ENEMY_IN_RANGE,
|
||||
ENEMY_DEAD
|
||||
};
|
||||
|
||||
ai::State goal;
|
||||
ai::State start;
|
||||
std::vector<ai::Action> actions;
|
||||
ai::State goal;
|
||||
ai::State start;
|
||||
std::vector<ai::Action> actions;
|
||||
|
||||
// start off enemy not dead and not in range
|
||||
start[ENEMY_DEAD] = false;
|
||||
start[ENEMY_IN_RANGE] = false;
|
||||
// start off enemy not dead and not in range
|
||||
start[ENEMY_DEAD] = false;
|
||||
start[ENEMY_IN_RANGE] = false;
|
||||
|
||||
// end goal is enemy is dead
|
||||
goal[ENEMY_DEAD] = true;
|
||||
// end goal is enemy is dead
|
||||
goal[ENEMY_DEAD] = true;
|
||||
|
||||
ai::Action move_closer("move_closer", 10);
|
||||
move_closer.needs(ENEMY_IN_RANGE, false);
|
||||
move_closer.effect(ENEMY_IN_RANGE, true);
|
||||
ai::Action move_closer("move_closer", 10);
|
||||
move_closer.needs(ENEMY_IN_RANGE, false);
|
||||
move_closer.effect(ENEMY_IN_RANGE, true);
|
||||
|
||||
REQUIRE(move_closer.can_effect(start));
|
||||
auto after_move_state = move_closer.apply_effect(start);
|
||||
REQUIRE(start[ENEMY_IN_RANGE] == false);
|
||||
REQUIRE(after_move_state[ENEMY_IN_RANGE] == true);
|
||||
REQUIRE(after_move_state[ENEMY_DEAD] == false);
|
||||
// start is clean but after move is dirty
|
||||
REQUIRE(move_closer.can_effect(start));
|
||||
REQUIRE(!move_closer.can_effect(after_move_state));
|
||||
REQUIRE(ai::distance_to_goal(start, after_move_state) == 1);
|
||||
CHECK(move_closer.can_effect(start));
|
||||
auto after_move_state = move_closer.apply_effect(start);
|
||||
CHECK(start[ENEMY_IN_RANGE] == false);
|
||||
CHECK(after_move_state[ENEMY_IN_RANGE] == true);
|
||||
CHECK(after_move_state[ENEMY_DEAD] == false);
|
||||
// start is clean but after move is dirty
|
||||
CHECK(move_closer.can_effect(start));
|
||||
CHECK(!move_closer.can_effect(after_move_state));
|
||||
CHECK(ai::distance_to_goal(start, after_move_state) == 1);
|
||||
|
||||
ai::Action kill_it("kill_it", 10);
|
||||
kill_it.needs(ENEMY_IN_RANGE, true);
|
||||
kill_it.needs(ENEMY_DEAD, false);
|
||||
kill_it.effect(ENEMY_DEAD, true);
|
||||
ai::Action kill_it("kill_it", 10);
|
||||
kill_it.needs(ENEMY_IN_RANGE, true);
|
||||
kill_it.needs(ENEMY_DEAD, false);
|
||||
kill_it.effect(ENEMY_DEAD, true);
|
||||
|
||||
REQUIRE(!kill_it.can_effect(start));
|
||||
REQUIRE(kill_it.can_effect(after_move_state));
|
||||
CHECK(!kill_it.can_effect(start));
|
||||
CHECK(kill_it.can_effect(after_move_state));
|
||||
|
||||
auto after_kill_state = kill_it.apply_effect(after_move_state);
|
||||
REQUIRE(!kill_it.can_effect(after_kill_state));
|
||||
REQUIRE(ai::distance_to_goal(after_move_state, after_kill_state) == 1);
|
||||
auto after_kill_state = kill_it.apply_effect(after_move_state);
|
||||
CHECK(!kill_it.can_effect(after_kill_state));
|
||||
CHECK(ai::distance_to_goal(after_move_state, after_kill_state) == 1);
|
||||
|
||||
kill_it.ignore(ENEMY_IN_RANGE);
|
||||
REQUIRE(kill_it.can_effect(after_move_state));
|
||||
kill_it.ignore(ENEMY_IN_RANGE);
|
||||
CHECK(kill_it.can_effect(after_move_state));
|
||||
|
||||
actions.push_back(kill_it);
|
||||
actions.push_back(move_closer);
|
||||
actions.push_back(kill_it);
|
||||
actions.push_back(move_closer);
|
||||
|
||||
REQUIRE(start != goal);
|
||||
}
|
||||
|
||||
TEST_CASE("basic feature tests", "[ai]") {
|
||||
enum StateNames {
|
||||
ENEMY_IN_RANGE,
|
||||
ENEMY_DEAD
|
||||
};
|
||||
|
||||
ai::State goal;
|
||||
ai::State start;
|
||||
std::vector<ai::Action> actions;
|
||||
|
||||
// start off enemy not dead and not in range
|
||||
start[ENEMY_DEAD] = false;
|
||||
start[ENEMY_IN_RANGE] = false;
|
||||
|
||||
// end goal is enemy is dead
|
||||
goal[ENEMY_DEAD] = true;
|
||||
|
||||
ai::Action move_closer("move_closer", 10);
|
||||
move_closer.needs(ENEMY_IN_RANGE, false);
|
||||
move_closer.effect(ENEMY_IN_RANGE, true);
|
||||
|
||||
ai::Action kill_it("kill_it", 10);
|
||||
kill_it.needs(ENEMY_IN_RANGE, true);
|
||||
// this is duplicated on purpose to confirm that setting
|
||||
// a positive then a negative properly cancels out
|
||||
kill_it.needs(ENEMY_DEAD, true);
|
||||
kill_it.needs(ENEMY_DEAD, false);
|
||||
|
||||
// same thing with effects
|
||||
kill_it.effect(ENEMY_DEAD, false);
|
||||
kill_it.effect(ENEMY_DEAD, true);
|
||||
|
||||
// order seems to matter which is wrong
|
||||
actions.push_back(kill_it);
|
||||
actions.push_back(move_closer);
|
||||
|
||||
auto result = ai::plan_actions(actions, start, goal);
|
||||
REQUIRE(result.complete);
|
||||
|
||||
auto state = start;
|
||||
|
||||
for(auto& action : result.script) {
|
||||
state = action.apply_effect(state);
|
||||
CHECK(start != goal);
|
||||
}
|
||||
|
||||
REQUIRE(state[ENEMY_DEAD]);
|
||||
}
|
||||
void test_basic_features() {
|
||||
enum StateNames {
|
||||
ENEMY_IN_RANGE,
|
||||
ENEMY_DEAD
|
||||
};
|
||||
|
||||
ai::State goal;
|
||||
ai::State start;
|
||||
std::vector<ai::Action> actions;
|
||||
|
||||
TEST_CASE("ai as a module like sound/sprites", "[ai]") {
|
||||
ai::reset();
|
||||
ai::init("tests/ai_fixture.json");
|
||||
// start off enemy not dead and not in range
|
||||
start[ENEMY_DEAD] = false;
|
||||
start[ENEMY_IN_RANGE] = false;
|
||||
|
||||
auto start = ai::load_state("test_start");
|
||||
auto goal = ai::load_state("test_goal");
|
||||
// end goal is enemy is dead
|
||||
goal[ENEMY_DEAD] = true;
|
||||
|
||||
auto a_plan = ai::plan("test1", start, goal);
|
||||
REQUIRE(a_plan.complete);
|
||||
ai::Action move_closer("move_closer", 10);
|
||||
move_closer.needs(ENEMY_IN_RANGE, false);
|
||||
move_closer.effect(ENEMY_IN_RANGE, true);
|
||||
|
||||
auto state = start;
|
||||
for(auto& action : a_plan.script) {
|
||||
fmt::println("ACTION: {}", action.name);
|
||||
state = action.apply_effect(state);
|
||||
ai::Action kill_it("kill_it", 10);
|
||||
kill_it.needs(ENEMY_IN_RANGE, true);
|
||||
// this is duplicated on purpose to confirm that setting
|
||||
// a positive then a negative properly cancels out
|
||||
kill_it.needs(ENEMY_DEAD, true);
|
||||
kill_it.needs(ENEMY_DEAD, false);
|
||||
|
||||
// same thing with effects
|
||||
kill_it.effect(ENEMY_DEAD, false);
|
||||
kill_it.effect(ENEMY_DEAD, true);
|
||||
|
||||
// order seems to matter which is wrong
|
||||
actions.push_back(kill_it);
|
||||
actions.push_back(move_closer);
|
||||
|
||||
auto result = ai::plan_actions(actions, start, goal);
|
||||
CHECK(result.complete);
|
||||
|
||||
auto state = start;
|
||||
|
||||
for(auto& action : result.script) {
|
||||
state = action.apply_effect(state);
|
||||
}
|
||||
|
||||
CHECK(state[ENEMY_DEAD]);
|
||||
}
|
||||
|
||||
REQUIRE(ai::test(state, "target_dead"));
|
||||
}
|
||||
|
||||
TEST_CASE("ai autowalker ai test", "[ai]") {
|
||||
ai::reset();
|
||||
ai::init("ai");
|
||||
auto start = ai::load_state("Host::initial_state");
|
||||
auto goal = ai::load_state("Host::final_state");
|
||||
int enemy_count = 5;
|
||||
|
||||
ai::set(start, "no_more_enemies", enemy_count == 0);
|
||||
|
||||
// find an enemy and kill them
|
||||
auto a_plan = ai::plan("Host::actions", start, goal);
|
||||
REQUIRE(!a_plan.complete);
|
||||
|
||||
auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script);
|
||||
REQUIRE(ai::test(result, "enemy_found"));
|
||||
REQUIRE(!ai::test(result, "no_more_enemies"));
|
||||
|
||||
// health is low, go heal
|
||||
ai::set(result, "health_good", false);
|
||||
ai::set(result, "in_combat", false);
|
||||
ai::set(result, "enemy_found", false);
|
||||
ai::set(result, "have_healing", true);
|
||||
ai::set(result, "have_item", true);
|
||||
REQUIRE(!ai::test(result, "health_good"));
|
||||
|
||||
auto health_plan = ai::plan("Host::actions", result, goal);
|
||||
result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script);
|
||||
REQUIRE(!health_plan.complete);
|
||||
REQUIRE(ai::test(result, "health_good"));
|
||||
|
||||
// health is good, enemies dead, go get stuff
|
||||
ai::set(result, "no_more_enemies", true);
|
||||
REQUIRE(ai::test(result, "no_more_enemies"));
|
||||
|
||||
auto new_plan = ai::plan("Host::actions", result, goal);
|
||||
result = ai::dump_script("\n\nWALKER COLLECT ITEMS", result, new_plan.script);
|
||||
REQUIRE(ai::test(result, "no_more_items"));
|
||||
REQUIRE(ai::test(result, "no_more_enemies"));
|
||||
}
|
||||
|
||||
TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") {
|
||||
ai::reset();
|
||||
ai::init("ai");
|
||||
auto ai_start = ai::load_state("Enemy::initial_state");
|
||||
auto ai_goal = ai::load_state("Enemy::final_state");
|
||||
|
||||
ai::EntityAI enemy("Enemy::actions", ai_start, ai_goal);
|
||||
|
||||
enemy.set_state("detect_enemy", true);
|
||||
enemy.update();
|
||||
REQUIRE(enemy.wants_to("find_enemy"));
|
||||
|
||||
enemy.set_state("enemy_found", true);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.update();
|
||||
REQUIRE(enemy.wants_to("kill_enemy"));
|
||||
|
||||
enemy.set_state("have_item", true);
|
||||
enemy.set_state("have_healing", true);
|
||||
enemy.set_state("in_combat", false);
|
||||
enemy.set_state("health_good", false);
|
||||
enemy.update();
|
||||
REQUIRE(enemy.wants_to("use_healing"));
|
||||
|
||||
enemy.set_state("have_healing", false);
|
||||
enemy.set_state("tough_personality", true);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.set_state("health_good", true);
|
||||
enemy.update();
|
||||
REQUIRE(enemy.wants_to("kill_enemy"));
|
||||
|
||||
fmt::println("\n\n\n\n=============================\n\n\n\n");
|
||||
enemy.set_state("have_healing", false);
|
||||
enemy.set_state("tough_personality", false);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.set_state("health_good", false);
|
||||
enemy.update();
|
||||
REQUIRE(enemy.wants_to("run_away"));
|
||||
|
||||
void test_ai_module_like_sound() {
|
||||
ai::reset();
|
||||
ai::init("tests/ai_fixture.json");
|
||||
|
||||
auto start = ai::load_state("test_start");
|
||||
auto goal = ai::load_state("test_goal");
|
||||
|
||||
auto a_plan = ai::plan("test1", start, goal);
|
||||
CHECK(a_plan.complete);
|
||||
|
||||
auto state = start;
|
||||
for(auto& action : a_plan.script) {
|
||||
fmt::println("ACTION: {}", action.name);
|
||||
state = action.apply_effect(state);
|
||||
}
|
||||
|
||||
CHECK(ai::test(state, "target_dead"));
|
||||
}
|
||||
|
||||
void test_ai_autowalker() {
|
||||
ai::reset();
|
||||
ai::init("ai");
|
||||
auto start = ai::load_state("Host::initial_state");
|
||||
auto goal = ai::load_state("Host::final_state");
|
||||
int enemy_count = 5;
|
||||
|
||||
ai::set(start, "no_more_enemies", enemy_count == 0);
|
||||
|
||||
// find an enemy and kill them
|
||||
auto a_plan = ai::plan("Host::actions", start, goal);
|
||||
CHECK(!a_plan.complete);
|
||||
|
||||
auto result = ai::dump_script("\n\nWALKER KILL STUFF", start, a_plan.script);
|
||||
CHECK(ai::test(result, "enemy_found"));
|
||||
CHECK(!ai::test(result, "no_more_enemies"));
|
||||
|
||||
// health is low, go heal
|
||||
ai::set(result, "health_good", false);
|
||||
ai::set(result, "in_combat", false);
|
||||
ai::set(result, "enemy_found", false);
|
||||
ai::set(result, "have_healing", true);
|
||||
ai::set(result, "have_item", true);
|
||||
CHECK(!ai::test(result, "health_good"));
|
||||
|
||||
auto health_plan = ai::plan("Host::actions", result, goal);
|
||||
result = ai::dump_script("\n\nWALKER NEED HEALTH", result, health_plan.script);
|
||||
CHECK(!health_plan.complete);
|
||||
CHECK(ai::test(result, "health_good"));
|
||||
|
||||
// health is good, enemies dead, go get stuff
|
||||
ai::set(result, "no_more_enemies", true);
|
||||
CHECK(ai::test(result, "no_more_enemies"));
|
||||
|
||||
auto new_plan = ai::plan("Host::actions", result, goal);
|
||||
result = ai::dump_script("\n\nWALKER COLLECT ITEMS", result, new_plan.script);
|
||||
CHECK(ai::test(result, "no_more_items"));
|
||||
CHECK(ai::test(result, "no_more_enemies"));
|
||||
}
|
||||
|
||||
void test_confirm_EntityAI_behaves() {
|
||||
ai::reset();
|
||||
ai::init("ai");
|
||||
auto ai_start = ai::load_state("Enemy::initial_state");
|
||||
auto ai_goal = ai::load_state("Enemy::final_state");
|
||||
|
||||
ai::EntityAI enemy("Enemy::actions", ai_start, ai_goal);
|
||||
|
||||
enemy.set_state("detect_enemy", true);
|
||||
enemy.update();
|
||||
CHECK(enemy.wants_to("find_enemy"));
|
||||
|
||||
enemy.set_state("enemy_found", true);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.update();
|
||||
CHECK(enemy.wants_to("kill_enemy"));
|
||||
|
||||
enemy.set_state("have_item", true);
|
||||
enemy.set_state("have_healing", true);
|
||||
enemy.set_state("in_combat", false);
|
||||
enemy.set_state("health_good", false);
|
||||
enemy.update();
|
||||
CHECK(enemy.wants_to("use_healing"));
|
||||
|
||||
enemy.set_state("have_healing", false);
|
||||
enemy.set_state("tough_personality", true);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.set_state("health_good", true);
|
||||
enemy.update();
|
||||
CHECK(enemy.wants_to("kill_enemy"));
|
||||
|
||||
fmt::println("\n\n\n\n=============================\n\n\n\n");
|
||||
enemy.set_state("have_healing", false);
|
||||
enemy.set_state("tough_personality", false);
|
||||
enemy.set_state("in_combat", true);
|
||||
enemy.set_state("health_good", false);
|
||||
enemy.update();
|
||||
CHECK(enemy.wants_to("run_away"));
|
||||
}
|
||||
|
||||
fuc2::Set TESTS{
|
||||
.name="ai functionality",
|
||||
.tests={
|
||||
TEST(test_state_and_actions),
|
||||
TEST(test_basic_features),
|
||||
TEST(test_ai_module_like_sound),
|
||||
TEST(test_ai_autowalker),
|
||||
TEST(test_confirm_EntityAI_behaves),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
tests = files(
|
||||
'ai.cpp',
|
||||
'animation.cpp',
|
||||
'base.cpp',
|
||||
'battle.cpp',
|
||||
|
|
@ -26,3 +25,8 @@ tests = files(
|
|||
'systems.cpp',
|
||||
'textures.cpp',
|
||||
)
|
||||
|
||||
fuc2_tests = files(
|
||||
'ai.cpp',
|
||||
'main.cpp'
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue