### Enemy FSM Setup Example Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/hierarchical_finite_state_machine/implementation.md Illustrates the setup of an enemy FSM, demonstrating how to define states and transitions for AI behavior. This example shows the integration of the `FSM` struct with custom enemy states and data. ```rust # use std::{collections::HashMap, hash::Hash}; # # type Condition = fn(&T) -> bool; # # trait State { # fn activated(&mut self, context: &mut T) {} # fn decide(&mut self, context: &mut T) {} # fn update(&mut self, context: &mut T) {} # } # # struct FSMTransition { # to: K, # condition: Condition, # } # # struct FSMState { # state: Box>, # transitions: Vec>, # } # # impl FSMState { # fn new + 'static>(state: S) -> Self { # Self { # state: Box::new(state), # transitions: vec![], # } # } # # fn transition(mut self, to: K, condition: Condition) -> Self { # self.transitions.push(FSMTransition {to, condition}); # self # } # # fn decide(&self, context: &T) -> Option where K: Clone { # for transition in &self.transitions { # if (transition.condition)(context) { # return Some(transition.to.clone()); # } # } # None # } # } # ``` -------------------------------- ### Complete FSM Setup with Enemy States Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Demonstrates the complete setup for a Finite State Machine, including the FSM struct, state registration, and the enemy-specific states and context. This code is runnable and shows how to initialize and use the FSM. ```Rust # use std::{collections::HashMap, hash::Hash}; # # trait State { # fn update(&mut self, context: &mut T) -> Option; # } # # struct FSM { # states: HashMap>>, # active_state: K, # } # # impl FSM { # fn new(active_state: K) -> Self { # Self { # states: Default::default(), # active_state, # } # } # # fn state + 'static>(mut self, id: K, state: S) -> Self { # self.states.insert(id, Box::new(state)); # self # } # # fn set_active_state(&mut self, id: K) { # if self.states.contains_key(&id) { # self.active_state = id; # } # } # # fn update(&mut self, context: &mut T) { # if let Some(state) = self.states.get_mut(&self.active_state) { # if let Some(id) = state.update(context) { # self.set_active_state(id); # } # } # } # } # # #[derive(Debug, PartialEq, Eq)] # enum Direction { # Up, # Down, # Left, # Right, # } # # impl Direction { # fn horizontal(&self) -> isize { # match self { # Self::Left => -1, # Self::Right => 1, # _ => 0, # } # } # # fn vertical(&self) -> isize { # match self { # Self::Up => -1, # Self::Down => 1, # _ => 0, # } # } # # fn next(&self) -> Self { # match self { # Self::Up => Self::Right, # Self::Down => Self::Left, # Self::Left => Self::Up, # Self::Right => Self::Down, # } # } # } # # #[derive(Debug, Hash, PartialEq, Eq)] # enum EnemyState { # Wait, # Move, # ChangeDirection, # } # # struct EnemyData { # position: (isize, isize), # direction: Direction, # } # # struct EnemyWaitState(pub usize); # # impl State for EnemyWaitState { # fn update(&mut self, context: &mut EnemyData) -> Option { # if self.0 > 0 { # self.0 -= 1; # None # } else { # Some(EnemyState::ChangeDirection) # } # } # } # # struct EnemyMoveState(pub usize); # # impl State for EnemyMoveState { # fn update(&mut self, context: &mut EnemyData) -> Option { # if self.0 > 0 { # self.0 -= 1; # context.position.0 += context.direction.horizontal(); # context.position.1 += context.direction.vertical(); # None # } else { # Some(EnemyState::Wait) # } # } # } # # struct EnemyChangeDirectionState; # # impl State for EnemyChangeDirectionState { # fn update(&mut self, context: &mut EnemyData) -> Option { # context.direction = context.direction.next(); # Some(EnemyState::Move) # } # } ``` -------------------------------- ### Basic Setup for Reasoner Decision Maker Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/use_of_reasoner.md This snippet demonstrates the necessary imports and enum/struct definitions for setting up the Reasoner decision maker. It includes an example enemy state enum and data struct. ```rust # 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, # } ``` -------------------------------- ### Hierarchical Machinery Setup Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/hierarchical_finite_state_machine/use_of_machinery.md Illustrates setting up a hierarchical finite state machine (HFSM) using the Machinery decision maker. This example defines states and data structures for an enemy AI, showing how to initialize the Machinery with an initial state. ```rust extern crate emergent; use emergent::prelude::*; use std::hash::Hash; #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Target { None, Found, Reached, } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] enum EnemyState { Patrol, Combat, FindWaypoint, WalkTowardsWaypoint, WalkTowardsPlayer, AttackPlayer, } struct EnemyData { waypoint: Target, player: Target, } struct Enemy { data: EnemyData, machinery: Machinery, } ``` -------------------------------- ### Enemy AI HFSM Setup Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/hierarchical_finite_state_machine/implementation.md This snippet shows the complete setup of an Enemy's Hierarchical Finite State Machine, including defining states like Patrol and Combat, and their respective transitions based on game conditions such as player proximity or status. ```rust use std::{collections::HashMap, hash::Hash}; type Condition = fn(&T) -> bool; trait State { fn activated(&mut self, context: &mut T) {} fn decide(&mut self, context: &mut T) {} fn update(&mut self, context: &mut T) {} } struct FSMTransition { to: K, condition: Condition, } struct FSMState { state: Box>, transitions: Vec>, } impl FSMState { fn new + 'static>(state: S) -> Self { Self { state: Box::new(state), transitions: vec![], } } fn transition(mut self, to: K, condition: Condition) -> Self { self.transitions.push(FSMTransition {to, condition}); self } fn decide(&self, context: &T) -> Option where K: Clone { for transition in &self.transitions { if (transition.condition)(context) { return Some(transition.to.clone()); } } None } } struct FSM { states: HashMap>, active_state: K, initial_state: K, } impl FSM { fn new(active_state: K) -> Self where K: Clone { Self { states: Default::default(), initial_state: active_state.clone(), active_state, } } fn state(mut self, id: K, state: FSMState) -> Self { self.states.insert(id, state); self } fn set_active_state(&mut self, id: K, context: &mut T, forced: bool) { if forced || id != self.active_state { if let Some(state) = self.states.get_mut(&id) { state.state.activated(context); self.active_state = id; } } } fn decide(&mut self, context: &mut T) where K: Clone { if let Some(state) = self.states.get_mut(&self.active_state) { if let Some(id) = state.decide(context) { self.set_active_state(id, context, false); } else { state.state.decide(context); } } } fn update(&mut self, context: &mut T) { if let Some(state) = self.states.get_mut(&self.active_state) { state.state.update(context); } } } impl State for FSM { fn activated(&mut self, context: &mut T) { self.set_active_state(self.initial_state.clone(), context, true); } fn decide(&mut self, context: &mut T) { self.decide(context); } fn update(&mut self, context: &mut T) { self.update(context); } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Target { None, Found, Reached, } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] enum EnemyState { Patrol, Combat, FindWaypoint, WalkTowardsWaypoint, WalkTowardsPlayer, AttackPlayer, } struct EnemyData { waypoint: Target, player: Target, } struct Enemy { data: EnemyData, fsm: FSM, } impl Enemy { fn new() -> Self { let mut data = EnemyData { waypoint: Target::None, player: Target::None, }; let patrol = FSM::new(EnemyState::FindWaypoint) .state( EnemyState::FindWaypoint, FSMState::new(EnemyFindWaypointState) .transition(EnemyState::WalkTowardsWaypoint, waypoint_found), ) .state( EnemyState::WalkTowardsWaypoint, FSMState::new(EnemyWalkTowardsWaypointState) .transition(EnemyState::FindWaypoint, waypoint_reached), ); let combat = FSM::new(EnemyState::WalkTowardsPlayer) .state( EnemyState::WalkTowardsPlayer, FSMState::new(EnemyWalkTowardsPlayerState) .transition(EnemyState::AttackPlayer, player_reached), ) .state( EnemyState::AttackPlayer, FSMState::new(EnemyAttackPlayerState) .transition(EnemyState::WalkTowardsPlayer, player_found), ); let mut fsm = FSM::new(EnemyState::Patrol) .state( EnemyState::Patrol, FSMState::new(patrol) .transition(EnemyState::Combat, player_found), ) .state( EnemyState::Combat, FSMState::new(combat) .transition(EnemyState::Patrol, player_dead), ); fsm.set_active_state(EnemyState::Patrol, &mut data, true); Self { data, fsm } } fn tick(&mut self) { self.fsm.decide(&mut self.data); self.fsm.update(&mut self.data); } } fn waypoint_found(data: &EnemyData) -> bool { data.waypoint == Target::Found } fn waypoint_reached(data: &EnemyData) -> bool { data.waypoint == Target::Reached } fn player_found(data: &EnemyData) -> bool { ``` -------------------------------- ### Enemy FSM Update Sequence Example Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Demonstrates the update sequence of an Enemy FSM, showing state changes and position updates over several iterations. This example assumes the Enemy struct and its associated states and data structures are defined. ```rust let mut enemy = Enemy::new(0, 0, Direction::Up); assert_eq!(enemy.fsm.active_state, EnemyState::ChangeDirection); assert_eq!(enemy.data.position.0, 0); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Up); for i in 0..3 { enemy.update(); assert_eq!(enemy.fsm.active_state, EnemyState::Move); assert_eq!(enemy.data.position.0, i); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); } for _ in 0..2 { enemy.update(); assert_eq!(enemy.fsm.active_state, EnemyState::Wait); assert_eq!(enemy.data.position.0, 2); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); } enemy.update(); assert_eq!(enemy.fsm.active_state, EnemyState::ChangeDirection); assert_eq!(enemy.data.position.0, 2); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); ``` -------------------------------- ### Enemy State Machine Example Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/hierarchical_finite_state_machine/implementation.md Demonstrates the behavior of an enemy state machine with various target states and transitions. Asserts the active state and data changes after each tick. ```rust fn player_reached(data: &EnemyData) -> bool { data.player == Target::Reached } fn player_dead(data: &EnemyData) -> bool { data.player == Target::None } struct EnemyFindWaypointState; impl State for EnemyFindWaypointState { fn activated(&mut self, context: &mut EnemyData) { context.waypoint = Target::Found; } } struct EnemyWalkTowardsWaypointState; impl State for EnemyWalkTowardsWaypointState { fn activated(&mut self, context: &mut EnemyData) { context.waypoint = Target::Reached; } } struct EnemyWalkTowardsPlayerState; impl State for EnemyWalkTowardsPlayerState { fn activated(&mut self, context: &mut EnemyData) { context.player = Target::Reached; } } struct EnemyAttackPlayerState; impl State for EnemyAttackPlayerState { fn activated(&mut self, context: &mut EnemyData) { context.player = Target::None; } } let mut enemy = Enemy::new(); enemy.data.waypoint = Target::Found; enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Patrol); assert_eq!(enemy.data.waypoint, Target::Reached); assert_eq!(enemy.data.player, Target::None); enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Patrol); assert_eq!(enemy.data.waypoint, Target::Found); assert_eq!(enemy.data.player, Target::None); enemy.data.player = Target::Found; enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Combat); assert_eq!(enemy.data.waypoint, Target::Found); assert_eq!(enemy.data.player, Target::Reached); enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Combat); assert_eq!(enemy.data.waypoint, Target::Found); assert_eq!(enemy.data.player, Target::None); enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Patrol); assert_eq!(enemy.data.waypoint, Target::Found); assert_eq!(enemy.data.player, Target::None); enemy.tick(); assert_eq!(enemy.fsm.active_state, EnemyState::Patrol); assert_eq!(enemy.data.waypoint, Target::Reached); assert_eq!(enemy.data.player, Target::None); ``` -------------------------------- ### Enemy FSM Setup and Usage Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Defines an Enemy struct with its data and FSM, including states like Wait, Move, and ChangeDirection. Demonstrates FSM initialization with states and transitions based on data conditions. ```rust struct EnemyData { position: (isize, isize), direction: Direction, // From not on we will keep track of remainding turns in the enemy data. // The reason for that is that all states that performs as long as there are // turns left, also you might have noticed that in previous versions of states, // as long as first cycle ends, all states doesn't wait for any turn because // we have zeroed their turn counters, which wasn't what we aimed for in the // first place. Now whenever state gets activated, it will set this counter // with value from its definition. turns: usize, } struct Enemy { data: EnemyData, fsm: FSM, } impl Enemy { fn new(x: isize, y: isize, direction: Direction) -> Self { let mut data = EnemyData { position: (x, y), direction, turns: 0, }; let fsm = FSM::new(EnemyState::ChangeDirection) .state( EnemyState::Wait, FSMState::new(EnemyWaitState(1)) .transition(EnemyState::ChangeDirection, |data| data.turns == 0), ) .state( EnemyState::Move, FSMState::new(EnemyMoveState(2)) .transition(EnemyState::Wait, |data| data.turns == 0), ) .state( EnemyState::ChangeDirection, FSMState::new(EnemyChangeDirectionState) .transition(EnemyState::Move, |_| true), ); Self { data, fsm } } fn tick(&mut self) { // For the simplicity we just perform decision making and update at once, // but you should be encouraged to call decision making only when it's needed, // or at least at lower frequency than update, because most of the times, if // not always, state changes are rare. self.fsm.decide(&mut self.data); self.fsm.update(&mut self.data); } } ``` -------------------------------- ### Utility AI State Score Calculation Example Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/explanation.md Demonstrates the step-by-step calculation of scores for different states using provided agent context values. This shows the practical application of the scoring formulas. ```plaintext - Idle: 0.001 - Gather Food: 0.5 * (1 - 0.9) -> 0.5 * 0.1 -> 0.05 - Gather Wood: 0 * (1 - 0.5) -> 0 * 0.5 -> 0 - Attack Opponent: (1 - 1.0) + 0.2 -> 0 + 0.2 -> 0.2 ``` -------------------------------- ### Enemy Struct and Initialization Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/implementation.md Defines the Enemy struct, its initial data, and the Utility AI setup with various states and their scoring functions. This is the core structure for the agent's decision-making process. ```rust struct Enemy { data: EnemyData, utility: Utility, } 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 utility = Utility::new(EnemyState::Idle) .state(EnemyState::Idle, EnemyIdleState, scorer_idle) .state(EnemyState::GatherFood, EnemyGatherFoodState, scorer_gather_food) .state(EnemyState::GatherWood, EnemyGatherWoodState, scorer_gather_wood) .state( EnemyState::AttackOpponent, EnemyAttackOpponentState, scorer_attack_opponent, ); Self { data, utility } } fn tick(&mut self) { self.utility.decide(&mut self.data); self.utility.update(&mut self.data); } } ``` -------------------------------- ### Enemy FSM Implementation with Machinery Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/use_of_machinery.md Defines states, tasks, and transitions for an enemy character using the Machinery decision maker. Includes setup for movement and direction changes. ```rust # extern crate emergent; # use emergent::prelude::*; # use std::hash::Hash; # # #[derive(Debug, Copy, Clone, PartialEq, Eq)] # enum Direction { # Up, # Down, # Left, # Right, # } # # impl Direction { # fn horizontal(&self) -> isize { # match self { # Self::Left => -1, # Self::Right => 1, # _ => 0, # } # } # # fn vertical(&self) -> isize { # match self { # Self::Up => -1, # Self::Down => 1, # _ => 0, # } # } # # fn next(&self) -> Self { # match self { # Self::Up => Self::Right, # Self::Down => Self::Left, # Self::Left => Self::Up, # Self::Right => Self::Down, # } # } # } # # #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] # enum EnemyState { # Wait, # Move, # ChangeDirection, # } # # struct EnemyData { # position: (isize, isize), # direction: Direction, # turns: usize, # } # struct WaitTask(pub usize); // Tasks are units that do the actual work of the state. impl Task for WaitTask { // While task is locked, FSM won't change to another state even if it can. // We lock this task for the time there are turns left. fn is_locked(&self, memory: &EnemyData) -> bool { memory.turns > 0 } fn on_enter(&mut self, memory: &mut EnemyData) { memory.turns = self.0; } fn on_update(&mut self, memory: &mut EnemyData) { memory.turns = memory.turns.max(1) - 1; } } struct MoveTask(pub usize); impl Task for MoveTask { fn is_locked(&self, memory: &EnemyData) -> bool { memory.turns > 0 } fn on_enter(&mut self, memory: &mut EnemyData) { memory.turns = self.0; } fn on_update(&mut self, memory: &mut EnemyData) { if memory.turns > 0 { memory.turns -= 1; memory.position.0 += memory.direction.horizontal(); memory.position.1 += memory.direction.vertical(); } } } struct ChangeDirectionTask; impl Task for ChangeDirectionTask { fn on_enter(&mut self, memory: &mut EnemyData) { memory.direction = memory.direction.next(); } } struct Enemy { data: EnemyData, machinery: Machinery, } impl Enemy { fn new(x: isize, y: isize, direction: Direction) -> Self { let mut data = EnemyData { position: (x, y), direction, turns: 0, }; let mut machinery = MachineryBuilder::default() .state( EnemyState::Wait, MachineryState::task(WaitTask(1)) // In `emergent` Conditions are traits that are implemented also for // booleans, which means we can just use constants as conditions so // here we make this transition always passing and the state locking // controls how long task will run. .change(MachineryChange::new(EnemyState::ChangeDirection, true)), ) .state( EnemyState::Move, MachineryState::task(MoveTask(2)) .change(MachineryChange::new(EnemyState::Wait, true)), ) .state( EnemyState::ChangeDirection, MachineryState::task(ChangeDirectionTask) .change(MachineryChange::new(EnemyState::Move, true)), ) .build(); // Newly created decision makers doesn't have any state activated and since // FSM can change its states starting from active state, we need to activate // first state by ourself. machinery.change_active_state( Some(EnemyState::ChangeDirection), &mut data, true, ); Self { data, machinery } } fn tick(&mut self) { // `process` method performs decision making. self.machinery.process(&mut self.data); self.machinery.update(&mut self.data); } } ``` ```rust let mut enemy = Enemy::new(0, 0, Direction::Up); assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::ChangeDirection)); assert_eq!(enemy.data.position.0, 0); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); for i in 1..3 { enemy.tick(); assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Move)); assert_eq!(enemy.data.position.0, i); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); } enemy.tick(); assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Wait)); assert_eq!(enemy.data.position.0, 2); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Right); enemy.tick(); assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::ChangeDirection)); assert_eq!(enemy.data.position.0, 2); assert_eq!(enemy.data.position.1, 0); assert_eq!(enemy.data.direction, Direction::Down); ``` -------------------------------- ### Enemy AI Structure and Reasoner Initialization Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/use_of_reasoner.md Defines the Enemy struct and initializes its Reasoner with various states and considerations. This setup is used for AI decision-making in a game context. ```rust struct Enemy { data: EnemyData, reasoner: Reasoner, } 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); } } ``` -------------------------------- ### Enemy FSM Initialization Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/hierarchical_finite_state_machine/implementation.md Initializes the Finite State Machine for an enemy, defining states for patrol and combat, and transitions between them based on player and waypoint status. This setup is used when creating a new enemy instance. ```Rust impl Enemy { fn new() -> Self { let mut data = EnemyData { waypoint: Target::None, player: Target::None, }; let patrol = FSM::new(EnemyState::FindWaypoint) .state( EnemyState::FindWaypoint, FSMState::new(EnemyFindWaypointState) .transition(EnemyState::WalkTowardsWaypoint, waypoint_found), ) .state( EnemyState::WalkTowardsWaypoint, FSMState::new(EnemyWalkTowardsWaypointState) .transition(EnemyState::FindWaypoint, waypoint_reached), ); let combat = FSM::new(EnemyState::WalkTowardsPlayer) .state( EnemyState::WalkTowardsPlayer, FSMState::new(EnemyWalkTowardsPlayerState) .transition(EnemyState::AttackPlayer, player_reached), ) .state( EnemyState::AttackPlayer, FSMState::new(EnemyAttackPlayerState) .transition(EnemyState::WalkTowardsPlayer, player_found), ); let mut fsm = FSM::new(EnemyState::Patrol) .state( EnemyState::Patrol, FSMState::new(patrol) .transition(EnemyState::Combat, player_found), ) .state( EnemyState::Combat, FSMState::new(combat) .transition(EnemyState::Patrol, player_dead), ); fsm.set_active_state(EnemyState::Patrol, &mut data, true); Self { data, fsm } } ``` -------------------------------- ### Utility AI State Scoring Examples Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/explanation.md Illustrates how different states are scored based on various factors like hunger, proximity, and needs. This helps in understanding the calculation logic for choosing the best action. ```plaintext - Idle: 0.001 - Gather Food: hunger * (1 - food proximity) - Gather Wood: (needs wood ? 1 : 0) * (1 - trees proximity) - Attack Opponent: (1 - opponent proximity) + opponent strength ``` -------------------------------- ### FSM Core Structures and Traits Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Defines the core components of the Finite State Machine (FSM) including the State trait, FSMState, FSMTransition, and the main FSM struct. This setup allows for generic states and transitions based on context. ```rust use std::{collections::HashMap, hash::Hash}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Direction { Up, Down, Left, Right, } impl Direction { fn horizontal(&self) -> isize { match self { Self::Left => -1, Self::Right => 1, _ => 0, } } fn vertical(&self) -> isize { match self { Self::Up => -1, Self::Down => 1, _ => 0, } } fn next(&self) -> Self { match self { Self::Up => Self::Right, Self::Down => Self::Left, Self::Left => Self::Up, Self::Right => Self::Down, } } } type Condition = fn(&T) -> bool; trait State { fn activated(&mut self, context: &mut T) {} fn update(&mut self, context: &mut T) {} } struct FSMTransition { to: K, condition: Condition, } struct FSMState { state: Box>, transitions: Vec>, } impl FSMState { fn new + 'static>(state: S) -> Self { Self { state: Box::new(state), transitions: vec![], } } fn transition(mut self, to: K, condition: Condition) -> Self { self.transitions.push(FSMTransition {to, condition}); self } fn decide(&self, context: &T) -> Option where K: Clone { for transition in &self.transitions { if (transition.condition)(context) { return Some(transition.to.clone()); } } None } } struct FSM { states: HashMap>, active_state: K, } impl FSM { fn new(active_state: K) -> Self { Self { states: Default::default(), active_state, } } fn state(mut self, id: K, state: FSMState) -> Self { self.states.insert(id, state); self } fn set_active_state(&mut self, id: K, context: &mut T) { if let Some(state) = self.states.get_mut(&id) { state.state.activated(context); self.active_state = id; } } fn decide(&mut self, context: &mut T) where K: Clone { if let Some(state) = self.states.get(&self.active_state) { if let Some(id) = state.decide(context) { self.set_active_state(id, context); } } } fn update(&mut self, context: &mut T) { if let Some(state) = self.states.get_mut(&self.active_state) { state.state.update(context); } } } ``` -------------------------------- ### Enemy Struct with FSM Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Defines the `Enemy` struct, which composes `EnemyData` and an `FSM`. The `new` function initializes the enemy with its starting position, direction, and configures the FSM with different states like Wait, Move, and ChangeDirection. The `update` method delegates to the FSM's update function. ```rust struct Enemy { data: EnemyData, fsm: FSM, } impl Enemy { fn new(x: isize, y: isize, direction: Direction) -> Self { let mut data = EnemyData { position: (x, y), direction, }; let mut fsm = FSM::new(EnemyState::ChangeDirection) .state(EnemyState::Wait, EnemyWaitState(1)) .state(EnemyState::ChangeDirection, EnemyChangeDirectionState) .state(EnemyState::Move, EnemyMoveState(2)); Self { data, fsm } } fn update(&mut self) { self.fsm.update(&mut self.data); } } ``` -------------------------------- ### Enemy AI Test Run and State Transitions Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/utility_ai/use_of_reasoner.md Demonstrates the initialization of an Enemy AI and its state transitions through multiple ticks, verifying data changes and active states. ```rust // 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)); ``` -------------------------------- ### FSM Core Structures and Implementation Source: https://github.com/psichix/emergent/blob/master/book/src/decision_makers/finite_state_machine/implementation.md Provides the fundamental structures and generic implementation for a Finite State Machine (FSM) in Rust, including states, transitions, and context updates. ```Rust # use std::{collections::HashMap, hash::Hash}; # # #[derive(Debug, Copy, Clone, PartialEq, Eq)] # enum Direction { # Up, # Down, # Left, # Right, # } # # impl Direction { # fn horizontal(&self) -> isize { # match self { # Self::Left => -1, # Self::Right => 1, # _ => 0, # } # } # # fn vertical(&self) -> isize { # match self { # Self::Up => -1, # Self::Down => 1, # _ => 0, # } # } # # fn next(&self) -> Self { # match self { # Self::Up => Self::Right, # Self::Down => Self::Left, # Self::Left => Self::Up, # Self::Right => Self::Down, # } # } # } # type Condition = fn(&T) -> bool; trait State { fn activated(&mut self, context: &mut T) {} fn update(&mut self, context: &mut T) {} } struct FSMTransition { to: K, condition: Condition, } struct FSMState { state: Box>, transitions: Vec>, } impl FSMState { fn new + 'static>(state: S) -> Self { Self { state: Box::new(state), transitions: vec![], } } fn transition(mut self, to: K, condition: Condition) -> Self { self.transitions.push(FSMTransition {to, condition}); self } fn decide(&self, context: &T) -> Option where K: Clone { for transition in &self.transitions { if (transition.condition)(context) { return Some(transition.to.clone()); } } None } } struct FSM { states: HashMap>, active_state: K, } impl FSM { fn new(active_state: K) -> Self { Self { states: Default::default(), active_state, } } fn state(mut self, id: K, state: FSMState) -> Self { self.states.insert(id, state); self } fn set_active_state(&mut self, id: K, context: &mut T) { if let Some(state) = self.states.get_mut(&id) { state.state.activated(context); self.active_state = id; } } fn decide(&mut self, context: &mut T) where K: Clone { if let Some(state) = self.states.get(&self.active_state) { if let Some(id) = state.decide(context) { self.set_active_state(id, context); } } } fn update(&mut self, context: &mut T) { if let Some(state) = self.states.get_mut(&self.active_state) { state.state.update(context); } } } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] enum EnemyState { Wait, Move, ChangeDirection, } struct EnemyData { position: (isize, isize), direction: Direction, turns: usize, } struct Enemy { data: EnemyData, fsm: FSM, } impl Enemy { fn new(x: isize, y: isize, direction: Direction) -> Self { let mut data = EnemyData { position: (x, y), direction, turns: 0, }; let fsm = FSM::new(EnemyState::ChangeDirection) .state( EnemyState::Wait, FSMState::new(EnemyWaitState(1)) .transition(EnemyState::ChangeDirection, |data| data.turns == 0), ) .state( EnemyState::Move, FSMState::new(EnemyMoveState(2)) .transition(EnemyState::Wait, |data| data.turns == 0), ) .state( EnemyState::ChangeDirection, FSMState::new(EnemyChangeDirectionState) .transition(EnemyState::Move, |_| true), ); Self { data, fsm } } } ```