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}