Files
ogre-prototype/src/gamedata/goap.h
2026-01-29 15:28:50 +03:00

296 lines
6.6 KiB
C++

#ifndef H_GOAP_H_
#define H_GOAP_H_
#undef NDEBUG
#include <string>
#include <memory>
#include <set>
#include <flecs.h>
#include <Ogre.h>
namespace goap
{
template <typename State> struct BaseAction {
std::string m_name;
State prereq;
State effects;
int m_cost;
public:
enum { OK = 0, BUSY, ABORT };
public:
BaseAction(const std::string &name, const State &prereq, const State &effects, int cost)
: m_name(name)
, prereq(prereq)
, effects(effects)
, m_cost(cost)
{
}
virtual const std::string &get_name() const
{
return m_name;
}
void plan_effects(State &state)
{
// std::cout << m_name << " pre: " << &state << " " << state.flags
// << std::endl;
_plan_effects(state);
// std::cout << m_name << " post: " << &state << " " << state.flags
// << std::endl;
}
virtual bool can_run(const State &state, bool debug = false)
{
return state.distance_to(prereq) == 0;
}
virtual void _plan_effects(State &state)
{
state.apply(effects);
}
// constant cost
virtual int get_cost(const State &state) const
{
return m_cost;
}
};
template <typename State, typename Act, int N = 100> class BasePlanner {
struct VisitedState {
int priority;
int cost;
State state;
// Used to reconstruct the path
VisitedState *parent;
Act *action;
// Only used for linked list management
VisitedState *next;
};
VisitedState nodes[N];
void visited_states_array_to_list(VisitedState *nodes, int len)
{
for (auto i = 0; i < len - 1; i++) {
nodes[i].next = &nodes[i + 1];
}
nodes[len - 1].next = nullptr;
}
VisitedState *priority_list_pop(VisitedState *&list)
{
VisitedState *min_elem = nullptr;
auto min_prio = std::numeric_limits<int>::max();
// Find the element with the lowest priority
for (auto p = list; p != nullptr; p = p->next) {
if (p->priority < min_prio) {
min_elem = p;
min_prio = p->priority;
}
}
// Remove it from the list
if (min_elem == list) {
list = list->next;
} else {
for (auto p = list; p != nullptr; p = p->next) {
if (p->next == min_elem) {
p->next = p->next->next;
min_elem->next = nullptr;
}
}
}
return min_elem;
}
VisitedState *list_pop_head(VisitedState *&head)
{
auto ret = head;
head = head->next;
ret->next = nullptr;
return ret;
}
void list_push_head(VisitedState *&head, VisitedState *elem)
{
elem->next = head;
head = elem;
}
void update_queued_state(VisitedState *previous,
const VisitedState *current)
{
if (previous->cost > current->cost) {
previous->priority = current->cost;
previous->priority = current->priority;
previous->parent = current->parent;
previous->action = current->action;
}
}
public:
struct BaseGoal {
std::string m_name;
State goal;
public:
BaseGoal(const std::string &name, const State &goal)
: m_name(name), goal(goal)
{
}
/** Checks if the goal is reached for the given state. */
virtual bool is_reached(const State &state) const
{
return distance_to(state) == 0;
}
/** Computes the distance from state to goal. */
virtual int distance_to(const State &state) const
{
return state.distance_to(goal);
}
virtual ~BaseGoal() = default;
const std::string &get_name() const
{
return m_name;
}
};
/** Finds a plan from state to goal and returns its length.
*
* If path is given, then the found path is stored there.
*/
int plan(const State &state, const BaseGoal &goal, Act *actions[],
unsigned action_count, Act **path = nullptr, int path_len = 10)
{
visited_states_array_to_list(nodes, N);
auto free_nodes = &nodes[0];
VisitedState *open = list_pop_head(free_nodes);
VisitedState *close = nullptr;
open->state = state;
open->cost = 0;
open->priority = 0;
open->parent = nullptr;
open->action = nullptr;
while (open) {
VisitedState *current = priority_list_pop(open);
list_push_head(close, current);
if (goal.is_reached(current->state)) {
auto len = 0;
for (auto p = current->parent; p;
p = p->parent) {
len++;
}
if (len > path_len) {
OgreAssert(len <= path_len, "Out of plan length");
return -3;
}
if (path) {
auto i = len - 1;
for (auto p = current; p->parent;
p = p->parent, i--) {
path[i] = p->action;
}
}
OgreAssert(len >= 0, "Bad plan length");
return len;
}
for (auto i = 0u; i < action_count; i++) {
auto action = actions[i];
if (action->can_run(current->state)) {
// Cannot allocate a new node, abort
if (free_nodes == nullptr) {
// Garbage collect the node that is most unlikely to be
// visited (i.e. lowest priority)
VisitedState *gc,
*gc_prev = nullptr;
for (gc = open; gc && gc->next;
gc = gc->next) {
gc_prev = gc;
}
if (!gc) {
OgreAssert(gc, "Out of memory");
return -2 /* OOM */;
}
if (gc_prev) {
gc_prev->next = nullptr;
}
free_nodes = gc;
}
auto neighbor =
list_pop_head(free_nodes);
neighbor->state = current->state;
action->plan_effects(neighbor->state);
neighbor->cost =
current->cost + 1 +
action->get_cost(
neighbor->state);
neighbor->priority =
current->priority + 1 +
goal.distance_to(
neighbor->state);
neighbor->parent = current;
neighbor->action = action;
bool should_insert = true;
// Check if the node is already in the list of nodes
// scheduled to be visited
for (auto p = open; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
// Check if the state is in the list of already visited
// state
for (auto p = close; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
if (should_insert) {
list_push_head(open, neighbor);
} else {
list_push_head(free_nodes,
neighbor);
}
}
}
}
// Reaching here means we did not find a path
return -1 /* No path */;
}
};
template <class RunnerType, class State>
struct DeclareAction : public BaseAction<State> {
RunnerType runner;
template <class... Args>
DeclareAction(const std::string &name, Args... args)
: runner(args...)
, BaseAction<State>(name, &runner)
{
}
};
}
#endif