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
|
||||
|
||||
debug_build:
|
||||
meson setup --wipe builddir --buildtype debug
|
||||
meson setup --wipe builddir -Db_ndebug=true --buildtype debugoptimized
|
||||
meson compile -j 10 -C builddir
|
||||
|
||||
tracy_build:
|
||||
|
|
22
goap.cpp
22
goap.cpp
|
@ -1,8 +1,10 @@
|
|||
#include "dbc.hpp"
|
||||
#include "goap.hpp"
|
||||
#include "ai_debug.hpp"
|
||||
#include "stats.hpp"
|
||||
|
||||
namespace ai {
|
||||
|
||||
using namespace nlohmann;
|
||||
using namespace dbc;
|
||||
|
||||
|
@ -36,7 +38,6 @@ namespace ai {
|
|||
$negative_preconds[name] = false;
|
||||
}
|
||||
|
||||
|
||||
bool Action::can_effect(State& state) {
|
||||
return ((state & $positive_preconds) == $positive_preconds) &&
|
||||
((state & $negative_preconds) == ALL_ZERO);
|
||||
|
@ -53,14 +54,24 @@ namespace ai {
|
|||
|
||||
Script reconstruct_path(std::unordered_map<Action, Action>& came_from, Action& 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);
|
||||
if(current != FINAL_ACTION) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -96,7 +107,7 @@ namespace ai {
|
|||
ActionState current{FINAL_ACTION, start};
|
||||
|
||||
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()) {
|
||||
current = find_lowest(open_set);
|
||||
|
@ -127,7 +138,10 @@ namespace ai {
|
|||
g_score[neighbor] = tentative_g_score;
|
||||
// open_set gets the fScore
|
||||
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',
|
||||
'raycaster.cpp',
|
||||
'render.cpp',
|
||||
'rituals.cpp',
|
||||
'save.cpp',
|
||||
'shiterator.hpp',
|
||||
'sound.cpp',
|
||||
|
@ -126,7 +127,7 @@ sources = [
|
|||
executable('runtests', sources + [
|
||||
'tests/ansi_parser.cpp',
|
||||
'tests/base.cpp',
|
||||
'tests/combat.cpp',
|
||||
'tests/rituals.cpp',
|
||||
'tests/components.cpp',
|
||||
'tests/config.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 <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: {}",
|
||||
sum, sumsq, n, min, max, mean(),
|
||||
msg, sum, sumsq, n, min, max, mean(),
|
||||
stddev());
|
||||
}
|
||||
|
|
|
@ -53,5 +53,5 @@ struct Stats {
|
|||
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 <iostream>
|
||||
#include "ai_debug.hpp"
|
||||
#include "rituals.hpp"
|
||||
|
||||
using namespace dbc;
|
||||
using namespace nlohmann;
|
||||
|
@ -207,5 +208,25 @@ TEST_CASE("Confirm EntityAI behaves as expected", "[ai]") {
|
|||
enemy.set_state("health_good", false);
|
||||
enemy.update();
|
||||
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 <iostream>
|
||||
#include "ai.hpp"
|
||||
#include "ai_debug.hpp"
|
||||
#include "rituals.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]") {
|
||||
ai::reset();
|
Loading…
Add table
Add a link
Reference in a new issue