Rituals are more or less sorted out in theory, and they helped find a cycle in the GOAP algorithm that I'm detecting/preventing.
This commit is contained in:
parent
8368d2e751
commit
49531ba148
9 changed files with 94 additions and 48 deletions
2
Makefile
2
Makefile
|
@ -14,7 +14,7 @@ release_build:
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
debug_build:
|
debug_build:
|
||||||
meson setup --wipe builddir --buildtype debug
|
meson setup --wipe builddir -Db_ndebug=true --buildtype debugoptimized
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
tracy_build:
|
tracy_build:
|
||||||
|
|
22
goap.cpp
22
goap.cpp
|
@ -1,8 +1,10 @@
|
||||||
#include "dbc.hpp"
|
#include "dbc.hpp"
|
||||||
#include "goap.hpp"
|
#include "goap.hpp"
|
||||||
#include "ai_debug.hpp"
|
#include "ai_debug.hpp"
|
||||||
|
#include "stats.hpp"
|
||||||
|
|
||||||
namespace ai {
|
namespace ai {
|
||||||
|
|
||||||
using namespace nlohmann;
|
using namespace nlohmann;
|
||||||
using namespace dbc;
|
using namespace dbc;
|
||||||
|
|
||||||
|
@ -36,7 +38,6 @@ namespace ai {
|
||||||
$negative_preconds[name] = false;
|
$negative_preconds[name] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Action::can_effect(State& state) {
|
bool Action::can_effect(State& state) {
|
||||||
return ((state & $positive_preconds) == $positive_preconds) &&
|
return ((state & $positive_preconds) == $positive_preconds) &&
|
||||||
((state & $negative_preconds) == ALL_ZERO);
|
((state & $negative_preconds) == ALL_ZERO);
|
||||||
|
@ -53,14 +54,24 @@ namespace ai {
|
||||||
|
|
||||||
Script reconstruct_path(std::unordered_map<Action, Action>& came_from, Action& current) {
|
Script reconstruct_path(std::unordered_map<Action, Action>& came_from, Action& current) {
|
||||||
Script total_path{current};
|
Script total_path{current};
|
||||||
|
bool final_found = false;
|
||||||
|
|
||||||
while(came_from.contains(current)) {
|
for(size_t i = 0; i <= came_from.size() && came_from.contains(current); i++) {
|
||||||
current = came_from.at(current);
|
current = came_from.at(current);
|
||||||
if(current != FINAL_ACTION) {
|
if(current != FINAL_ACTION) {
|
||||||
total_path.push_front(current);
|
total_path.push_front(current);
|
||||||
|
} else {
|
||||||
|
final_found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this here temporarily while I figure out cycle detects/prevention
|
||||||
|
if(!final_found && total_path[0] != FINAL_ACTION) {
|
||||||
|
auto error = fmt::format("!!!!! You may have a cycle in your json. No FINAL found. Here's the path: ");
|
||||||
|
for(auto& action : total_path) error += fmt::format("{} ", action.name);
|
||||||
|
dbc::sentinel(error);
|
||||||
|
}
|
||||||
|
|
||||||
return total_path;
|
return total_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +107,7 @@ namespace ai {
|
||||||
ActionState current{FINAL_ACTION, start};
|
ActionState current{FINAL_ACTION, start};
|
||||||
|
|
||||||
g_score[start] = 0;
|
g_score[start] = 0;
|
||||||
open_set[current] = g_score[start] + h(start, goal, current.action);
|
open_set.insert_or_assign(current, g_score[start] + h(start, goal, current.action));
|
||||||
|
|
||||||
while(!open_set.empty()) {
|
while(!open_set.empty()) {
|
||||||
current = find_lowest(open_set);
|
current = find_lowest(open_set);
|
||||||
|
@ -127,7 +138,10 @@ namespace ai {
|
||||||
g_score[neighbor] = tentative_g_score;
|
g_score[neighbor] = tentative_g_score;
|
||||||
// open_set gets the fScore
|
// open_set gets the fScore
|
||||||
ActionState neighbor_as{neighbor_action, neighbor};
|
ActionState neighbor_as{neighbor_action, neighbor};
|
||||||
open_set[neighbor_as] = tentative_g_score + h(neighbor, goal, neighbor_as.action);
|
|
||||||
|
int score = tentative_g_score + h(neighbor, goal, neighbor_as.action);
|
||||||
|
// could maintain lowest here and avoid searching all things
|
||||||
|
open_set.insert_or_assign(neighbor_as, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,7 @@ sources = [
|
||||||
'rand.cpp',
|
'rand.cpp',
|
||||||
'raycaster.cpp',
|
'raycaster.cpp',
|
||||||
'render.cpp',
|
'render.cpp',
|
||||||
|
'rituals.cpp',
|
||||||
'save.cpp',
|
'save.cpp',
|
||||||
'shiterator.hpp',
|
'shiterator.hpp',
|
||||||
'sound.cpp',
|
'sound.cpp',
|
||||||
|
@ -126,7 +127,7 @@ sources = [
|
||||||
executable('runtests', sources + [
|
executable('runtests', sources + [
|
||||||
'tests/ansi_parser.cpp',
|
'tests/ansi_parser.cpp',
|
||||||
'tests/base.cpp',
|
'tests/base.cpp',
|
||||||
'tests/combat.cpp',
|
'tests/rituals.cpp',
|
||||||
'tests/components.cpp',
|
'tests/components.cpp',
|
||||||
'tests/config.cpp',
|
'tests/config.cpp',
|
||||||
'tests/dbc.cpp',
|
'tests/dbc.cpp',
|
||||||
|
|
23
rituals.cpp
Normal file
23
rituals.cpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#include "rituals.hpp"
|
||||||
|
#include "ai_debug.hpp"
|
||||||
|
|
||||||
|
void RitualAI::reset() {
|
||||||
|
start = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RitualAI::will_do(std::string name) {
|
||||||
|
ai::check_valid_action(name, "RitualAI::is_able_to");
|
||||||
|
return plan.script[0].name == name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RitualAI::set_state(std::string name, bool setting) {
|
||||||
|
ai::set(start, name, setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RitualAI::update() {
|
||||||
|
plan = ai::plan(script, start, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RitualAI::dump() {
|
||||||
|
ai::dump_script(script, start, plan.script);
|
||||||
|
}
|
23
rituals.hpp
Normal file
23
rituals.hpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ai.hpp"
|
||||||
|
|
||||||
|
struct RitualAI {
|
||||||
|
std::string script;
|
||||||
|
ai::State start;
|
||||||
|
ai::State original;
|
||||||
|
ai::State goal;
|
||||||
|
ai::ActionPlan plan;
|
||||||
|
|
||||||
|
RitualAI(std::string script, ai::State start, ai::State goal) :
|
||||||
|
script(script), start(start), original(start), goal(goal)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RitualAI() {};
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
bool will_do(std::string name);
|
||||||
|
void set_state(std::string name, bool setting);
|
||||||
|
void update();
|
||||||
|
void dump();
|
||||||
|
};
|
|
@ -1,10 +1,10 @@
|
||||||
#include "stats.hpp"
|
#include "stats.hpp"
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
|
||||||
void Stats::dump()
|
void Stats::dump(std::string msg)
|
||||||
{
|
{
|
||||||
fmt::println("sum: {}, sumsq: {}, n: {}, "
|
fmt::println("{}: sum: {}, sumsq: {}, n: {}, "
|
||||||
"min: {}, max: {}, mean: {}, stddev: {}",
|
"min: {}, max: {}, mean: {}, stddev: {}",
|
||||||
sum, sumsq, n, min, max, mean(),
|
msg, sum, sumsq, n, min, max, mean(),
|
||||||
stddev());
|
stddev());
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,5 +53,5 @@ struct Stats {
|
||||||
sample(1/elapsed.count());
|
sample(1/elapsed.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
void dump();
|
void dump(std::string msg="");
|
||||||
};
|
};
|
||||||
|
|
23
tests/ai.cpp
23
tests/ai.cpp
|
@ -3,6 +3,7 @@
|
||||||
#include "ai.hpp"
|
#include "ai.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "ai_debug.hpp"
|
#include "ai_debug.hpp"
|
||||||
|
#include "rituals.hpp"
|
||||||
|
|
||||||
using namespace dbc;
|
using namespace dbc;
|
||||||
using namespace nlohmann;
|
using namespace nlohmann;
|
||||||
|
@ -207,5 +208,25 @@ TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") {
|
||||||
enemy.set_state("health_good", false);
|
enemy.set_state("health_good", false);
|
||||||
enemy.update();
|
enemy.update();
|
||||||
REQUIRE(enemy.wants_to("run_away"));
|
REQUIRE(enemy.wants_to("run_away"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("confirm that cycles are avoided/detected", "[ai]") {
|
||||||
|
ai::reset();
|
||||||
|
ai::init("tests/cyclic_rituals.json");
|
||||||
|
|
||||||
|
auto start = ai::load_state("initial");
|
||||||
|
auto goal = ai::load_state("final");
|
||||||
|
|
||||||
|
RitualAI ritual("actions", start, goal);
|
||||||
|
ritual.reset();
|
||||||
|
ritual.set_state("has_magick", true);
|
||||||
|
ritual.set_state("cursed_item", true);
|
||||||
|
ritual.set_state("shiny_bauble", true);
|
||||||
|
|
||||||
|
bool it_throws = false;
|
||||||
|
try { ritual.update(); } catch(...) { it_throws = true; }
|
||||||
|
REQUIRE(it_throws);
|
||||||
|
|
||||||
|
fmt::println("\n\n------------ CYCLES AVOIDED");
|
||||||
|
ritual.dump();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,7 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "ai.hpp"
|
#include "rituals.hpp"
|
||||||
#include "ai_debug.hpp"
|
|
||||||
|
|
||||||
struct RitualAI {
|
|
||||||
std::string script;
|
|
||||||
ai::State start;
|
|
||||||
ai::State original;
|
|
||||||
ai::State goal;
|
|
||||||
ai::ActionPlan plan;
|
|
||||||
|
|
||||||
RitualAI(std::string script, ai::State start, ai::State goal) :
|
|
||||||
script(script), start(start), original(start), goal(goal)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
RitualAI() {};
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
start = original;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool will_do(std::string name) {
|
|
||||||
ai::check_valid_action(name, "RitualAI::is_able_to");
|
|
||||||
return plan.script[0].name == name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_state(std::string name, bool setting) {
|
|
||||||
ai::set(start, name, setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() {
|
|
||||||
plan = ai::plan(script, start, goal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dump() {
|
|
||||||
dump_script(script, start, plan.script);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_CASE("prototype combat system ideas", "[combat]") {
|
TEST_CASE("prototype combat system ideas", "[combat]") {
|
||||||
ai::reset();
|
ai::reset();
|
Loading…
Add table
Add a link
Reference in a new issue