Skip to main content

battler_data/mons/
type.rs

1use core::fmt;
2
3use hashbrown::HashMap;
4use serde::{
5    Deserialize,
6    Serialize,
7    de::Visitor,
8};
9use serde_string_enum::{
10    DeserializeLabeledStringEnum,
11    SerializeLabeledStringEnum,
12};
13
14/// The type of a species, which determines its weaknesses and resistances.
15#[derive(
16    Debug,
17    Default,
18    Clone,
19    Copy,
20    PartialEq,
21    Eq,
22    PartialOrd,
23    Ord,
24    Hash,
25    SerializeLabeledStringEnum,
26    DeserializeLabeledStringEnum,
27)]
28pub enum Type {
29    #[string = "Normal"]
30    #[default]
31    Normal,
32    #[string = "Fighting"]
33    Fighting,
34    #[string = "Flying"]
35    Flying,
36    #[string = "Poison"]
37    Poison,
38    #[string = "Ground"]
39    Ground,
40    #[string = "Rock"]
41    Rock,
42    #[string = "Bug"]
43    Bug,
44    #[string = "Ghost"]
45    Ghost,
46    #[string = "Steel"]
47    Steel,
48    #[string = "Fire"]
49    Fire,
50    #[string = "Water"]
51    Water,
52    #[string = "Grass"]
53    Grass,
54    #[string = "Electric"]
55    Electric,
56    #[string = "Psychic"]
57    Psychic,
58    #[string = "Ice"]
59    Ice,
60    #[string = "Dragon"]
61    Dragon,
62    #[string = "Dark"]
63    Dark,
64    #[string = "Fairy"]
65    Fairy,
66    #[string = "None"]
67    None,
68    #[string = "Stellar"]
69    Stellar,
70}
71
72/// Type effectiveness of one type against another.
73#[derive(Debug, Default, Clone, Copy, PartialEq)]
74pub enum TypeEffectiveness {
75    /// No effect.
76    None,
77    /// Not very effective.
78    Weak,
79    /// Normal effectiveness.
80    #[default]
81    Normal,
82    /// Super effective.
83    Strong,
84}
85
86impl From<f32> for TypeEffectiveness {
87    fn from(value: f32) -> Self {
88        if value < 0f32 || (value).abs() < f32::EPSILON {
89            Self::None
90        } else if value < 0.5 || (value - 0.5).abs() < f32::EPSILON {
91            Self::Weak
92        } else if value < 1f32 || (value - 1f32).abs() < f32::EPSILON {
93            Self::Normal
94        } else {
95            Self::Strong
96        }
97    }
98}
99
100impl Into<f32> for TypeEffectiveness {
101    fn into(self) -> f32 {
102        match self {
103            Self::None => 0f32,
104            Self::Weak => 0.5,
105            Self::Normal => 1f32,
106            Self::Strong => 2f32,
107        }
108    }
109}
110
111impl Serialize for TypeEffectiveness {
112    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
113    where
114        S: serde::Serializer,
115    {
116        match &self {
117            Self::None => serializer.serialize_u32(Into::<f32>::into(*self) as u32),
118            TypeEffectiveness::Weak => serializer.serialize_f32(Into::<f32>::into(*self)),
119            Self::Normal => serializer.serialize_u32(Into::<f32>::into(*self) as u32),
120            Self::Strong => serializer.serialize_u32(Into::<f32>::into(*self) as u32),
121        }
122    }
123}
124
125struct TypeEffectivenessVisitor;
126
127impl<'de> Visitor<'de> for TypeEffectivenessVisitor {
128    type Value = TypeEffectiveness;
129
130    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
131        formatter.write_str("one of the following values: 0, 0.5, 1, 2")
132    }
133
134    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
135    where
136        E: serde::de::Error,
137    {
138        Ok(Self::Value::from(v as f32))
139    }
140
141    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
142    where
143        E: serde::de::Error,
144    {
145        Ok(Self::Value::from(v as f32))
146    }
147
148    fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
149    where
150        E: serde::de::Error,
151    {
152        Ok(Self::Value::from(v))
153    }
154
155    fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
156    where
157        E: serde::de::Error,
158    {
159        Ok(Self::Value::from(v as f32))
160    }
161}
162
163impl<'de> Deserialize<'de> for TypeEffectiveness {
164    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165    where
166        D: serde::Deserializer<'de>,
167    {
168        deserializer.deserialize_f32(TypeEffectivenessVisitor)
169    }
170}
171
172/// A type table, which contains type effectiveness information for types against some other value.
173///
174/// The key here is the attacking type.
175pub type TypeTable<T> = HashMap<Type, HashMap<T, TypeEffectiveness>>;
176
177/// A type chart, which contains all type effectiveness information for types against other types
178/// and effects.
179///
180/// The key here is the attacking type.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct TypeChart {
183    pub types: TypeTable<Type>,
184}
185
186impl TypeChart {
187    pub fn new() -> Self {
188        Self {
189            types: TypeTable::new(),
190        }
191    }
192
193    pub fn from_filled(types: TypeTable<Type>) -> Self {
194        Self { types }
195    }
196}
197
198#[cfg(test)]
199mod type_test {
200    use crate::{
201        Type,
202        test_util::{
203            test_string_deserialization,
204            test_string_serialization,
205        },
206    };
207
208    #[test]
209    fn serializes_to_string() {
210        test_string_serialization(Type::Grass, "Grass");
211        test_string_serialization(Type::Fire, "Fire");
212        test_string_serialization(Type::Water, "Water");
213    }
214
215    #[test]
216    fn deserializes_lowercase() {
217        test_string_deserialization("normal", Type::Normal);
218        test_string_deserialization("dragon", Type::Dragon);
219        test_string_deserialization("ghost", Type::Ghost);
220    }
221}
222
223#[cfg(test)]
224mod type_effectiveness_test {
225    use hashbrown::HashMap;
226
227    use crate::{
228        Type,
229        TypeChart,
230        TypeEffectiveness,
231        TypeTable,
232        test_util::test_deserialization,
233    };
234
235    #[test]
236    fn serializes_to_number() {
237        test_deserialization("0", TypeEffectiveness::None);
238        test_deserialization("0.5", TypeEffectiveness::Weak);
239        test_deserialization("1", TypeEffectiveness::Normal);
240        test_deserialization("2", TypeEffectiveness::Strong);
241    }
242
243    #[test]
244    fn deserializes_type_table() {
245        let str = r#"{
246            "Fire": {
247                "Fire": 0.5,
248                "Water": 0.5,
249                "Grass": 2,
250                "Ice": 2,
251                "Bug": 2,
252                "Rock": 0.5,
253                "Dragon": 0.5,
254                "Steel": 2
255            }
256        }"#;
257        let tc = serde_json::from_str::<TypeTable<Type>>(str).unwrap();
258        let expected = TypeTable::from_iter([(
259            Type::Fire,
260            HashMap::from_iter([
261                (Type::Fire, TypeEffectiveness::Weak),
262                (Type::Water, TypeEffectiveness::Weak),
263                (Type::Grass, TypeEffectiveness::Strong),
264                (Type::Ice, TypeEffectiveness::Strong),
265                (Type::Bug, TypeEffectiveness::Strong),
266                (Type::Rock, TypeEffectiveness::Weak),
267                (Type::Dragon, TypeEffectiveness::Weak),
268                (Type::Steel, TypeEffectiveness::Strong),
269            ]),
270        )]);
271        assert_eq!(tc, expected)
272    }
273
274    #[test]
275    fn deserializes_type_chart() {
276        let str = r#"{
277           "types": {
278                "Fire": {
279                    "Fire": 0.5,
280                    "Water": 0.5,
281                    "Grass": 2,
282                    "Ice": 2,
283                    "Bug": 2,
284                    "Rock": 0.5,
285                    "Dragon": 0.5,
286                    "Steel": 2
287                }
288            }
289        }"#;
290        let tc = serde_json::from_str::<TypeChart>(str).unwrap();
291        let expected = TypeChart::from_filled(TypeTable::from_iter([(
292            Type::Fire,
293            HashMap::from_iter([
294                (Type::Fire, TypeEffectiveness::Weak),
295                (Type::Water, TypeEffectiveness::Weak),
296                (Type::Grass, TypeEffectiveness::Strong),
297                (Type::Ice, TypeEffectiveness::Strong),
298                (Type::Bug, TypeEffectiveness::Strong),
299                (Type::Rock, TypeEffectiveness::Weak),
300                (Type::Dragon, TypeEffectiveness::Weak),
301                (Type::Steel, TypeEffectiveness::Strong),
302            ]),
303        )]));
304        assert_eq!(tc, expected)
305    }
306}