diff options
-rw-r--r-- | src/statistics/boon.rs | 175 | ||||
-rw-r--r-- | src/statistics/mod.rs (renamed from src/statistics.rs) | 2 |
2 files changed, 177 insertions, 0 deletions
diff --git a/src/statistics/boon.rs b/src/statistics/boon.rs new file mode 100644 index 0000000..65e5c89 --- /dev/null +++ b/src/statistics/boon.rs @@ -0,0 +1,175 @@ +use std::cmp; + +/// The type of a boon. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BoonType { + /// Boon stacks duration, e.g. Regeneration. + Duration, + /// Boon stacks intensity, e.g. Might. + Intensity, +} + +/// A struct that helps with simulating boon changes over time. +/// +/// This basically simulates a single boon-queue (for a single boon). +/// +/// # A quick word about how boon queues work +/// +/// For each boon, you have an internal *boon queue*, limited to a specific +/// capacity. +#[derive(Clone, Debug)] +pub struct BoonQueue { + capacity: u32, + queue: Vec<u64>, + boon_type: BoonType, +} + +impl BoonQueue { + /// Create a new boon queue. + /// + /// * `capacity` - The capacity of the queue. + /// * `boon_type` - How the boons stack. + pub fn new(capacity: u32, boon_type: BoonType) -> BoonQueue { + BoonQueue { + capacity, + queue: Vec::new(), + boon_type, + } + } + + fn fix_queue(&mut self) { + // Sort reversed, so that the longest stack is at the front. + self.queue.sort_unstable_by(|a, b| b.cmp(a)); + // Truncate queue by cutting of the shortest stacks + if self.queue.len() > self.capacity as usize { + self.queue.drain(self.capacity as usize..); + } + } + + /// Get the type of this boon. + pub fn boon_type(&self) -> BoonType { + self.boon_type + } + + /// Add a boon stack to this queue. + /// + /// * `duration` - Duration (in milliseconds) of the added stack. + pub fn add_stack(&mut self, duration: u64) { + self.queue.push(duration); + self.fix_queue(); + } + + /// Return the amount of current stacks. + /// + /// If the boon type is a duration boon, this will always return 0 or 1. + /// + /// If the boon type is an intensity boon, it will return the number of + /// stacks. + pub fn current_stacks(&self) -> u32 { + let result = match self.boon_type { + BoonType::Intensity => self.queue.len(), + BoonType::Duration => cmp::min(1, self.queue.len()), + }; + result as u32 + } + + /// Simulate time passing. + /// + /// This will decrease the remaining duration of the stacks accordingly. + /// + /// * `duration` - The amount of time (in milliseconds) to simulate. + pub fn simulate(&mut self, duration: u64) { + let mut remaining = duration; + match self.boon_type { + BoonType::Duration => { + while remaining > 0 && !self.queue.is_empty() { + let next = self.queue.remove(0); + if next > remaining { + self.queue.push(next - remaining); + break; + } else { + remaining -= next; + } + } + self.fix_queue(); + } + + BoonType::Intensity => { + self.queue = self.queue + .iter() + .cloned() + .filter(|v| *v > duration) + .map(|v| v - duration) + .collect(); + } + } + } + + /// Remove all stacks. + pub fn clear(&mut self) { + self.queue.clear(); + } + + /// Calculate when the stacks will have the next visible change. + /// + /// This assumes that the stacks will not be modified during this time. + /// + /// The return value is the duration in milliseconds. If the boon queue is + /// currently empty, 0 is returned. + pub fn next_change(&self) -> u64 { + match self.boon_type { + BoonType::Duration => self.queue.iter().sum(), + BoonType::Intensity => self.queue.last().cloned().unwrap_or(0), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_queue_capacity() { + let mut queue = BoonQueue::new(5, BoonType::Intensity); + assert_eq!(queue.current_stacks(), 0); + for _ in 0..10 { + queue.add_stack(10); + } + assert_eq!(queue.current_stacks(), 5); + } + + #[test] + fn test_simulate_duration() { + let mut queue = BoonQueue::new(10, BoonType::Duration); + queue.add_stack(10); + queue.add_stack(20); + assert_eq!(queue.current_stacks(), 1); + queue.simulate(30); + assert_eq!(queue.current_stacks(), 0); + + queue.add_stack(50); + queue.simulate(30); + assert_eq!(queue.current_stacks(), 1); + queue.simulate(10); + assert_eq!(queue.current_stacks(), 1); + queue.simulate(15); + assert_eq!(queue.current_stacks(), 0); + } + + #[test] + fn test_simulate_intensity() { + let mut queue = BoonQueue::new(5, BoonType::Intensity); + + queue.add_stack(10); + queue.add_stack(20); + assert_eq!(queue.current_stacks(), 2); + + queue.simulate(5); + assert_eq!(queue.current_stacks(), 2); + + queue.simulate(5); + assert_eq!(queue.current_stacks(), 1); + queue.simulate(15); + assert_eq!(queue.current_stacks(), 0); + } +} diff --git a/src/statistics.rs b/src/statistics/mod.rs index af208ce..a9dd178 100644 --- a/src/statistics.rs +++ b/src/statistics/mod.rs @@ -2,6 +2,8 @@ use super::*; use std::collections::HashMap; +pub mod boon; + pub type StatResult<T> = Result<T, StatError>; quick_error! { |