battler_data/mons/
stat.rs

1use std::fmt::Debug;
2
3use ahash::HashMap;
4use serde::{
5    Deserialize,
6    Serialize,
7};
8use serde_string_enum::{
9    DeserializeLabeledStringEnum,
10    SerializeLabeledStringEnum,
11};
12
13/// A single stat value.
14#[derive(
15    Debug,
16    Clone,
17    Copy,
18    PartialEq,
19    Eq,
20    Hash,
21    SerializeLabeledStringEnum,
22    DeserializeLabeledStringEnum,
23)]
24pub enum Stat {
25    #[string = "hp"]
26    HP,
27    #[string = "atk"]
28    #[alias = "Attack"]
29    Atk,
30    #[string = "def"]
31    #[alias = "Defense"]
32    Def,
33    #[string = "spa"]
34    #[alias = "spatk"]
35    #[alias = "Sp.Atk"]
36    #[alias = "Special Attack"]
37    SpAtk,
38    #[string = "spd"]
39    #[alias = "spdef"]
40    #[alias = "Sp.Def"]
41    #[alias = "Special Defense"]
42    SpDef,
43    #[string = "spe"]
44    #[alias = "Speed"]
45    Spe,
46}
47
48/// A map of values for each stat.
49pub type StatMap<T> = HashMap<Stat, T>;
50
51/// A table of stat values.
52pub type PartialStatTable = StatMap<u16>;
53
54fn next_stat_for_iterator(stat: Stat) -> Option<Stat> {
55    match stat {
56        Stat::HP => Some(Stat::Atk),
57        Stat::Atk => Some(Stat::Def),
58        Stat::Def => Some(Stat::SpAtk),
59        Stat::SpAtk => Some(Stat::SpDef),
60        Stat::SpDef => Some(Stat::Spe),
61        Stat::Spe => None,
62    }
63}
64
65/// Iterator over the entries of a [`StatTable`].
66pub struct StatTableEntries<'s> {
67    table: &'s StatTable,
68    next_stat: Option<Stat>,
69}
70
71impl<'s> StatTableEntries<'s> {
72    /// Creates a new iterator over the entries of a [`StatTable`].
73    fn new(table: &'s StatTable) -> Self {
74        Self {
75            table,
76            next_stat: Some(Stat::HP),
77        }
78    }
79}
80
81impl<'s> Iterator for StatTableEntries<'s> {
82    type Item = (Stat, u16);
83
84    fn next(&mut self) -> Option<Self::Item> {
85        let stat = self.next_stat?;
86        let value = self.table.get(stat);
87        self.next_stat = next_stat_for_iterator(stat);
88        Some((stat, value))
89    }
90}
91
92/// A full stat table.
93///
94/// Similar to [`PartialStatTable`], but all values must be defined.
95#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
96pub struct StatTable {
97    #[serde(default)]
98    pub hp: u16,
99    #[serde(default)]
100    pub atk: u16,
101    #[serde(default)]
102    pub def: u16,
103    #[serde(default)]
104    pub spa: u16,
105    #[serde(default)]
106    pub spd: u16,
107    #[serde(default)]
108    pub spe: u16,
109}
110
111impl StatTable {
112    /// Returns the value for the given stat.
113    pub fn get(&self, stat: Stat) -> u16 {
114        match stat {
115            Stat::HP => self.hp,
116            Stat::Atk => self.atk,
117            Stat::Def => self.def,
118            Stat::SpAtk => self.spa,
119            Stat::SpDef => self.spd,
120            Stat::Spe => self.spe,
121        }
122    }
123
124    /// Sets the given value in the stat table.
125    pub fn set(&mut self, stat: Stat, value: u16) {
126        let stat = match stat {
127            Stat::HP => &mut self.hp,
128            Stat::Atk => &mut self.atk,
129            Stat::Def => &mut self.def,
130            Stat::SpAtk => &mut self.spa,
131            Stat::SpDef => &mut self.spd,
132            Stat::Spe => &mut self.spe,
133        };
134        *stat = value;
135    }
136
137    /// Creates an iterator over all stat entries.
138    pub fn entries<'s>(&'s self) -> StatTableEntries<'s> {
139        StatTableEntries::new(self)
140    }
141
142    /// Creates an iterator over all stat values.
143    pub fn values<'s>(&'s self) -> impl Iterator<Item = u16> + 's {
144        self.entries().map(|(_, value)| value)
145    }
146
147    /// Sums up all stats in the table.
148    pub fn sum(&self) -> u32 {
149        self.hp as u32
150            + self.atk as u32
151            + self.def as u32
152            + self.spa as u32
153            + self.spd as u32
154            + self.spe as u32
155    }
156
157    /// Copies the stat tale without the HP stat.
158    pub fn without_hp(&self) -> PartialStatTable {
159        PartialStatTable::from_iter([
160            (Stat::Atk, self.atk),
161            (Stat::Def, self.def),
162            (Stat::SpAtk, self.spa),
163            (Stat::SpDef, self.spd),
164            (Stat::Spe, self.spe),
165        ])
166    }
167}
168
169impl From<&PartialStatTable> for StatTable {
170    fn from(value: &PartialStatTable) -> Self {
171        Self {
172            hp: *value.get(&Stat::HP).unwrap_or(&0) as u16,
173            atk: *value.get(&Stat::Atk).unwrap_or(&0) as u16,
174            def: *value.get(&Stat::Def).unwrap_or(&0) as u16,
175            spa: *value.get(&Stat::SpAtk).unwrap_or(&0) as u16,
176            spd: *value.get(&Stat::SpDef).unwrap_or(&0) as u16,
177            spe: *value.get(&Stat::Spe).unwrap_or(&0) as u16,
178        }
179    }
180}
181
182impl FromIterator<(Stat, u16)> for StatTable {
183    fn from_iter<T: IntoIterator<Item = (Stat, u16)>>(iter: T) -> Self {
184        let mut out = StatTable::default();
185        for (stat, value) in iter {
186            out.set(stat, value);
187        }
188        out
189    }
190}
191
192impl<'s> IntoIterator for &'s StatTable {
193    type IntoIter = StatTableEntries<'s>;
194    type Item = (Stat, u16);
195    fn into_iter(self) -> Self::IntoIter {
196        self.entries()
197    }
198}
199
200#[cfg(test)]
201mod stat_test {
202    use crate::{
203        mons::Stat,
204        test_util::{
205            test_string_deserialization,
206            test_string_serialization,
207        },
208    };
209
210    #[test]
211    fn serializes_to_string() {
212        test_string_serialization(Stat::HP, "hp");
213        test_string_serialization(Stat::Atk, "atk");
214        test_string_serialization(Stat::Def, "def");
215        test_string_serialization(Stat::SpAtk, "spa");
216        test_string_serialization(Stat::SpDef, "spd");
217        test_string_serialization(Stat::Spe, "spe");
218    }
219
220    #[test]
221    fn deserializes_capitalized() {
222        test_string_deserialization("HP", Stat::HP);
223        test_string_deserialization("Atk", Stat::Atk);
224        test_string_deserialization("Def", Stat::Def);
225        test_string_deserialization("SpAtk", Stat::SpAtk);
226        test_string_deserialization("SpDef", Stat::SpDef);
227        test_string_deserialization("Spe", Stat::Spe);
228    }
229
230    #[test]
231    fn deserializes_full_names() {
232        test_string_deserialization("Attack", Stat::Atk);
233        test_string_deserialization("Defense", Stat::Def);
234        test_string_deserialization("Special Attack", Stat::SpAtk);
235        test_string_deserialization("Sp.Atk", Stat::SpAtk);
236        test_string_deserialization("Special Defense", Stat::SpDef);
237        test_string_deserialization("Sp.Def", Stat::SpDef);
238        test_string_deserialization("Speed", Stat::Spe);
239    }
240}
241
242#[cfg(test)]
243mod stat_table_test {
244    use crate::{
245        PartialStatTable,
246        Stat,
247        StatTable,
248    };
249
250    #[test]
251    fn converts_from_partial_stat_table() {
252        let mut table = PartialStatTable::default();
253        table.insert(Stat::HP, 2);
254        table.insert(Stat::SpDef, 255);
255        let table = StatTable::from(&table);
256        assert_eq!(
257            table,
258            StatTable {
259                hp: 2,
260                atk: 0,
261                def: 0,
262                spa: 0,
263                spd: 255,
264                spe: 0,
265            }
266        )
267    }
268
269    #[test]
270    fn gets_associated_value() {
271        let st = StatTable {
272            hp: 1,
273            atk: 2,
274            def: 3,
275            spa: 4,
276            spd: 5,
277            spe: 6,
278        };
279        assert_eq!(st.get(Stat::HP), 1);
280        assert_eq!(st.get(Stat::Atk), 2);
281        assert_eq!(st.get(Stat::Def), 3);
282        assert_eq!(st.get(Stat::SpAtk), 4);
283        assert_eq!(st.get(Stat::SpDef), 5);
284        assert_eq!(st.get(Stat::Spe), 6);
285    }
286
287    #[test]
288    fn sets_associated_value() {
289        let mut st = StatTable {
290            hp: 1,
291            atk: 2,
292            def: 3,
293            spa: 4,
294            spd: 5,
295            spe: 6,
296        };
297        st.set(Stat::HP, 2);
298        st.set(Stat::Atk, 4);
299        st.set(Stat::Def, 6);
300        st.set(Stat::SpAtk, 8);
301        st.set(Stat::SpDef, 10);
302        st.set(Stat::Spe, 12);
303        assert_eq!(st.get(Stat::HP), 2);
304        assert_eq!(st.get(Stat::Atk), 4);
305        assert_eq!(st.get(Stat::Def), 6);
306        assert_eq!(st.get(Stat::SpAtk), 8);
307        assert_eq!(st.get(Stat::SpDef), 10);
308        assert_eq!(st.get(Stat::Spe), 12);
309    }
310
311    #[test]
312    fn sums() {
313        let st = StatTable {
314            hp: 100,
315            atk: 120,
316            def: 120,
317            spa: 150,
318            spd: 100,
319            spe: 90,
320        };
321        assert_eq!(st.sum(), 680);
322    }
323
324    #[test]
325    fn values_iterates_over_all_values() {
326        let st = StatTable {
327            hp: 100,
328            atk: 120,
329            def: 120,
330            spa: 150,
331            spd: 100,
332            spe: 90,
333        };
334        assert!(st.values().all(|val| val < 255));
335        assert_eq!(st.values().sum::<u16>(), 680);
336    }
337
338    #[test]
339    fn from_iter_constructs_table() {
340        let st = StatTable::from_iter([
341            (Stat::HP, 108),
342            (Stat::Atk, 130),
343            (Stat::Def, 95),
344            (Stat::SpAtk, 80),
345            (Stat::SpDef, 85),
346            (Stat::Spe, 102),
347        ]);
348        assert_eq!(
349            st,
350            StatTable {
351                hp: 108,
352                atk: 130,
353                def: 95,
354                spa: 80,
355                spd: 85,
356                spe: 102,
357            }
358        )
359    }
360}