forked from Sollimann/bonsai
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.rs
More file actions
184 lines (169 loc) · 5.32 KB
/
Copy pathmain.rs
File metadata and controls
184 lines (169 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use bonsai_bt::Behavior::WhileAll;
use bonsai_bt::{Behavior::Action, Event, Failure, Running, Status, Success, UpdateArgs, BT};
#[derive(Clone, Debug, PartialEq)]
pub enum EnemyNPC {
Run,
Shoot,
HasActionPointsLeft,
Rest,
Die,
IsDead,
}
fn game_tick(bt: &mut BT<EnemyNPC, BlackBoardData>, state: &mut EnemyNPCState) -> Status {
let e: Event = UpdateArgs { dt: 0.0 }.into();
#[rustfmt::skip]
let status = bt.tick(&e, &mut |args: bonsai_bt::ActionArgs<Event, EnemyNPC>, blackboard| {
match *args.action {
EnemyNPC::Run => {
state.perform_action("run");
(Success, 0.0)
},
EnemyNPC::HasActionPointsLeft => {
if state.action_points == 0 {
println!("NPC does not have actions points left... ");
(Success, 0.0)
}
else {
println!("NPC has action points: {}", state.action_points );
(Running, 0.0)
}
}
EnemyNPC::Shoot => {
state.perform_action("shoot");
// for the sake of example we get access to blackboard and update
// one of its values here:
blackboard.times_shot += 1;
(Success, 0.0)
}
EnemyNPC::Rest => {
if state.fully_rested() {
return (Success, 0.0)
}
state.rest();
(Running, 0.0)
}
EnemyNPC::Die => {
state.die();
(Success, 0.0)
}
EnemyNPC::IsDead => {
if state.is_alive() {
return (Running, 0.0);
}
(Success, 0.0)
}
}
}).unwrap();
// return status:
status.0
}
struct EnemyNPCState {
pub action_points: usize,
pub max_action_points: usize,
pub alive: bool,
}
impl EnemyNPCState {
fn consume_action_point(&mut self) {
self.action_points = self.action_points.saturating_sub(1);
}
fn rest(&mut self) {
self.action_points = (self.action_points + 1).min(self.max_action_points);
println!("Rested for a while... Action points: {}", self.action_points);
}
fn die(&mut self) {
println!("NPC died...");
self.alive = false
}
fn is_alive(&self) -> bool {
if self.alive {
println!("NPC is alive...");
} else {
println!("NPC is dead...");
}
self.alive
}
fn fully_rested(&self) -> bool {
self.action_points == self.max_action_points
}
fn perform_action(&mut self, action: &str) {
if self.action_points > 0 {
self.consume_action_point();
println!("Performing action: {}. Action points: {}", action, self.action_points);
} else {
println!("Cannot perform action: {}. Not enough action points.", action);
}
}
}
/// Demonstrates a usage of [WhileAll] behavior with
/// a simple NPC simulation.
///
/// The NPC AI first enters a higher [WhileAll] that
/// checks if the NPC is dead, then it succeeds to inner [WhileAll]
/// where the NPC performs actions until it is determined that
/// no action points are left to consume. Then the AI control flow returns
/// to the previous higher sequence where the executions continues and the NPC rests
/// and regains its actions points. After that the NPC is killed and it is once again
/// checked if the NPC is alive. Then the program quits.
///
/// Timeline of execution in more detail:
///
/// 1. check if the NPC is dead (no)
/// 2. execute "run and shoot" subprogram
/// 3. check if action points are available (yes)
/// 4. run
/// 5. shoot
/// 6. check if action points are available (yes)
/// 7. run
/// 8. shoot (notice that we don't have action points
/// here but we try anyway and move on the sequence)
/// 9. check if action points are available (no)
/// 10. exit the subprogram
/// 11. rest and regain action points
/// (this action returns [Running] until fully rested
/// so control flow is returned to main loop)
/// 12. kill the NPC
/// 13. check if the NPC is dead (yes)
/// 14. quit
///
///
///
///
fn main() {
let run_and_shoot_ai = WhileAll(
Box::new(Action(EnemyNPC::HasActionPointsLeft)),
vec![Action(EnemyNPC::Run), Action(EnemyNPC::Shoot)],
);
let top_ai = WhileAll(
Box::new(Action(EnemyNPC::IsDead)),
vec![run_and_shoot_ai.clone(), Action(EnemyNPC::Rest), Action(EnemyNPC::Die)],
);
let blackboard = BlackBoardData { times_shot: 0 };
let mut bt = BT::new(top_ai, blackboard);
let print_graph = false;
if print_graph {
println!("{}", bt.get_graphviz());
}
let max_actions = 3;
let mut npc_state = EnemyNPCState {
action_points: max_actions,
max_action_points: max_actions,
alive: true,
};
loop {
println!("reached main loop...");
match game_tick(&mut bt, &mut npc_state) {
Success | Failure => {
break;
}
Running => {}
}
}
println!(
"NPC shot {} times during the simulation.",
bt.blackboard_mut().times_shot
);
}
#[derive(Debug)]
struct BlackBoardData {
times_shot: usize,
}