AI Toolkit
Loading...
Searching...
No Matches
goap.hpp
1#pragma once
2
167#include <unordered_set>
168#include <functional>
169#include <coroutine>
170#include <optional>
171#include <memory>
172#include <vector>
173#include <queue>
174#include <stack>
175
176#include <type_traits>
177#include <concepts>
178
179namespace aitoolkit::goap {
180 template <typename T>
181 concept blackboard_trait = requires(const T &a, const T &b) {
182 { a == b } -> std::convertible_to<bool>;
183 { std::hash<T>{}(a) } -> std::convertible_to<size_t>;
184 };
185
201 template <blackboard_trait T>
202 class action {
203 public:
204 virtual ~action() = default;
205
209 virtual float cost(const T& blackboard) const = 0;
210
214 virtual bool check_preconditions(const T& blackboard) const = 0;
215
219 virtual void apply_effects(T& blackboard, bool dry_run) const = 0;
220 };
221
225 template <blackboard_trait T>
226 using action_ptr = std::unique_ptr<action<T>>;
227
228 template <typename A, typename T>
229 concept action_trait = std::derived_from<A, action<T>>;
230
231 template <blackboard_trait T, action_trait<T>... Actions>
232 std::vector<action_ptr<T>> action_list(Actions... actions) {
233 auto action_list = std::vector<action_ptr<T>>{};
234 action_list.reserve(sizeof...(Actions));
235 (action_list.push_back(std::make_unique<Actions>(std::move(actions))), ...);
236 return action_list;
237 }
238
244 template <blackboard_trait T>
245 class plan {
246 public:
247 plan() = default;
248
252 size_t size() const {
253 return m_plan.size();
254 }
255
259 operator bool() const {
260 return !m_plan.empty();
261 }
262
266 void run_next(T& blackboard) {
267 if (!m_plan.empty()) {
268 auto action_idx = m_plan.top();
269 m_plan.pop();
270
271 auto& action = m_actions[action_idx];
272 action->apply_effects(blackboard, false);
273 }
274 }
275
276 private:
277 std::stack<size_t> m_plan;
278 std::vector<action_ptr<T>> m_actions;
279
280 friend plan<T> planner<T>(
281 std::vector<action_ptr<T>> actions,
284 size_t max_iterations
285 );
286 };
287
303 template <blackboard_trait T>
305 std::vector<action_ptr<T>> actions,
308 size_t max_iterations = 0
309 ) {
310 struct node_type {
312 float cost;
313
314 std::optional<size_t> action_taken_idx;
315 std::shared_ptr<node_type> parent;
316 };
317
318 using node_ptr = std::shared_ptr<node_type>;
319
320 struct node_compare {
321 bool operator()(const node_ptr& a, const node_ptr& b) const {
322 return a->cost > b->cost;
323 }
324 };
325
326 std::priority_queue<node_ptr, std::vector<node_ptr>, node_compare> open_set;
327 std::unordered_set<T> closed_set;
328
329 open_set.push(std::make_shared<node_type>(node_type{
330 .blackboard = initital_blackboard,
331 .cost = 0.0f,
332 .action_taken_idx = std::nullopt,
333 .parent = nullptr
334 }));
335
336 for (
337 size_t iteration = 0;
338 !open_set.empty() && (max_iterations == 0 || iteration < max_iterations);
339 ++iteration
340 ) {
341 auto current_node = open_set.top();
342 open_set.pop();
343
344 if (current_node->blackboard == goal_blackboard) {
345 auto p = plan<T>();
346 p.m_actions = std::move(actions);
347
348 while (current_node->parent != nullptr) {
349 auto action_idx = current_node->action_taken_idx.value();
350 p.m_plan.push(action_idx);
351 current_node = current_node->parent;
352 }
353
354 return p;
355 }
356
357 if (!closed_set.contains(current_node->blackboard)) {
358 closed_set.insert(current_node->blackboard);
359
360 for (size_t action_idx = 0; action_idx < actions.size(); action_idx++) {
361 auto& action = actions[action_idx];
362
363 if (action->check_preconditions(current_node->blackboard)) {
364 auto next_blackboard = current_node->blackboard;
366 auto next_cost = current_node->cost + action->cost(current_node->blackboard);
367
368 if (!closed_set.contains(next_blackboard)) {
369 open_set.push(std::make_shared<node_type>(node_type{
370 .blackboard = next_blackboard,
371 .cost = next_cost,
372 .action_taken_idx = action_idx,
373 .parent = current_node
374 }));
375 }
376 }
377 }
378 }
379 }
380
381 return plan<T>();
382 }
383}
An action that can be performed on a blackboard.
Definition goap.hpp:202
virtual float cost(const T &blackboard) const =0
The cost of performing this action.
virtual void apply_effects(T &blackboard, bool dry_run) const =0
Apply the effects of this action to the blackboard.
virtual bool check_preconditions(const T &blackboard) const =0
Check if the preconditions for this action are met.
A plan is a sequence of actions that will lead to a goal state.
Definition goap.hpp:245
friend plan< T > planner(std::vector< action_ptr< T > > actions, T initital_blackboard, T goal_blackboard, size_t max_iterations)
Create a plan.
Definition goap.hpp:304
size_t size() const
Get the number of actions in the plan.
Definition goap.hpp:252
void run_next(T &blackboard)
Execute the next planned action.
Definition goap.hpp:266
Definition goap.hpp:229
Definition goap.hpp:181
plan< T > planner(std::vector< action_ptr< T > > actions, T initital_blackboard, T goal_blackboard, size_t max_iterations=0)
Create a plan.
Definition goap.hpp:304