fg_notation/
abbreviated.rs

1use core::fmt;
2use std::str::FromStr;
3
4use crate::{numpad, CreationError};
5
6/// A move represented using [abbreviated notation](https://glossary.infil.net/?t=Notation)
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Move {
9    button: Button,
10    motion: Motion,
11    modifier: Modifier,
12}
13
14/// An abreviated notation button
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Button(String);
17
18/// An abreviated notation motion
19#[non_exhaustive]
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Motion {
22    N,
23    U,
24    D,
25    B,
26    F,
27    DB,
28    DF,
29    UB,
30    UF,
31    QCF,
32    QCB,
33    HCF,
34    HCB,
35    DP,
36    RDP,
37    FullCircle,
38    Double360,
39    Other(String),
40}
41
42// An abreviated notation modifier
43#[non_exhaustive]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum Modifier {
46    Close,
47    Far,
48    Standing,
49    Crouching,
50    Jump,
51    SuperJump,
52    JumpCancel,
53    TigerKnee,
54    None,
55}
56
57impl Move {
58    /// Create a single [`Move`] from `input` that can be represented
59    /// as a string
60    ///
61    /// Returns a [`CreationError`] if any component of the input is invalid
62    pub fn new<S>(input: S) -> Result<Self, CreationError>
63    where
64        S: ToString,
65    {
66        let mut input = input.to_string().trim().to_string();
67        let modifier = Self::get_modifier(&mut input)?;
68        let input = input.split_whitespace().collect::<Vec<&str>>();
69        let motion = if input.len() > 1 {
70            Motion::new(input[0])
71        } else {
72            Motion::N
73        };
74        let button = Button::new(input.last().unwrap())?;
75
76        Ok(Self {
77            button,
78            motion,
79            modifier,
80        })
81    }
82
83    fn get_modifier(input: &mut String) -> Result<Modifier, CreationError> {
84        if input.contains('.') {
85            let prefix = input.chars().take_while(|c| *c != '.').collect::<String>();
86            for _ in 0..prefix.len() {
87                (*input).remove(0);
88            }
89            (*input).remove(0);
90            Ok(Modifier::new(prefix)?)
91        } else {
92            Ok(Modifier::None)
93        }
94    }
95
96    pub fn button(&self) -> Button {
97        self.button.clone()
98    }
99
100    pub fn motion(&self) -> Motion {
101        self.motion.clone()
102    }
103
104    pub fn modifier(&self) -> Modifier {
105        self.modifier
106    }
107}
108
109impl Button {
110    /// Create a [`Button`] from something that can be represented
111    /// as a string
112    ///
113    /// Returns a [`CreationError`] if the input contains non ASCII
114    /// alphabetic characters
115    pub fn new<S>(b: S) -> Result<Self, CreationError>
116    where
117        S: ToString,
118    {
119        let b = b.to_string();
120        if !b.chars().all(|c| c.is_ascii_alphabetic()) {
121            Err(CreationError::InvalidButton)
122        } else {
123            Ok(Self(b))
124        }
125    }
126}
127
128impl Modifier {
129    /// Create a [`Modifier`] from something that can be represented as
130    /// a string
131    ///
132    /// Returns a [`CreationError`] if the provided prefix cannot be
133    /// matched to a valid modifier
134    pub fn new<S>(m: S) -> Result<Self, CreationError>
135    where
136        S: ToString,
137    {
138        let m = m.to_string();
139        match m.to_lowercase().as_str() {
140            "j." | "j" => Ok(Self::Jump),
141            "sj." | "sj" => Ok(Self::SuperJump),
142            "jc." | "jc" => Ok(Self::JumpCancel),
143            "cl." | "cl" => Ok(Self::Close),
144            "f." | "f" => Ok(Self::Far),
145            "tk." | "tk" => Ok(Self::TigerKnee),
146            "cr." | "cr" => Ok(Self::Crouching),
147            "st." | "st" => Ok(Self::Standing),
148            _ => Err(CreationError::InvalidModifier),
149        }
150    }
151}
152
153impl Motion {
154    /// Create a [`Motion`] from something that can be represented
155    /// as a string
156    pub fn new<S>(m: S) -> Self
157    where
158        S: ToString,
159    {
160        let m = m.to_string();
161        match m.to_lowercase().as_str() {
162            "n" => Self::N,
163            "u" => Self::U,
164            "d" => Self::D,
165            "b" => Self::B,
166            "f" => Self::F,
167            "ub" | "u/b" => Self::UB,
168            "uf" | "u/f" => Self::UF,
169            "db" | "d/b" => Self::DB,
170            "df" | "d/f" => Self::DF,
171            "qcf" => Self::QCF,
172            "qcb" => Self::QCB,
173            "hcf" => Self::HCF,
174            "hcb" => Self::HCB,
175            "360" => Self::FullCircle,
176            "720" => Self::Double360,
177            other => Self::Other(other.to_string()),
178        }
179    }
180}
181
182impl From<numpad::Move> for Move {
183    fn from(m: numpad::Move) -> Self {
184        let button = Button::from(m.button());
185        let m_motion = m.motion();
186        let motion = if m_motion.is_neutral() {
187            Motion::N
188        } else {
189            Motion::from(m_motion)
190        };
191        let modifier = Modifier::from(m.modifier());
192
193        Self {
194            button,
195            motion,
196            modifier,
197        }
198    }
199}
200
201impl FromStr for Move {
202    type Err = CreationError;
203
204    fn from_str(s: &str) -> Result<Self, Self::Err> {
205        Self::new(s)
206    }
207}
208
209impl fmt::Display for Move {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        write!(
212            f,
213            "{}{}{}{}",
214            self.modifier,
215            self.motion,
216            if self.motion != Motion::N { " " } else { "" },
217            self.button
218        )
219    }
220}
221
222impl From<numpad::Motion> for Motion {
223    fn from(m: numpad::Motion) -> Self {
224        match m.to_string().as_str() {
225            "5" | "" => Self::N,
226            "8" => Self::U,
227            "2" => Self::D,
228            "4" => Self::B,
229            "6" => Self::F,
230            "7" => Self::UB,
231            "9" => Self::UF,
232            "1" => Self::DB,
233            "3" => Self::DF,
234            "236" => Self::QCF,
235            "214" => Self::QCB,
236            "41236" => Self::HCF,
237            "63214" => Self::HCB,
238            "41236987" => Self::FullCircle,
239            "4123698741236987" => Self::Double360,
240            other => Self::Other(other.to_string()),
241        }
242    }
243}
244
245impl FromStr for Motion {
246    type Err = CreationError;
247
248    fn from_str(s: &str) -> Result<Self, Self::Err> {
249        Ok(Self::new(s))
250    }
251}
252
253impl fmt::Display for Motion {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        match self {
256            Motion::N => write!(f, ""),
257            Motion::U => write!(f, "U"),
258            Motion::D => write!(f, "D"),
259            Motion::B => write!(f, "B"),
260            Motion::F => write!(f, "F"),
261            Motion::DB => write!(f, "DB"),
262            Motion::DF => write!(f, "DF"),
263            Motion::UB => write!(f, "UB"),
264            Motion::UF => write!(f, "UF"),
265            Motion::QCF => write!(f, "QCF"),
266            Motion::QCB => write!(f, "QCB"),
267            Motion::HCF => write!(f, "HCF"),
268            Motion::HCB => write!(f, "HCB"),
269            Motion::DP => write!(f, "DP"),
270            Motion::RDP => write!(f, "RDP"),
271            Motion::FullCircle => write!(f, "360"),
272            Motion::Double360 => write!(f, "720"),
273            Motion::Other(o) => write!(f, "'{o}'"),
274        }
275    }
276}
277
278impl From<numpad::Modifier> for Modifier {
279    fn from(m: numpad::Modifier) -> Self {
280        match m {
281            numpad::Modifier::Jump => Self::Jump,
282            numpad::Modifier::SuperJump => Self::SuperJump,
283            numpad::Modifier::JumpCancel => Self::JumpCancel,
284            numpad::Modifier::Close => Self::Close,
285            numpad::Modifier::Far => Self::Far,
286            numpad::Modifier::TigerKnee => Self::TigerKnee,
287            numpad::Modifier::None => Self::None,
288        }
289    }
290}
291
292impl FromStr for Modifier {
293    type Err = CreationError;
294
295    fn from_str(s: &str) -> Result<Self, Self::Err> {
296        Self::new(s)
297    }
298}
299
300impl fmt::Display for Modifier {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        let prefix = match self {
303            Modifier::Close => "cl.",
304            Modifier::Far => "f.",
305            Modifier::Standing => "st.",
306            Modifier::Crouching => "cr.",
307            Modifier::Jump => "j.",
308            Modifier::SuperJump => "sj.",
309            Modifier::JumpCancel => "jc.",
310            Modifier::TigerKnee => "tk.",
311            Modifier::None => "",
312        };
313        write!(f, "{prefix}")
314    }
315}
316
317impl From<numpad::Button> for Button {
318    fn from(b: numpad::Button) -> Self {
319        Self(b.to_string())
320    }
321}
322
323impl FromStr for Button {
324    type Err = CreationError;
325
326    fn from_str(s: &str) -> Result<Self, Self::Err> {
327        Self::new(s)
328    }
329}
330
331impl fmt::Display for Button {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(f, "{}", self.0)
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use pretty_assertions::assert_eq;
341
342    #[test]
343    fn qcf_hp() {
344        let attack = "qcf HP";
345        let created = Move::new(attack).unwrap();
346
347        assert_eq!(
348            created,
349            Move {
350                button: Button("HP".to_string()),
351                motion: Motion::QCF,
352                modifier: Modifier::None
353            }
354        )
355    }
356
357    #[test]
358    fn cr_mk() {
359        let attack = "cr.mk";
360        let created = Move::new(attack).unwrap();
361
362        assert_eq!(
363            created,
364            Move {
365                button: Button("mk".to_string()),
366                motion: Motion::N,
367                modifier: Modifier::Crouching
368            },
369        )
370    }
371
372    #[test]
373    fn tk_qcf_hk() {
374        let attack = "tk.qcf HK";
375        let created = Move::new(attack).unwrap();
376
377        assert_eq!(
378            created,
379            Move {
380                button: Button("HK".to_string()),
381                motion: Motion::QCF,
382                modifier: Modifier::TigerKnee
383            }
384        )
385    }
386}