1use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
42use std::cmp::Ordering;
43
44#[derive(Debug, Clone, PartialEq, Default)]
54pub struct WorldState {
55 bools: HashMap<String, bool>,
56 floats: HashMap<String, f32>,
57}
58
59impl WorldState {
60 pub fn new() -> Self { Self::default() }
61
62 pub fn set_bool(&mut self, key: &str, value: bool) {
65 self.bools.insert(key.to_string(), value);
66 }
67
68 pub fn get_bool(&self, key: &str) -> bool {
69 *self.bools.get(key).unwrap_or(&false)
70 }
71
72 pub fn has_bool(&self, key: &str) -> bool {
73 self.bools.contains_key(key)
74 }
75
76 pub fn set_float(&mut self, key: &str, value: f32) {
79 self.floats.insert(key.to_string(), value);
80 }
81
82 pub fn get_float(&self, key: &str) -> f32 {
83 *self.floats.get(key).unwrap_or(&0.0)
84 }
85
86 pub fn has_float(&self, key: &str) -> bool {
87 self.floats.contains_key(key)
88 }
89
90 pub fn satisfies(&self, goal: &WorldState) -> bool {
98 for (k, &v) in &goal.bools {
99 if self.get_bool(k) != v { return false; }
100 }
101 for (k, &v) in &goal.floats {
102 if self.get_float(k) < v { return false; }
103 }
104 true
105 }
106
107 pub fn distance_to(&self, goal: &WorldState) -> usize {
109 let bool_unsatisfied = goal.bools.iter()
110 .filter(|(k, &v)| self.get_bool(k) != v)
111 .count();
112 let float_unsatisfied = goal.floats.iter()
113 .filter(|(k, &v)| self.get_float(k) < v)
114 .count();
115 bool_unsatisfied + float_unsatisfied
116 }
117
118 pub fn apply(&self, effects: &ActionEffects) -> WorldState {
120 let mut next = self.clone();
121 for (k, &v) in &effects.bools { next.bools.insert(k.clone(), v); }
122 for (k, &v) in &effects.floats_add {
123 let cur = next.get_float(k);
124 next.floats.insert(k.clone(), cur + v);
125 }
126 for (k, &v) in &effects.floats_set {
127 next.floats.insert(k.clone(), v);
128 }
129 next
130 }
131
132 pub fn merge_from(&mut self, other: &WorldState) {
134 for (k, &v) in &other.bools { self.bools.insert(k.clone(), v); }
135 for (k, &v) in &other.floats { self.floats.insert(k.clone(), v); }
136 }
137
138 pub fn is_empty(&self) -> bool {
140 self.bools.is_empty() && self.floats.is_empty()
141 }
142
143 fn snapshot_key(&self) -> StateKey {
145 let mut bool_pairs: Vec<(String, bool)> = self.bools.iter()
146 .map(|(k, &v)| (k.clone(), v)).collect();
147 bool_pairs.sort_by(|a, b| a.0.cmp(&b.0));
148
149 let mut float_pairs: Vec<(String, u32)> = self.floats.iter()
150 .map(|(k, &v)| (k.clone(), v.to_bits())).collect();
151 float_pairs.sort_by(|a, b| a.0.cmp(&b.0));
152
153 StateKey { bools: bool_pairs, floats: float_pairs }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Hash)]
158struct StateKey {
159 bools: Vec<(String, bool)>,
160 floats: Vec<(String, u32)>,
161}
162
163#[derive(Debug, Clone, Default)]
168pub struct ActionEffects {
169 pub bools: HashMap<String, bool>,
171 pub floats_add: HashMap<String, f32>,
173 pub floats_set: HashMap<String, f32>,
175}
176
177impl ActionEffects {
178 pub fn new() -> Self { Self::default() }
179
180 pub fn set_bool(mut self, key: &str, value: bool) -> Self {
181 self.bools.insert(key.to_string(), value);
182 self
183 }
184
185 pub fn add_float(mut self, key: &str, delta: f32) -> Self {
186 self.floats_add.insert(key.to_string(), delta);
187 self
188 }
189
190 pub fn set_float(mut self, key: &str, value: f32) -> Self {
191 self.floats_set.insert(key.to_string(), value);
192 self
193 }
194}
195
196#[derive(Debug, Clone, Default)]
200pub struct Preconditions {
201 pub bools: HashMap<String, bool>,
202 pub floats_gte: HashMap<String, f32>,
204 pub floats_lte: HashMap<String, f32>,
206 pub floats_gt: HashMap<String, f32>,
208 pub floats_lt: HashMap<String, f32>,
210}
211
212impl Preconditions {
213 pub fn new() -> Self { Self::default() }
214
215 pub fn require_bool(mut self, key: &str, value: bool) -> Self {
216 self.bools.insert(key.to_string(), value);
217 self
218 }
219
220 pub fn require_float_gte(mut self, key: &str, min: f32) -> Self {
221 self.floats_gte.insert(key.to_string(), min);
222 self
223 }
224
225 pub fn require_float_lte(mut self, key: &str, max: f32) -> Self {
226 self.floats_lte.insert(key.to_string(), max);
227 self
228 }
229
230 pub fn require_float_gt(mut self, key: &str, min: f32) -> Self {
231 self.floats_gt.insert(key.to_string(), min);
232 self
233 }
234
235 pub fn require_float_lt(mut self, key: &str, max: f32) -> Self {
236 self.floats_lt.insert(key.to_string(), max);
237 self
238 }
239
240 pub fn satisfied_by(&self, state: &WorldState) -> bool {
242 for (k, &v) in &self.bools {
243 if state.get_bool(k) != v { return false; }
244 }
245 for (k, &t) in &self.floats_gte { if state.get_float(k) < t { return false; } }
246 for (k, &t) in &self.floats_lte { if state.get_float(k) > t { return false; } }
247 for (k, &t) in &self.floats_gt { if state.get_float(k) <= t { return false; } }
248 for (k, &t) in &self.floats_lt { if state.get_float(k) >= t { return false; } }
249 true
250 }
251}
252
253#[derive(Debug, Clone)]
264pub struct Action {
265 pub name: String,
267 pub cost: f32,
269 pub preconditions: Preconditions,
271 pub effects: ActionEffects,
273 pub duration_secs: f32,
275 pub interrupt_priority: u32,
277 pub disabled: bool,
279 pub tags: Vec<String>,
281}
282
283impl Action {
284 pub fn new(name: &str, cost: f32) -> Self {
285 Self {
286 name: name.to_string(),
287 cost,
288 preconditions: Preconditions::new(),
289 effects: ActionEffects::new(),
290 duration_secs: 0.0,
291 interrupt_priority: 0,
292 disabled: false,
293 tags: Vec::new(),
294 }
295 }
296
297 pub fn require_bool(mut self, key: &str, value: bool) -> Self {
300 self.preconditions = self.preconditions.require_bool(key, value);
301 self
302 }
303
304 pub fn require_float_gte(mut self, key: &str, min: f32) -> Self {
305 self.preconditions = self.preconditions.require_float_gte(key, min);
306 self
307 }
308
309 pub fn require_float_lte(mut self, key: &str, max: f32) -> Self {
310 self.preconditions = self.preconditions.require_float_lte(key, max);
311 self
312 }
313
314 pub fn require_float_gt(mut self, key: &str, val: f32) -> Self {
315 self.preconditions = self.preconditions.require_float_gt(key, val);
316 self
317 }
318
319 pub fn require_float_lt(mut self, key: &str, val: f32) -> Self {
320 self.preconditions = self.preconditions.require_float_lt(key, val);
321 self
322 }
323
324 pub fn effect_bool(mut self, key: &str, value: bool) -> Self {
327 self.effects = self.effects.set_bool(key, value);
328 self
329 }
330
331 pub fn effect_add_float(mut self, key: &str, delta: f32) -> Self {
332 self.effects = self.effects.add_float(key, delta);
333 self
334 }
335
336 pub fn effect_set_float(mut self, key: &str, value: f32) -> Self {
337 self.effects = self.effects.set_float(key, value);
338 self
339 }
340
341 pub fn with_duration(mut self, secs: f32) -> Self {
344 self.duration_secs = secs;
345 self
346 }
347
348 pub fn with_interrupt_priority(mut self, p: u32) -> Self {
349 self.interrupt_priority = p;
350 self
351 }
352
353 pub fn with_tag(mut self, tag: &str) -> Self {
354 self.tags.push(tag.to_string());
355 self
356 }
357
358 pub fn disabled(mut self) -> Self {
359 self.disabled = true;
360 self
361 }
362
363 pub fn is_applicable(&self, state: &WorldState) -> bool {
366 !self.disabled && self.preconditions.satisfied_by(state)
367 }
368
369 pub fn apply_effects(&self, state: &WorldState) -> WorldState {
371 state.apply(&self.effects)
372 }
373}
374
375#[derive(Debug, Clone)]
381pub struct Goal {
382 pub name: String,
383 pub state: WorldState,
384 pub priority: u32,
385 pub ttl_secs: Option<f32>,
387 created_at: f32,
388}
389
390impl Goal {
391 pub fn new(name: &str, state: WorldState, priority: u32) -> Self {
392 Self { name: name.to_string(), state, priority, ttl_secs: None, created_at: 0.0 }
393 }
394
395 pub fn with_ttl(mut self, ttl_secs: f32) -> Self {
396 self.ttl_secs = Some(ttl_secs);
397 self
398 }
399
400 pub fn is_expired(&self, sim_time: f32) -> bool {
401 self.ttl_secs.map_or(false, |ttl| sim_time - self.created_at > ttl)
402 }
403}
404
405#[derive(Debug, Default)]
412pub struct GoalStack {
413 goals: Vec<Goal>,
414 sim_time: f32,
415}
416
417impl GoalStack {
418 pub fn new() -> Self { Self::default() }
419
420 pub fn push(&mut self, mut goal: Goal) {
422 goal.created_at = self.sim_time;
423 self.goals.push(goal);
424 self.goals.sort_by(|a, b| b.priority.cmp(&a.priority));
426 }
427
428 pub fn remove(&mut self, name: &str) {
430 self.goals.retain(|g| g.name != name);
431 }
432
433 pub fn active(&self) -> Option<&Goal> {
435 self.goals.iter().find(|g| !g.is_expired(self.sim_time))
436 }
437
438 pub fn tick(&mut self, dt: f32) {
440 self.sim_time += dt;
441 let t = self.sim_time;
442 self.goals.retain(|g| !g.is_expired(t));
443 }
444
445 pub fn is_empty(&self) -> bool { self.goals.is_empty() }
446 pub fn len(&self) -> usize { self.goals.len() }
447
448 pub fn iter(&self) -> impl Iterator<Item = &Goal> {
450 self.goals.iter()
451 }
452
453 pub fn has_goal(&self, name: &str) -> bool {
455 self.goals.iter().any(|g| g.name == name && !g.is_expired(self.sim_time))
456 }
457
458 pub fn sim_time(&self) -> f32 { self.sim_time }
459}
460
461#[derive(Clone)]
464struct SearchNode {
465 state: WorldState,
466 path: Vec<String>,
468 cost: f32,
470 heuristic: usize,
472}
473
474impl SearchNode {
475 fn f(&self) -> f32 { self.cost + self.heuristic as f32 }
476 fn f_ord(&self) -> u64 { (self.f() * 1_000_000.0) as u64 }
477}
478
479impl PartialEq for SearchNode {
480 fn eq(&self, other: &Self) -> bool { self.f_ord() == other.f_ord() }
481}
482impl Eq for SearchNode {}
483
484impl PartialOrd for SearchNode {
485 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
486}
487
488impl Ord for SearchNode {
489 fn cmp(&self, other: &Self) -> Ordering {
490 other.f_ord().cmp(&self.f_ord())
492 }
493}
494
495pub struct GoapPlanner;
499
500impl GoapPlanner {
501 pub fn plan(
505 start: &WorldState,
506 goal: &WorldState,
507 actions: &[Action],
508 max_depth: usize,
509 ) -> Option<Vec<String>> {
510 if start.satisfies(goal) {
511 return Some(Vec::new()); }
513
514 let mut open: BinaryHeap<SearchNode> = BinaryHeap::new();
515 let mut closed: HashSet<StateKey> = HashSet::new();
516
517 open.push(SearchNode {
518 state: start.clone(),
519 path: Vec::new(),
520 cost: 0.0,
521 heuristic: start.distance_to(goal),
522 });
523
524 while let Some(node) = open.pop() {
525 if node.state.satisfies(goal) {
526 return Some(node.path);
527 }
528
529 if node.path.len() >= max_depth { continue; }
530
531 let key = node.state.snapshot_key();
532 if closed.contains(&key) { continue; }
533 closed.insert(key);
534
535 for action in actions {
536 if !action.is_applicable(&node.state) { continue; }
537
538 let next_state = action.apply_effects(&node.state);
539 let next_key = next_state.snapshot_key();
540 if closed.contains(&next_key) { continue; }
541
542 let next_cost = node.cost + action.cost;
543 let mut next_path = node.path.clone();
544 next_path.push(action.name.clone());
545
546 open.push(SearchNode {
547 state: next_state.clone(),
548 path: next_path,
549 cost: next_cost,
550 heuristic: next_state.distance_to(goal),
551 });
552 }
553 }
554
555 None }
557
558 pub fn plan_with_cost(
560 start: &WorldState,
561 goal: &WorldState,
562 actions: &[Action],
563 max_depth: usize,
564 ) -> Option<(Vec<String>, f32)> {
565 if start.satisfies(goal) {
566 return Some((Vec::new(), 0.0));
567 }
568
569 let mut open: BinaryHeap<SearchNode> = BinaryHeap::new();
570 let mut closed: HashSet<StateKey> = HashSet::new();
571
572 open.push(SearchNode {
573 state: start.clone(),
574 path: Vec::new(),
575 cost: 0.0,
576 heuristic: start.distance_to(goal),
577 });
578
579 while let Some(node) = open.pop() {
580 if node.state.satisfies(goal) {
581 let cost = node.cost;
582 return Some((node.path, cost));
583 }
584
585 if node.path.len() >= max_depth { continue; }
586
587 let key = node.state.snapshot_key();
588 if closed.contains(&key) { continue; }
589 closed.insert(key);
590
591 for action in actions {
592 if !action.is_applicable(&node.state) { continue; }
593
594 let next_state = action.apply_effects(&node.state);
595 let next_key = next_state.snapshot_key();
596 if closed.contains(&next_key) { continue; }
597
598 let next_cost = node.cost + action.cost;
599 let mut next_path = node.path.clone();
600 next_path.push(action.name.clone());
601
602 open.push(SearchNode {
603 state: next_state.clone(),
604 path: next_path,
605 cost: next_cost,
606 heuristic: next_state.distance_to(goal),
607 });
608 }
609 }
610
611 None
612 }
613
614 pub fn plan_alternatives(
616 start: &WorldState,
617 goal: &WorldState,
618 actions: &[Action],
619 max_depth: usize,
620 max_plans: usize,
621 ) -> Vec<(Vec<String>, f32)> {
622 let mut results: Vec<(Vec<String>, f32)> = Vec::new();
623
624 let mut queue: VecDeque<SearchNode> = VecDeque::new();
626 queue.push_back(SearchNode {
627 state: start.clone(),
628 path: Vec::new(),
629 cost: 0.0,
630 heuristic: start.distance_to(goal),
631 });
632
633 let mut visited: HashSet<StateKey> = HashSet::new();
634
635 while let Some(node) = queue.pop_front() {
636 if results.len() >= max_plans { break; }
637
638 if node.state.satisfies(goal) {
639 results.push((node.path.clone(), node.cost));
640 continue; }
642
643 if node.path.len() >= max_depth { continue; }
644
645 let key = node.state.snapshot_key();
646 if visited.contains(&key) { continue; }
647 visited.insert(key);
648
649 for action in actions {
650 if !action.is_applicable(&node.state) { continue; }
651 let next_state = action.apply_effects(&node.state);
652 let mut next_path = node.path.clone();
653 next_path.push(action.name.clone());
654 queue.push_back(SearchNode {
655 state: next_state.clone(),
656 path: next_path,
657 cost: node.cost + action.cost,
658 heuristic: next_state.distance_to(goal),
659 });
660 }
661 }
662
663 results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
664 results
665 }
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Eq)]
672pub enum PlanStepStatus {
673 NotStarted,
674 InProgress,
675 Completed,
676 Failed,
677 Interrupted,
678}
679
680#[derive(Debug, Clone)]
682pub struct PlanStep {
683 pub action_name: String,
684 pub status: PlanStepStatus,
685 pub elapsed: f32,
687 pub duration: f32,
689}
690
691impl PlanStep {
692 fn new(action_name: &str, duration: f32) -> Self {
693 Self {
694 action_name: action_name.to_string(),
695 status: PlanStepStatus::NotStarted,
696 elapsed: 0.0,
697 duration,
698 }
699 }
700}
701
702#[derive(Debug, Clone, Copy, PartialEq, Eq)]
706pub enum ExecutorState {
707 Idle,
709 Executing,
711 Succeeded,
713 Replanning,
715 Failed,
717 Interrupted,
719}
720
721#[derive(Debug)]
742pub struct PlanExecutor {
743 actions: Vec<Action>,
745 steps: Vec<PlanStep>,
747 current: usize,
749 sim_state: WorldState,
751 goal: Option<WorldState>,
753 pub state: ExecutorState,
755 pub max_depth: usize,
757 pub sim_time: f32,
759 pub replan_count: u32,
761 pub max_replans: u32,
763 step_start_state: WorldState,
766 pub history: Vec<String>,
768}
769
770impl PlanExecutor {
771 pub fn new(actions: Vec<Action>, max_depth: usize) -> Self {
772 Self {
773 actions,
774 steps: Vec::new(),
775 current: 0,
776 sim_state: WorldState::new(),
777 goal: None,
778 state: ExecutorState::Idle,
779 max_depth,
780 sim_time: 0.0,
781 replan_count: 0,
782 max_replans: 5,
783 step_start_state: WorldState::new(),
784 history: Vec::new(),
785 }
786 }
787
788 pub fn set_world_state(&mut self, state: WorldState) {
790 self.sim_state = state;
791 }
792
793 pub fn update_bool(&mut self, key: &str, value: bool) {
795 self.sim_state.set_bool(key, value);
796 }
797
798 pub fn update_float(&mut self, key: &str, value: f32) {
800 self.sim_state.set_float(key, value);
801 }
802
803 pub fn start(&mut self, goal: WorldState) -> Result<usize, PlanError> {
807 self.goal = Some(goal.clone());
808 self.replan_count = 0;
809 self.history.clear();
810 self.do_plan(&goal)
811 }
812
813 pub fn interrupt(&mut self) {
815 if let Some(step) = self.steps.get_mut(self.current) {
816 step.status = PlanStepStatus::Interrupted;
817 }
818 self.state = ExecutorState::Interrupted;
819 }
820
821 pub fn tick(
830 &mut self,
831 dt: f32,
832 observe_state: impl Fn(&WorldState) -> WorldState,
833 ) -> Option<&str> {
834 self.sim_time += dt;
835
836 match self.state {
837 ExecutorState::Idle
838 | ExecutorState::Succeeded
839 | ExecutorState::Failed
840 | ExecutorState::Interrupted => return None,
841
842 ExecutorState::Replanning => {
843 if let Some(goal) = self.goal.clone() {
845 match self.do_plan(&goal) {
846 Ok(_) => {} Err(_) => {
848 self.state = ExecutorState::Failed;
849 return None;
850 }
851 }
852 } else {
853 self.state = ExecutorState::Idle;
854 return None;
855 }
856 }
857
858 ExecutorState::Executing => {}
859 }
860
861 if self.current >= self.steps.len() {
862 self.state = ExecutorState::Succeeded;
863 return None;
864 }
865
866 {
867 let step = &mut self.steps[self.current];
868 if step.status == PlanStepStatus::NotStarted {
869 step.status = PlanStepStatus::InProgress;
870 }
871 step.elapsed += dt;
872 }
873 self.step_start_state = self.sim_state.clone();
874
875 let observed = observe_state(&self.sim_state);
878 if self.state_has_drifted(&observed) {
879 self.sim_state.merge_from(&observed);
881 self.steps[self.current].status = PlanStepStatus::Interrupted;
882
883 if self.replan_count >= self.max_replans {
884 self.state = ExecutorState::Failed;
885 return None;
886 }
887
888 self.replan_count += 1;
889 self.state = ExecutorState::Replanning;
890 return Some(&self.steps[self.current].action_name);
891 }
892
893 self.sim_state.merge_from(&observed);
895
896 let step = &self.steps[self.current];
899 let action_name = step.action_name.clone();
900 let elapsed = step.elapsed;
901 let duration = step.duration;
902
903 if duration > 0.0 && elapsed < duration {
904 return Some(&self.steps[self.current].action_name);
905 }
906
907 if let Some(action) = self.find_action(&action_name) {
909 let effects = action.effects.clone();
910 self.sim_state = self.sim_state.apply(&effects);
911 }
912
913 self.steps[self.current].status = PlanStepStatus::Completed;
914 self.history.push(action_name);
915 self.current += 1;
916
917 if self.current >= self.steps.len() {
919 self.state = ExecutorState::Succeeded;
920 return None;
921 }
922
923 let next_name = self.steps[self.current].action_name.clone();
925 if let Some(action) = self.find_action(&next_name) {
926 if !action.is_applicable(&self.sim_state) {
927 if self.replan_count >= self.max_replans {
928 self.state = ExecutorState::Failed;
929 return None;
930 }
931 self.replan_count += 1;
932 self.state = ExecutorState::Replanning;
933 }
934 }
935
936 Some(&self.steps[self.current.saturating_sub(1)].action_name)
937 }
938
939 pub fn current_action(&self) -> Option<&str> {
941 if self.state == ExecutorState::Executing && self.current < self.steps.len() {
942 Some(&self.steps[self.current].action_name)
943 } else {
944 None
945 }
946 }
947
948 pub fn plan_names(&self) -> Vec<&str> {
950 self.steps.iter().map(|s| s.action_name.as_str()).collect()
951 }
952
953 pub fn progress(&self) -> (usize, usize) {
955 (self.current, self.steps.len())
956 }
957
958 pub fn has_succeeded(&self) -> bool { self.state == ExecutorState::Succeeded }
960
961 pub fn has_failed(&self) -> bool { self.state == ExecutorState::Failed }
963
964 pub fn reset(&mut self) {
966 self.steps = Vec::new();
967 self.current = 0;
968 self.goal = None;
969 self.state = ExecutorState::Idle;
970 self.replan_count = 0;
971 self.history.clear();
972 }
973
974 pub fn world_state(&self) -> &WorldState { &self.sim_state }
976
977 fn do_plan(&mut self, goal: &WorldState) -> Result<usize, PlanError> {
980 match GoapPlanner::plan(&self.sim_state, goal, &self.actions, self.max_depth) {
981 Some(names) => {
982 self.steps = names.iter().map(|n| {
983 let dur = self.find_action(n).map(|a| a.duration_secs).unwrap_or(0.0);
984 PlanStep::new(n, dur)
985 }).collect();
986 self.current = 0;
987 self.state = ExecutorState::Executing;
988 let len = self.steps.len();
989 Ok(len)
990 }
991 None => {
992 self.state = ExecutorState::Failed;
993 Err(PlanError::NoPlanFound)
994 }
995 }
996 }
997
998 fn find_action(&self, name: &str) -> Option<&Action> {
999 self.actions.iter().find(|a| a.name == name)
1000 }
1001
1002 fn state_has_drifted(&self, observed: &WorldState) -> bool {
1005 if self.current >= self.steps.len() { return false; }
1007 let name = &self.steps[self.current].action_name;
1008 if let Some(action) = self.find_action(name) {
1009 for (k, &expected) in &action.preconditions.bools {
1011 if observed.has_bool(k) && observed.get_bool(k) != expected {
1012 return true;
1013 }
1014 }
1015 for (k, &min) in &action.preconditions.floats_gte {
1017 if observed.has_float(k) && observed.get_float(k) < min {
1018 return true;
1019 }
1020 }
1021 for (k, &max) in &action.preconditions.floats_lte {
1022 if observed.has_float(k) && observed.get_float(k) > max {
1023 return true;
1024 }
1025 }
1026 }
1027 false
1028 }
1029}
1030
1031#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1034pub enum PlanError {
1035 NoPlanFound,
1037 NoActions,
1039 AlreadySatisfied,
1041}
1042
1043impl std::fmt::Display for PlanError {
1044 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1045 match self {
1046 PlanError::NoPlanFound => write!(f, "GOAP: no plan found"),
1047 PlanError::NoActions => write!(f, "GOAP: action library is empty"),
1048 PlanError::AlreadySatisfied => write!(f, "GOAP: goal already satisfied"),
1049 }
1050 }
1051}
1052
1053impl std::error::Error for PlanError {}
1054
1055#[derive(Debug, Default, Clone)]
1059pub struct ActionLibrary {
1060 actions: Vec<Action>,
1061}
1062
1063impl ActionLibrary {
1064 pub fn new() -> Self { Self::default() }
1065
1066 pub fn add(&mut self, action: Action) { self.actions.push(action); }
1067
1068 pub fn remove(&mut self, name: &str) {
1069 self.actions.retain(|a| a.name != name);
1070 }
1071
1072 pub fn get(&self, name: &str) -> Option<&Action> {
1073 self.actions.iter().find(|a| a.name == name)
1074 }
1075
1076 pub fn get_mut(&mut self, name: &str) -> Option<&mut Action> {
1077 self.actions.iter_mut().find(|a| a.name == name)
1078 }
1079
1080 pub fn enable(&mut self, name: &str) {
1081 if let Some(a) = self.get_mut(name) { a.disabled = false; }
1082 }
1083
1084 pub fn disable(&mut self, name: &str) {
1085 if let Some(a) = self.get_mut(name) { a.disabled = true; }
1086 }
1087
1088 pub fn all(&self) -> &[Action] { &self.actions }
1089
1090 pub fn by_tag(&self, tag: &str) -> Vec<&Action> {
1091 self.actions.iter().filter(|a| a.tags.iter().any(|t| t == tag)).collect()
1092 }
1093
1094 pub fn applicable(&self, state: &WorldState) -> Vec<&Action> {
1095 self.actions.iter().filter(|a| a.is_applicable(state)).collect()
1096 }
1097
1098 pub fn plan(
1099 &self,
1100 start: &WorldState,
1101 goal: &WorldState,
1102 max_depth: usize,
1103 ) -> Option<Vec<String>> {
1104 GoapPlanner::plan(start, goal, &self.actions, max_depth)
1105 }
1106}
1107
1108#[derive(Debug)]
1119pub struct GoapAgent {
1120 pub name: String,
1121 pub goals: GoalStack,
1122 pub library: ActionLibrary,
1123 pub executor: PlanExecutor,
1124 active_goal: Option<String>,
1125}
1126
1127impl GoapAgent {
1128 pub fn new(name: &str, actions: Vec<Action>, max_depth: usize) -> Self {
1129 let library = ActionLibrary { actions: actions.clone() };
1130 let executor = PlanExecutor::new(actions, max_depth);
1131 Self {
1132 name: name.to_string(),
1133 goals: GoalStack::new(),
1134 library,
1135 executor,
1136 active_goal: None,
1137 }
1138 }
1139
1140 pub fn push_goal(&mut self, goal: Goal) {
1142 self.goals.push(goal);
1143 }
1144
1145 pub fn set_world_state(&mut self, state: WorldState) {
1148 self.executor.set_world_state(state);
1149 }
1150
1151 pub fn set_bool(&mut self, key: &str, value: bool) {
1153 self.executor.update_bool(key, value);
1154 }
1155
1156 pub fn set_float(&mut self, key: &str, value: f32) {
1158 self.executor.update_float(key, value);
1159 }
1160
1161 pub fn tick(&mut self, dt: f32) -> Option<&str> {
1164 self.goals.tick(dt);
1165
1166 let desired = self.goals.active().map(|g| g.name.clone());
1168 if desired != self.active_goal {
1169 self.active_goal = desired.clone();
1170 if let Some(goal_name) = desired {
1171 if let Some(goal) = self.goals.iter()
1172 .find(|g| g.name == goal_name)
1173 .map(|g| g.state.clone())
1174 {
1175 let _ = self.executor.start(goal);
1177 }
1178 } else {
1179 self.executor.reset();
1180 }
1181 }
1182
1183 let world = self.executor.sim_state.clone();
1185 self.executor.tick(dt, |_| world.clone())
1186 }
1187
1188 pub fn current_action(&self) -> Option<&str> { self.executor.current_action() }
1189 pub fn is_idle(&self) -> bool { self.executor.state == ExecutorState::Idle }
1190 pub fn has_succeeded(&self) -> bool { self.executor.has_succeeded() }
1191 pub fn has_failed(&self) -> bool { self.executor.has_failed() }
1192 pub fn plan_names(&self) -> Vec<&str> { self.executor.plan_names() }
1193}
1194
1195#[cfg(test)]
1198mod tests {
1199 use super::*;
1200
1201 fn build_test_actions() -> Vec<Action> {
1202 vec![
1203 Action::new("pick_up_weapon", 1.0)
1204 .require_bool("has_weapon", false)
1205 .effect_bool("has_weapon", true),
1206
1207 Action::new("attack_enemy", 2.0)
1208 .require_bool("has_weapon", true)
1209 .require_bool("enemy_dead", false)
1210 .effect_bool("enemy_dead", true),
1211
1212 Action::new("flee", 0.5)
1213 .require_bool("enemy_dead", false)
1214 .effect_bool("safe", true),
1215 ]
1216 }
1217
1218 #[test]
1219 fn plan_pick_up_then_attack() {
1220 let mut start = WorldState::new();
1221 start.set_bool("has_weapon", false);
1222 start.set_bool("enemy_dead", false);
1223
1224 let mut goal = WorldState::new();
1225 goal.set_bool("enemy_dead", true);
1226
1227 let actions = build_test_actions();
1228 let plan = GoapPlanner::plan(&start, &goal, &actions, 5);
1229 assert!(plan.is_some());
1230 let plan = plan.unwrap();
1231 assert_eq!(plan, vec!["pick_up_weapon", "attack_enemy"]);
1232 }
1233
1234 #[test]
1235 fn plan_already_satisfied() {
1236 let mut start = WorldState::new();
1237 start.set_bool("enemy_dead", true);
1238
1239 let mut goal = WorldState::new();
1240 goal.set_bool("enemy_dead", true);
1241
1242 let actions = build_test_actions();
1243 let plan = GoapPlanner::plan(&start, &goal, &actions, 5).unwrap();
1244 assert!(plan.is_empty(), "Goal already satisfied — plan should be empty");
1245 }
1246
1247 #[test]
1248 fn plan_no_solution() {
1249 let start = WorldState::new();
1250 let mut goal = WorldState::new();
1251 goal.set_bool("magic_flag", true);
1252
1253 let actions = build_test_actions();
1254 let plan = GoapPlanner::plan(&start, &goal, &actions, 5);
1255 assert!(plan.is_none());
1256 }
1257
1258 #[test]
1259 fn world_state_satisfies() {
1260 let mut s = WorldState::new();
1261 s.set_bool("a", true);
1262 s.set_float("hp", 80.0);
1263
1264 let mut g = WorldState::new();
1265 g.set_bool("a", true);
1266 g.set_float("hp", 50.0); assert!(s.satisfies(&g));
1269
1270 s.set_float("hp", 30.0);
1271 assert!(!s.satisfies(&g));
1272 }
1273
1274 #[test]
1275 fn action_effects_applied() {
1276 let action = Action::new("test", 1.0)
1277 .effect_bool("door_open", true)
1278 .effect_set_float("energy", 0.0)
1279 .effect_add_float("gold", 10.0);
1280
1281 let mut state = WorldState::new();
1282 state.set_bool("door_open", false);
1283 state.set_float("energy", 100.0);
1284 state.set_float("gold", 5.0);
1285
1286 let next = action.apply_effects(&state);
1287 assert_eq!(next.get_bool("door_open"), true);
1288 assert_eq!(next.get_float("energy"), 0.0);
1289 assert_eq!(next.get_float("gold"), 15.0);
1290 }
1291
1292 #[test]
1293 fn goal_stack_priority_order() {
1294 let mut stack = GoalStack::new();
1295
1296 let mut g1 = WorldState::new(); g1.set_bool("low_priority", true);
1297 let mut g2 = WorldState::new(); g2.set_bool("high_priority", true);
1298
1299 stack.push(Goal::new("low", g1, 1));
1300 stack.push(Goal::new("high", g2, 10));
1301
1302 assert_eq!(stack.active().map(|g| g.name.as_str()), Some("high"));
1303 }
1304
1305 #[test]
1306 fn goal_ttl_expiry() {
1307 let mut stack = GoalStack::new();
1308 let mut g = WorldState::new(); g.set_bool("x", true);
1309 stack.push(Goal::new("temp", g, 1).with_ttl(0.5));
1310
1311 assert!(stack.active().is_some());
1312 stack.tick(1.0); assert!(stack.active().is_none());
1314 }
1315
1316 #[test]
1317 fn executor_completes_plan() {
1318 let actions = build_test_actions();
1319 let mut executor = PlanExecutor::new(actions, 10);
1320
1321 let mut ws = WorldState::new();
1322 ws.set_bool("has_weapon", false);
1323 ws.set_bool("enemy_dead", false);
1324 executor.set_world_state(ws);
1325
1326 let mut goal = WorldState::new();
1327 goal.set_bool("enemy_dead", true);
1328
1329 let result = executor.start(goal);
1330 assert!(result.is_ok(), "Expected a plan to be found");
1331 assert_eq!(result.unwrap(), 2, "Expected 2-step plan");
1332
1333 let names = executor.plan_names();
1334 assert_eq!(names, vec!["pick_up_weapon", "attack_enemy"]);
1335 }
1336
1337 #[test]
1338 fn plan_alternatives_returns_multiple() {
1339 let actions = build_test_actions();
1340 let mut start = WorldState::new();
1341 start.set_bool("has_weapon", false);
1342 start.set_bool("enemy_dead", false);
1343
1344 let mut goal = WorldState::new();
1345 goal.set_bool("enemy_dead", true);
1346
1347 let alts = GoapPlanner::plan_alternatives(&start, &goal, &actions, 5, 3);
1348 assert!(!alts.is_empty());
1349 }
1350
1351 #[test]
1352 fn action_library_applicable() {
1353 let mut lib = ActionLibrary::new();
1354 for a in build_test_actions() { lib.add(a); }
1355
1356 let mut ws = WorldState::new();
1357 ws.set_bool("has_weapon", false);
1358 ws.set_bool("enemy_dead", false);
1359
1360 let applicable = lib.applicable(&ws);
1361 let names: Vec<&str> = applicable.iter().map(|a| a.name.as_str()).collect();
1362 assert!(names.contains(&"pick_up_weapon"));
1363 assert!(names.contains(&"flee"));
1364 assert!(!names.contains(&"attack_enemy")); }
1366
1367 #[test]
1368 fn float_precondition_plan() {
1369 let actions = vec![
1370 Action::new("heal", 1.0)
1371 .require_float_lte("hp", 50.0)
1372 .effect_set_float("hp", 100.0),
1373 ];
1374
1375 let mut start = WorldState::new();
1376 start.set_float("hp", 30.0);
1377
1378 let mut goal = WorldState::new();
1379 goal.set_float("hp", 80.0);
1380
1381 let plan = GoapPlanner::plan(&start, &goal, &actions, 3);
1382 assert!(plan.is_some());
1383 assert_eq!(plan.unwrap(), vec!["heal"]);
1384 }
1385}