#ifndef H_GOAP_H_ #define H_GOAP_H_ #undef NDEBUG #include #include #include #include #include namespace goap { template 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 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::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 struct DeclareAction : public BaseAction { RunnerType runner; template DeclareAction(const std::string &name, Args... args) : runner(args...) , BaseAction(name, &runner) { } }; } #endif