Skip to content

Commit

Permalink
bugfix - make SequenceArgs public (#30)
Browse files Browse the repository at this point in the history
building of simple NPC AI example fails for rust version < 1.74.0. I
tested locally with Rust version 1.70.0 and was able to reproduce the
issue. The bug was raised in this issue:
#28

tested locally and the fix works on version 1.70.0 and 1.73.0
  • Loading branch information
Sollimann committed Jan 6, 2024
1 parent ec272b3 commit 637be3d
Show file tree
Hide file tree
Showing 3 changed files with 4 additions and 142 deletions.
141 changes: 1 addition & 140 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,146 +67,7 @@ see *async drone* example in the `/examples` folder for more details.

## Example of use

This is a enemy NPC (non-player-character) behavior mock-up which decides if the AI should shoot while running for nearby cover, rush in to attack the player up close or stand its ground while firing at the player.

#### Tree vizualization
<p align="center">
<img src="https://github.com/Sollimann/bonsai/blob/main/docs/resources/images/npc_bt.png" width="800" ">
</p>

#### Implementation
```rust
use std::{collections::HashMap, thread::sleep, time::Duration};

use bonsai_bt::{
Behavior::{Action, Select, Sequence},
Event, Status, Running, Timer, UpdateArgs, BT,
};

type Damage = u32;
type Distance = f64;

#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq)]
enum EnemyNPC {
/// Run
Run,
/// Cover
GetInCover,
/// Blind fire
BlindFire(Damage),
/// When player is close -> melee attack
///
/// distance [m], damage
MeleeAttack(Distance, Damage),
/// When player is far away -> fire weapon
FireWeapon(Damage),
}

use game::{Enemy, Player}; // fictive game imports

fn game_tick(timer: &mut Timer, bt: &mut BT<EnemyNPC, String, serde_json::Value>) {
// how much time should the bt advance into the future
let dt = timer.get_dt();

// proceed to next iteration in event loop
let e: Event = UpdateArgs { dt }.into();

#[rustfmt::skip]
bt.state.tick(&e,&mut |args: bonsai_bt::ActionArgs<Event, EnemyNPC>| {
match *args.action {
EnemyNPC::Run => {
Enemy::run_away_from_player(); // you must implement these methods
(bonsai_bt::Running, 0.0)
},
EnemyNPC::GetInCover => {
let in_cover: Bool = Enemy::get_in_cover();
if in_cover {
(bonsai_bt::Success, dt)
} else {
(bonsai_bt::Running, 0.0)
}
},
EnemyNPC::BlindFire(damage) => {
let has_ammo: Bool = Enemy::has_ammo();
if has_ammo {
Enemy::shoot_in_direction();
(bonsai_bt::Success, dt)
} else {
(bonsai_bt::Failure, dt)
}
},
EnemyNPC::MeleeAttack(dist, damage) => {
let player = Player::get_player();
let pos = Enemy::get_pos();
let diff = sub(*pos, player.pos);
if len(diff) < dist {
let &mut player_health = Player::get_health();
*player_health = Player::decrease_health(damage);
(bonsai_bt::Success, dt)
} else {
(bonsai_bt::Failure, dt)
}
},
EnemyNPC::FireWeapon(damage) => {
let has_ammo: Bool = Enemy::has_ammo();
if has_ammo {
Enemy::shoot_at_player();
(bonsai_bt::Success, dt)
} else {
(bonsai_bt::Failure, dt)
}
},
}
});
}

fn main() {
use crate::EnemyNPC::{BlindFire, FireWeapon, GetInCover, MeleeAttack, Run};

// define blackboard (even though we're not using it)
let blackboard: HashMap<String, serde_json::Value> = HashMap::new();

// create ai behavior
let run = Action(Run);
let cover = Action(GetInCover);
let run_for_five_secs = While(Box::new(Wait(5.0)), vec![run]);
let run_and_shoot = While(Box::new(run_for_five_secs), vec![Action(BlindFire(50))]);
let run_cover = Sequence(vec![run_and_shoot, cover]);

let player_close = Select(vec![Action(MeleeAttack(1.0, 100)), Action(FireWeapon(50))]);
let under_attack_behavior = Select(vec![run_cover, player_close]);

let bt_serialized = serde_json::to_string_pretty(&under_attack_behavior).unwrap();
println!("creating bt: \n {} \n", bt_serialized);
let mut bt = BT::new(under_attack_behavior, blackboard);

// create a monotonic timer
let mut timer = Timer::init_time();

loop {
// decide bt frequency by sleeping the loop
sleep(Duration::new(0, 0.1e+9 as u32));

// tick the bt
game_tick(&mut timer, &mut bt);
}
}
```

Compile the behavior tree into a [graphviz](https://graphviz.org/) compatible [DiGraph](https://docs.rs/petgraph/latest/petgraph/graph/type.DiGraph.html).

```rust
let mut bt = BT::new(under_attack_behavior, blackboard);

// produce a string DiGraph compatible with graphviz
// paste the contents in graphviz, e.g: https://dreampuf.github.io/GraphvizOnline/#
let g = bt.get_graphviz();
println!("{}", g);
```

<p align="center">
<img src="https://github.com/Sollimann/bonsai/blob/main/docs/resources/images/graphviz.png" width="700" ">
</p>
See [Examples](examples/README.md) folder.

## Similar Crates

Expand Down
2 changes: 1 addition & 1 deletion bonsai/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name = "bonsai-bt"
readme = "../README.md"
repository = "https://github.com/sollimann/bonsai.git"
rust-version = "1.60.0"
version = "0.6.0"
version = "0.6.1"

[lib]
name = "bonsai_bt"
Expand Down
3 changes: 2 additions & 1 deletion bonsai/src/sequence.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::status::Status::*;
use crate::{event::UpdateEvent, ActionArgs, Behavior, State, Status, RUNNING};
use std::fmt::Debug;
pub(crate) struct SequenceArgs<'a, A, E, F, B> {

pub struct SequenceArgs<'a, A, E, F, B> {
pub select: bool,
pub upd: Option<f64>,
pub seq: &'a [Behavior<A>],
Expand Down

0 comments on commit 637be3d

Please sign in to comment.