Use of Machinery decision maker from emergent crate

If you would like to just use existing solution for FSM, consider trying emergent crate and its Machinery decision making engine:

#![allow(unused)]
fn main() {
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<EnemyData> 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<EnemyData> 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<EnemyData> for ChangeDirectionTask {
  fn on_enter(&mut self, memory: &mut EnemyData) {
    memory.direction = memory.direction.next();
  }
}

struct Enemy {
  data: EnemyData,
  machinery: Machinery<EnemyData, EnemyState>,
}

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);
  }
}

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);
}