Use of Reasoner decision maker from emergent crate
Usage of Reasoner
decision maker from emergent
crate isn't much different
from what we have made by ourselves - what's different, considerations are actually
separate structs for which we implement Consideration
trait but we could also
just use scoring functions and wrap them with ClosureConsideration
, but we
want to keep ourselves on the full modularity track - the point is, there are
many ways you can organize your logic with emergent
crate, it is all for you
to decide what works best for you.
#![allow(unused)] fn main() { extern crate emergent; use emergent::prelude::*; #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] enum EnemyState { Idle, GatherFood, GatherWood, AttackOpponent, } struct EnemyData { hunger: f32, distance_to_food: f32, distance_to_trees: f32, wood_needed: usize, distance_to_opponent: f32, opponent_strength: f32, } struct Enemy { data: EnemyData, reasoner: Reasoner<EnemyData, EnemyState>, } impl Enemy { fn new() -> Self { let data = EnemyData { hunger: 0.0, distance_to_food: 1.0, distance_to_trees: 1.0, wood_needed: 0, distance_to_opponent: 1.0, opponent_strength: 0.0, }; let reasoner = ReasonerBuilder::default() // Just like with conditions, Consideration trait is implemented to scalars // so we can just use constants here. // This translates to: `0.001` .state(EnemyState::Idle, ReasonerState::new(0.001, EnemyIdleTask)) .state(EnemyState::GatherFood, ReasonerState::new( // Product evaluator will multiply all its children consideration scores. // This translates to: `hunger * (1 - food proximity)` EvaluatorProduct::default() .consideration(Hunger) .consideration(ConsiderationRemap::new( FoodProximity, ReverseScoreMapping, )), EnemyGatherFoodTask, )) .state(EnemyState::GatherWood, ReasonerState::new( // This translates to: `(wood needed ? 1 : 0) * (1 - trees proximity)` EvaluatorProduct::default() .consideration(ConditionConsideration::unit(WoodNeeded)) .consideration(ConsiderationRemap::new( TreesProximity, ReverseScoreMapping, )), EnemyGatherWoodTask, )) .state(EnemyState::AttackOpponent, ReasonerState::new( // This translates to: `(1 - opponent proximity) + opponent strength` EvaluatorSum::default() .consideration(ConsiderationRemap::new( OpponentProximity, ReverseScoreMapping, )) .consideration(OpponentStrength), EnemyAttackOpponentTask, )) .build(); Self { data, reasoner } } fn tick(&mut self) { self.reasoner.process(&mut self.data); self.reasoner.update(&mut self.data); } } // Conditions struct WoodNeeded; impl Condition<EnemyData> for WoodNeeded { fn validate(&self, memory: &EnemyData) -> bool { memory.wood_needed > 0 } } // Considerations struct Hunger; impl Consideration<EnemyData> for Hunger { fn score(&self, memory: &EnemyData) -> Scalar { memory.hunger } } struct FoodProximity; impl Consideration<EnemyData> for FoodProximity { fn score(&self, memory: &EnemyData) -> Scalar { memory.distance_to_food } } struct TreesProximity; impl Consideration<EnemyData> for TreesProximity { fn score(&self, memory: &EnemyData) -> Scalar { memory.distance_to_trees } } struct OpponentProximity; impl Consideration<EnemyData> for OpponentProximity { fn score(&self, memory: &EnemyData) -> Scalar { memory.distance_to_opponent } } struct OpponentStrength; impl Consideration<EnemyData> for OpponentStrength { fn score(&self, memory: &EnemyData) -> Scalar { memory.opponent_strength } } // Enemy state tasks struct EnemyIdleTask; impl Task<EnemyData> for EnemyIdleTask {} struct EnemyGatherFoodTask; impl Task<EnemyData> for EnemyGatherFoodTask { fn on_enter(&mut self, memory: &mut EnemyData) { memory.hunger = 0.0; memory.distance_to_food = 1.0; } } struct EnemyGatherWoodTask; impl Task<EnemyData> for EnemyGatherWoodTask { fn on_enter(&mut self, memory: &mut EnemyData) { memory.wood_needed = memory.wood_needed.max(1) - 1; memory.distance_to_trees = 1.0; } } struct EnemyAttackOpponentTask; impl Task<EnemyData> for EnemyAttackOpponentTask { fn on_enter(&mut self, memory: &mut EnemyData) { memory.distance_to_opponent = 1.0; memory.opponent_strength = 0.0; } } // Test run let mut enemy = Enemy::new(); assert_eq!(enemy.reasoner.active_state(), None); assert_eq!(enemy.data.hunger, 0.0); assert_eq!(enemy.data.distance_to_food, 1.0); assert_eq!(enemy.data.distance_to_trees, 1.0); assert_eq!(enemy.data.wood_needed, 0); assert_eq!(enemy.data.distance_to_opponent, 1.0); assert_eq!(enemy.data.opponent_strength, 0.0); enemy.data.hunger = 0.5; enemy.data.distance_to_food = 0.9; enemy.data.distance_to_trees = 0.5; enemy.data.opponent_strength = 0.2; enemy.tick(); assert_eq!(enemy.reasoner.active_state(), Some(&EnemyState::AttackOpponent)); assert_eq!(enemy.data.hunger, 0.5); assert_eq!(enemy.data.distance_to_food, 0.9); assert_eq!(enemy.data.distance_to_trees, 0.5); assert_eq!(enemy.data.wood_needed, 0); assert_eq!(enemy.data.distance_to_opponent, 1.0); assert_eq!(enemy.data.opponent_strength, 0.0); enemy.tick(); assert_eq!(enemy.reasoner.active_state(), Some(&EnemyState::GatherFood)); assert_eq!(enemy.data.hunger, 0.0); assert_eq!(enemy.data.distance_to_food, 1.0); assert_eq!(enemy.data.distance_to_trees, 0.5); assert_eq!(enemy.data.wood_needed, 0); assert_eq!(enemy.data.distance_to_opponent, 1.0); assert_eq!(enemy.data.opponent_strength, 0.0); enemy.data.wood_needed = 1; enemy.tick(); assert_eq!(enemy.reasoner.active_state(), Some(&EnemyState::GatherWood)); assert_eq!(enemy.data.hunger, 0.0); assert_eq!(enemy.data.distance_to_food, 1.0); assert_eq!(enemy.data.distance_to_trees, 1.0); assert_eq!(enemy.data.wood_needed, 0); assert_eq!(enemy.data.distance_to_opponent, 1.0); assert_eq!(enemy.data.opponent_strength, 0.0); enemy.tick(); assert_eq!(enemy.reasoner.active_state(), Some(&EnemyState::Idle)); assert_eq!(enemy.data.hunger, 0.0); assert_eq!(enemy.data.distance_to_food, 1.0); assert_eq!(enemy.data.distance_to_trees, 1.0); assert_eq!(enemy.data.wood_needed, 0); assert_eq!(enemy.data.distance_to_opponent, 1.0); assert_eq!(enemy.data.opponent_strength, 0.0); }