aboutsummaryrefslogtreecommitdiff
path: root/src/statistics/boon.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/statistics/boon.rs')
-rw-r--r--src/statistics/boon.rs175
1 files changed, 175 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);
+ }
+}