296 lines
6.6 KiB
C++
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
|