diff --git a/Makefile b/Makefile index 6144bd3..cdba2a3 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ tracy_build: meson compile -j 10 -C builddir test: build - ./builddir/runtests + ./builddir/runtests "[goap]" run: build test powershell "cp ./builddir/zedcaster.exe ." diff --git a/goap.cpp b/goap.cpp index 0ee6110..72f3488 100644 --- a/goap.cpp +++ b/goap.cpp @@ -8,22 +8,12 @@ namespace ailol { } bool Action::can_effect(GOAPState& state) { - for(auto [name, setting] : preconds) { - if(state[name] != setting) return false; - } - - return true; + return ((state & positive_preconds) == positive_preconds) && + ((state & negative_preconds) == ALL_ZERO); } GOAPState Action::apply_effect(GOAPState& state) { - // RCR SUGGEST: state = (state & ~write_mask) | effect - auto state_cp = state; - - for(auto [name, setting] : effects) { - state_cp[name] = setting; - } - - return state_cp; + return (state | positive_effects) & ~negative_effects; } int distance_to_goal(GOAPState& from, GOAPState& to) { diff --git a/goap.hpp b/goap.hpp index 391ff74..62fcc28 100644 --- a/goap.hpp +++ b/goap.hpp @@ -7,19 +7,41 @@ namespace ailol { constexpr const int SCORE_MAX = std::numeric_limits::max(); - constexpr const size_t STATE_MAX = 16; + constexpr const size_t STATE_MAX = 93; using GOAPState = std::bitset; + const GOAPState ALL_ZERO; + const GOAPState ALL_ONES = ~ALL_ZERO; + struct Action { std::string name; int cost = 0; - std::unordered_map preconds; - std::unordered_map effects; + GOAPState positive_preconds; + GOAPState negative_preconds; + + GOAPState positive_effects; + GOAPState negative_effects; Action(std::string name, int cost) : - name(name), cost(cost) {} + name(name), cost(cost) { } + + void set_precond(int name, bool val) { + if(val) { + positive_preconds[name] = true; + } else { + negative_preconds[name] = true; + } + } + + void set_effect(int name, bool val) { + if(val) { + positive_effects[name] = true; + } else { + negative_effects[name] = true; + } + } bool can_effect(GOAPState& state); GOAPState apply_effect(GOAPState& state); diff --git a/tests/goap.cpp b/tests/goap.cpp index 5d7fb9d..3f3e242 100644 --- a/tests/goap.cpp +++ b/tests/goap.cpp @@ -1,6 +1,7 @@ #include #include "dbc.hpp" #include "goap.hpp" +#include using namespace dbc; using namespace ailol; @@ -23,8 +24,8 @@ TEST_CASE("worldstate works", "[goap]") { goal[ENEMY_DEAD] = true; Action move_closer("move_closer", 10); - move_closer.preconds[ENEMY_IN_RANGE] = false; - move_closer.effects[ENEMY_IN_RANGE] = true; + move_closer.set_precond(ENEMY_IN_RANGE, false); + move_closer.set_effect(ENEMY_IN_RANGE, true); REQUIRE(move_closer.can_effect(start)); auto after_move_state = move_closer.apply_effect(start); @@ -37,9 +38,9 @@ TEST_CASE("worldstate works", "[goap]") { REQUIRE(distance_to_goal(start, after_move_state) == 1); Action kill_it("kill_it", 10); - kill_it.preconds[ENEMY_IN_RANGE] = true; - kill_it.preconds[ENEMY_DEAD] = false; - kill_it.effects[ENEMY_DEAD] = true; + kill_it.set_precond(ENEMY_IN_RANGE, true); + kill_it.set_precond(ENEMY_DEAD, false); + kill_it.set_effect(ENEMY_DEAD, true); REQUIRE(!kill_it.can_effect(start)); REQUIRE(kill_it.can_effect(after_move_state)); @@ -72,13 +73,13 @@ TEST_CASE("basic feature tests", "[goap]") { goal[ENEMY_DEAD] = true; Action move_closer("move_closer", 10); - move_closer.preconds[ENEMY_IN_RANGE] = false; - move_closer.effects[ENEMY_IN_RANGE] = true; + move_closer.set_precond(ENEMY_IN_RANGE, false); + move_closer.set_effect(ENEMY_IN_RANGE, true); Action kill_it("kill_it", 10); - kill_it.preconds[ENEMY_IN_RANGE] = true; - kill_it.preconds[ENEMY_DEAD] = false; - kill_it.effects[ENEMY_DEAD] = true; + kill_it.set_precond(ENEMY_IN_RANGE, true); + kill_it.set_precond(ENEMY_DEAD, false); + kill_it.set_effect(ENEMY_DEAD, true); // order seems to matter which is wrong actions.push_back(kill_it); @@ -112,28 +113,28 @@ TEST_CASE("wargame test from cppGOAP", "[goap]") { // Now establish all the possible actions for the action pool // In this example we're providing the AI some different FPS actions Action spiral("searchSpiral", 5); - spiral.preconds[target_acquired] = false; - spiral.preconds[target_lost] = true; - spiral.effects[target_acquired] = true; + spiral.set_precond(target_acquired, false); + spiral.set_precond(target_lost, true); + spiral.set_effect(target_acquired, true); actions.push_back(spiral); Action serpentine("searchSerpentine", 5); - serpentine.preconds[target_acquired] = false; - serpentine.preconds[target_lost] = false; - serpentine.effects[target_acquired] = true; + serpentine.set_precond(target_acquired, false); + serpentine.set_precond(target_lost, false); + serpentine.set_effect(target_acquired, true); actions.push_back(serpentine); Action intercept("interceptTarget", 5); - intercept.preconds[target_acquired] = true; - intercept.preconds[target_dead] = false; - intercept.effects[target_in_warhead_range] = true; + intercept.set_precond(target_acquired, true); + intercept.set_precond(target_dead, false); + intercept.set_effect(target_in_warhead_range, true); actions.push_back(intercept); Action detonateNearTarget("detonateNearTarget", 5); - detonateNearTarget.preconds[target_in_warhead_range] = true; - detonateNearTarget.preconds[target_acquired] = true; - detonateNearTarget.preconds[target_dead] = false; - detonateNearTarget.effects[target_dead] = true; + detonateNearTarget.set_precond(target_in_warhead_range, true); + detonateNearTarget.set_precond(target_acquired, true); + detonateNearTarget.set_precond(target_dead, false); + detonateNearTarget.set_effect(target_dead, true); actions.push_back(detonateNearTarget); // Here's the initial state...