zzstat/
source.rs

1//! Stat sources module.
2//!
3//! Sources produce base values for stats. Multiple sources for the same
4//! stat are summed together (additive). Sources are stateless and
5//! deterministic - the same input always produces the same output.
6
7use crate::context::StatContext;
8use crate::stat_id::StatId;
9use std::collections::HashMap;
10
11/// Trait for stat sources that produce base values.
12///
13/// Sources are stateless and deterministic - same input always produces
14/// same output. Multiple sources for the same stat are summed together
15/// (additive).
16///
17/// # Examples
18///
19/// ```rust
20/// use zzstat::{StatSource, StatId, StatContext};
21/// use zzstat::source::ConstantSource;
22///
23/// let source = ConstantSource(100.0);
24/// let context = StatContext::new();
25/// let stat_id = StatId::from_str("HP");
26///
27/// let value = source.get_value(&stat_id, &context);
28/// assert_eq!(value, 100.0);
29/// ```
30pub trait StatSource: Send + Sync {
31    /// Get the value for a stat from this source.
32    ///
33    /// # Arguments
34    ///
35    /// * `stat_id` - The stat identifier
36    /// * `context` - The stat context (may be used for conditional values)
37    ///
38    /// # Returns
39    ///
40    /// The base value contributed by this source.
41    fn get_value(&self, stat_id: &StatId, context: &StatContext) -> f64;
42}
43
44/// A constant source that always returns the same value.
45///
46/// This is the simplest source type - it always produces the same
47/// value regardless of context.
48///
49/// # Examples
50///
51/// ```rust
52/// use zzstat::source::{ConstantSource, StatSource};
53/// use zzstat::{StatId, StatContext};
54///
55/// let source = ConstantSource(100.0);
56/// let context = StatContext::new();
57/// let stat_id = StatId::from_str("HP");
58///
59/// assert_eq!(source.get_value(&stat_id, &context), 100.0);
60/// ```
61#[derive(Debug, Clone)]
62pub struct ConstantSource(pub f64);
63
64impl StatSource for ConstantSource {
65    fn get_value(&self, _stat_id: &StatId, _context: &StatContext) -> f64 {
66        self.0
67    }
68}
69
70/// A map-based source that looks up values by StatId.
71///
72/// Useful when you have a collection of stat values that you want
73/// to use as sources. Returns 0.0 for stats not in the map.
74///
75/// # Examples
76///
77/// ```rust
78/// use zzstat::source::{MapSource, StatSource};
79/// use zzstat::{StatId, StatContext};
80/// use std::collections::HashMap;
81///
82/// let mut values = HashMap::new();
83/// values.insert(StatId::from_str("HP"), 100.0);
84/// values.insert(StatId::from_str("MP"), 50.0);
85///
86/// let source = MapSource::new(values);
87/// let context = StatContext::new();
88///
89/// assert_eq!(source.get_value(&StatId::from_str("HP"), &context), 100.0);
90/// assert_eq!(source.get_value(&StatId::from_str("MP"), &context), 50.0);
91/// assert_eq!(source.get_value(&StatId::from_str("ATK"), &context), 0.0);
92/// ```
93#[derive(Debug, Clone)]
94pub struct MapSource {
95    values: HashMap<StatId, f64>,
96}
97
98impl MapSource {
99    /// Create a new `MapSource` from a `HashMap`.
100    ///
101    /// # Examples
102    ///
103    /// ```rust
104    /// use zzstat::source::MapSource;
105    /// use zzstat::StatId;
106    /// use std::collections::HashMap;
107    ///
108    /// let mut values = HashMap::new();
109    /// values.insert(StatId::from_str("HP"), 100.0);
110    /// let source = MapSource::new(values);
111    /// ```
112    pub fn new(values: HashMap<StatId, f64>) -> Self {
113        Self { values }
114    }
115
116    /// Create a new empty `MapSource`.
117    ///
118    /// # Examples
119    ///
120    /// ```rust
121    /// use zzstat::source::MapSource;
122    ///
123    /// let mut source = MapSource::empty();
124    /// source.insert(zzstat::StatId::from_str("HP"), 100.0);
125    /// ```
126    pub fn empty() -> Self {
127        Self {
128            values: HashMap::new(),
129        }
130    }
131
132    /// Insert a value into the map.
133    ///
134    /// # Examples
135    ///
136    /// ```rust
137    /// use zzstat::source::MapSource;
138    ///
139    /// let mut source = MapSource::empty();
140    /// source.insert(zzstat::StatId::from_str("HP"), 100.0);
141    /// ```
142    pub fn insert(&mut self, stat_id: StatId, value: f64) {
143        self.values.insert(stat_id, value);
144    }
145}
146
147impl StatSource for MapSource {
148    fn get_value(&self, stat_id: &StatId, _context: &StatContext) -> f64 {
149        self.values.get(stat_id).copied().unwrap_or(0.0)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_constant_source() {
159        let source = ConstantSource(100.0);
160        let context = StatContext::new();
161        let stat_id = StatId::from_str("HP");
162
163        assert_eq!(source.get_value(&stat_id, &context), 100.0);
164    }
165
166    #[test]
167    fn test_map_source() {
168        let mut source = MapSource::empty();
169        let hp_id = StatId::from_str("HP");
170        let atk_id = StatId::from_str("ATK");
171
172        source.insert(hp_id.clone(), 100.0);
173        source.insert(atk_id.clone(), 50.0);
174
175        let context = StatContext::new();
176        assert_eq!(source.get_value(&hp_id, &context), 100.0);
177        assert_eq!(source.get_value(&atk_id, &context), 50.0);
178        assert_eq!(
179            source.get_value(&StatId::from_str("MISSING"), &context),
180            0.0
181        );
182    }
183}