Skip to main content

battler_data/clauses/
rules.rs

1use alloc::{
2    borrow::ToOwned,
3    string::String,
4};
5use core::{
6    fmt,
7    fmt::Display,
8    hash,
9    hash::Hash,
10    str::FromStr,
11};
12
13use anyhow::{
14    Error,
15    Result,
16};
17use hashbrown::HashSet;
18use serde_string_enum::{
19    DeserializeStringEnum,
20    SerializeStringEnum,
21};
22
23use crate::Id;
24
25/// A single rule that must be validated before a battle starts.
26#[derive(Debug, Clone, Eq, SerializeStringEnum, DeserializeStringEnum)]
27pub enum Rule {
28    /// Bans something, such as a Mon, item, move, or ability. Serialized as `- ID`.
29    Ban(Id),
30    /// Unbans something, such as a Mon, item, move, or ability. Serialized as `+ ID`.
31    ///
32    /// An unban is used to override a ban rule that is typically more general. For example, `-
33    /// Legendary, + Giratina` would allow the Mon `Giratina` to be used, even though it is a
34    /// legendary.
35    Unban(Id),
36    /// Some other rule attached to a value. Serialized as `name = value`.
37    ///
38    /// If `value` is empty, then the rule is simply serialized as `name`.
39    Value { name: Id, value: String },
40    /// Repeals a previously established rule. Serialized as `! name`.
41    ///
42    /// Compound and single rules can be repealed. Bans and unbans cannot be repealed.
43    Repeal(Id),
44}
45
46impl Rule {
47    /// Constructs a new named rule without a value.
48    pub fn value_name(name: &str) -> Rule {
49        Rule::Value {
50            name: Id::from(name),
51            value: String::new(),
52        }
53    }
54
55    /// Constructs a new named rule without a value, directly from an ID.
56    pub fn value_id(name: Id) -> Rule {
57        Rule::Value {
58            name: name,
59            value: String::new(),
60        }
61    }
62}
63
64impl Display for Rule {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::Ban(id) => write!(f, "-{id}"),
68            Self::Unban(id) => write!(f, "+{id}"),
69            Self::Value { name, value } => {
70                if value.is_empty() {
71                    write!(f, "{name}")
72                } else {
73                    write!(f, "{name}={value}")
74                }
75            }
76            Self::Repeal(id) => write!(f, "!{id}"),
77        }
78    }
79}
80
81impl FromStr for Rule {
82    type Err = Error;
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        match &s[0..1] {
85            "-" => Ok(Self::Ban(Id::from(s[1..].trim()))),
86            "+" => Ok(Self::Unban(Id::from(s[1..].trim()))),
87            "!" => Ok(Self::Repeal(Id::from(s[1..].trim()))),
88            _ => match s.split_once('=') {
89                None => Ok(Self::Value {
90                    name: Id::from(s.trim()),
91                    value: "".to_owned(),
92                }),
93                Some((name, value)) => Ok(Self::Value {
94                    name: Id::from(name.trim()),
95                    value: value.trim().to_owned(),
96                }),
97            },
98        }
99    }
100}
101
102impl Hash for Rule {
103    fn hash<H: hash::Hasher>(&self, state: &mut H) {
104        match self {
105            Self::Ban(id) => id.hash(state),
106            Self::Unban(id) => id.hash(state),
107            Self::Value { name, .. } => name.hash(state),
108            Self::Repeal(id) => id.hash(state),
109        }
110    }
111}
112
113impl PartialEq for Rule {
114    fn eq(&self, other: &Self) -> bool {
115        match self {
116            Self::Ban(id) => match other {
117                Self::Ban(other) => id.eq(other),
118                _ => false,
119            },
120            Self::Unban(id) => match other {
121                Self::Unban(other) => id.eq(other),
122                _ => false,
123            },
124            Self::Value { name, value: _ } => match other {
125                Self::Value {
126                    name: other,
127                    value: _,
128                } => name.eq(other),
129                _ => false,
130            },
131            Self::Repeal(id) => match other {
132                Self::Repeal(other) => id.eq(other),
133                _ => false,
134            },
135        }
136    }
137}
138
139/// A user-defined set of rules.
140pub type SerializedRuleSet = HashSet<Rule>;
141
142#[cfg(test)]
143mod rule_test {
144    use alloc::borrow::ToOwned;
145
146    use crate::{
147        Id,
148        Rule,
149        test_util::test_string_serialization,
150    };
151
152    #[test]
153    fn serializes_to_string() {
154        test_string_serialization(Rule::Ban(Id::from("bulbasaur")), "-bulbasaur");
155        test_string_serialization(Rule::Ban(Id::from("Giratina (Origin)")), "-giratinaorigin");
156        test_string_serialization(Rule::Unban(Id::from("Porygon-Z")), "+porygonz");
157        test_string_serialization(Rule::Repeal(Id::from("Evasion Clause")), "!evasionclause");
158        test_string_serialization(
159            Rule::Value {
160                name: Id::from("Max Level"),
161                value: "50".to_owned(),
162            },
163            "maxlevel=50",
164        );
165    }
166}