Rituals are more or less sorted out in theory, and they helped find a cycle in the GOAP algorithm that I'm detecting/preventing.

master
Zed A. Shaw 2 weeks ago
parent 8368d2e751
commit 49531ba148
  1. 2
      Makefile
  2. 22
      goap.cpp
  3. 3
      meson.build
  4. 23
      rituals.cpp
  5. 23
      rituals.hpp
  6. 6
      stats.cpp
  7. 2
      stats.hpp
  8. 21
      tests/ai.cpp
  9. 38
      tests/rituals.cpp

@ -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:

@ -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',

@ -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);
}

@ -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="");
};

@ -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…
Cancel
Save