nil-core 0.4.15

Multiplayer strategy game
Documentation
// Copyright (C) Call of Nil contributors
// SPDX-License-Identifier: AGPL-3.0-only

pub mod build;
pub mod idle;
pub mod recruit;

use crate::error::Result;
use crate::world::World;
use derive_more::Into;
use idle::IdleBehavior;
use rand::rngs::ThreadRng;
use rand::seq::{IndexedRandom, SliceRandom};
use std::any::Any;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::fmt::Debug;
use std::ops::{Add, AddAssign, ControlFlow, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};

pub trait Behavior: Any + Debug {
  fn score(&self, world: &World) -> Result<BehaviorScore>;
  fn behave(&self, world: &mut World) -> Result<ControlFlow<()>>;

  fn boxed(self) -> Box<dyn Behavior>
  where
    Self: Sized + 'static,
  {
    Box::new(self)
  }
}

#[must_use]
pub struct BehaviorProcessor<'a> {
  world: &'a mut World,
  behaviors: Vec<Box<dyn Behavior>>,
  buffer: Vec<(usize, BehaviorScore)>,
  candidates: Vec<(usize, f64)>,
  broken: HashSet<usize>,
  rng: ThreadRng,
}

impl<'a> BehaviorProcessor<'a> {
  pub(crate) fn new(world: &'a mut World, mut behaviors: Vec<Box<dyn Behavior>>) -> Self {
    let buffer = Vec::with_capacity(behaviors.len());
    let candidates = Vec::new();
    let broken = HashSet::new();

    let mut rng = rand::rng();
    behaviors.shuffle(&mut rng);

    Self {
      world,
      behaviors,
      buffer,
      candidates,
      broken,
      rng,
    }
  }

  fn is_idle(&self, idx: usize) -> bool {
    (self.behaviors[idx].as_ref() as &dyn Any).is::<IdleBehavior>()
  }
}

impl Iterator for BehaviorProcessor<'_> {
  type Item = Result<()>;

  fn next(&mut self) -> Option<Self::Item> {
    self.buffer.clear();
    self.candidates.clear();

    for (idx, behavior) in self.behaviors.iter().enumerate() {
      if !self.broken.contains(&idx) {
        match behavior.score(self.world) {
          Ok(score) => self.buffer.push((idx, score)),
          Err(err) => return Some(Err(err)),
        }
      }
    }

    self
      .buffer
      .sort_unstable_by_key(|(_, score)| *score);

    let highest = self.buffer.last()?;
    if self.is_idle(highest.0) {
      return None;
    }

    self
      .buffer
      .iter()
      .filter(|(_, score)| highest.1.is_within_range(*score, 0.2))
      .map(|(idx, score)| (*idx, f64::from(*score)))
      .collect_into(&mut self.candidates);

    let idx = self
      .candidates
      .choose(&mut self.rng)
      .map(|(idx, _)| *idx)
      .filter(|idx| !self.is_idle(*idx))?;

    let behavior = &self.behaviors[idx];
    match behavior.behave(self.world) {
      Ok(cf) => {
        if let ControlFlow::Break(()) = cf {
          self.broken.insert(idx);
        }

        Some(Ok(()))
      }
      Err(err) => Some(Err(err)),
    }
  }
}

#[derive(Clone, Copy, Debug, Into)]
pub struct BehaviorScore(f64);

impl BehaviorScore {
  pub const MIN: Self = BehaviorScore(0.0);
  pub const MAX: Self = BehaviorScore(1.0);

  #[inline]
  pub const fn new(score: f64) -> Self {
    debug_assert!(score.is_finite());
    debug_assert!(!score.is_subnormal());
    Self(score.clamp(Self::MIN.0, Self::MAX.0))
  }

  #[inline]
  pub(crate) fn is_within_range(self, other: BehaviorScore, range: f64) -> bool {
    (self.0 - other.0).abs() < range
  }
}

impl Default for BehaviorScore {
  fn default() -> Self {
    Self(0.0)
  }
}

impl From<f64> for BehaviorScore {
  fn from(score: f64) -> Self {
    Self::new(score)
  }
}

impl PartialEq for BehaviorScore {
  fn eq(&self, other: &Self) -> bool {
    matches!(self.0.total_cmp(&other.0), Ordering::Equal)
  }
}

impl Eq for BehaviorScore {}

impl PartialOrd for BehaviorScore {
  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    Some(self.cmp(other))
  }
}

impl Ord for BehaviorScore {
  fn cmp(&self, other: &Self) -> Ordering {
    self.0.total_cmp(&other.0)
  }
}

impl PartialEq<f64> for BehaviorScore {
  fn eq(&self, other: &f64) -> bool {
    self.0.eq(other)
  }
}

impl PartialOrd<f64> for BehaviorScore {
  fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
    self.0.partial_cmp(other)
  }
}

impl Add<f64> for BehaviorScore {
  type Output = BehaviorScore;

  fn add(self, rhs: f64) -> Self::Output {
    BehaviorScore::new(self.0 + rhs)
  }
}

impl AddAssign<f64> for BehaviorScore {
  fn add_assign(&mut self, rhs: f64) {
    *self = *self + rhs;
  }
}

impl Sub<f64> for BehaviorScore {
  type Output = BehaviorScore;

  fn sub(self, rhs: f64) -> Self::Output {
    BehaviorScore::new(self.0 - rhs)
  }
}

impl SubAssign<f64> for BehaviorScore {
  fn sub_assign(&mut self, rhs: f64) {
    *self = *self - rhs;
  }
}

impl Mul<f64> for BehaviorScore {
  type Output = BehaviorScore;

  fn mul(self, rhs: f64) -> Self::Output {
    BehaviorScore::new(self.0 * rhs)
  }
}

impl MulAssign<f64> for BehaviorScore {
  fn mul_assign(&mut self, rhs: f64) {
    *self = *self * rhs;
  }
}

impl Div<f64> for BehaviorScore {
  type Output = BehaviorScore;

  fn div(self, rhs: f64) -> Self::Output {
    BehaviorScore::new(self.0 / rhs)
  }
}

impl DivAssign<f64> for BehaviorScore {
  fn div_assign(&mut self, rhs: f64) {
    *self = *self / rhs;
  }
}