1use core::fmt;
2use std::str::FromStr;
3
4use crate::{abbreviated, CreationError};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Move {
9 modifier: Modifier,
10 motion: Motion,
11 button: Button,
12}
13
14#[derive(Debug, Clone, Eq)]
16pub struct Motion(String);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Button(String);
21
22#[non_exhaustive]
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Modifier {
26 Jump,
27 SuperJump,
28 JumpCancel,
29 Close,
30 Far,
31 TigerKnee,
32 None,
33}
34
35impl Move {
36 pub fn new<S>(input: S) -> Result<Self, CreationError>
42 where
43 S: ToString,
44 {
45 let mut input = input.to_string().trim().to_string();
46 let modifier = Self::get_modifier(&mut input)?;
47 let motion = Motion::new(
48 input
49 .chars()
50 .take_while(|c| !c.is_ascii_alphabetic())
51 .collect::<String>(),
52 )?;
53 let button = Button::new(
54 input
55 .chars()
56 .skip_while(|c| !c.is_ascii_alphabetic())
57 .collect::<String>(),
58 )?;
59
60 Ok(Self {
61 modifier,
62 motion,
63 button,
64 })
65 }
66
67 pub fn button(&self) -> Button {
68 self.button.clone()
69 }
70
71 pub fn motion(&self) -> Motion {
72 self.motion.clone()
73 }
74
75 pub fn modifier(&self) -> Modifier {
76 self.modifier
77 }
78
79 fn get_modifier(input: &mut String) -> Result<Modifier, CreationError> {
80 if input.contains('.') {
81 let prefix = input.chars().take_while(|c| *c != '.').collect::<String>();
82 for _ in 0..prefix.len() {
83 (*input).remove(0); }
85 (*input).remove(0); Ok(Modifier::new(prefix)?)
87 } else {
88 Ok(Modifier::None)
89 }
90 }
91}
92
93impl Modifier {
94 pub fn new<S>(m: S) -> Result<Self, CreationError>
100 where
101 S: ToString,
102 {
103 let m = m.to_string();
104
105 match m.to_lowercase().as_str() {
106 "j." | "j" => Ok(Self::Jump),
107 "sj." | "sj" => Ok(Self::SuperJump),
108 "jc." | "jc" => Ok(Self::JumpCancel),
109 "c." | "c" => Ok(Self::Close),
110 "f." | "f" => Ok(Self::Far),
111 "tk." | "tk" => Ok(Self::TigerKnee),
112 _ => Err(CreationError::InvalidModifier),
113 }
114 }
115}
116
117impl Button {
118 pub fn new<S>(b: S) -> Result<Self, CreationError>
121 where
122 S: ToString,
123 {
124 let b = b.to_string();
125 if !b.chars().all(|c| c.is_ascii_alphabetic()) {
126 Err(CreationError::InvalidButton)
127 } else {
128 Ok(Self(b))
129 }
130 }
131}
132
133impl Motion {
134 #![allow(clippy::len_without_is_empty)] pub fn new<S>(m: S) -> Result<Self, CreationError>
142 where
143 S: ToString,
144 {
145 let m = m.to_string();
146
147 if !m
148 .chars()
149 .all(|c| c.is_ascii_digit() || (c == '[' || c == ']'))
150 {
151 Err(CreationError::InvalidMotion)
152 } else {
153 Ok(Self(m))
154 }
155 }
156
157 pub fn len(&self) -> usize {
158 self.0.len()
159 }
160
161 #[must_use]
162 pub fn is_neutral(&self) -> bool {
163 self.0 == "5"
164 }
165}
166
167impl From<abbreviated::Move> for Move {
168 fn from(m: abbreviated::Move) -> Self {
169 let button = Button::from(m.button());
170 let a_mod = m.modifier();
171 let motion = if a_mod == abbreviated::Modifier::Standing {
172 Motion::new("5").unwrap()
173 } else if a_mod == abbreviated::Modifier::Crouching {
174 Motion::new("2").unwrap()
175 } else {
176 Motion::from(m.motion())
177 };
178 let modifier = Modifier::from(a_mod);
179
180 Self {
181 button,
182 motion,
183 modifier,
184 }
185 }
186}
187
188impl fmt::Display for Move {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 write!(f, "{}{}{}", self.modifier, self.motion.0, self.button.0)
191 }
192}
193
194impl FromStr for Move {
195 type Err = CreationError;
196
197 fn from_str(s: &str) -> Result<Self, Self::Err> {
198 Self::new(s)
199 }
200}
201
202impl From<abbreviated::Button> for Button {
203 fn from(b: abbreviated::Button) -> Self {
204 Button(b.to_string())
205 }
206}
207
208impl FromStr for Button {
209 type Err = CreationError;
210
211 fn from_str(s: &str) -> Result<Self, Self::Err> {
212 Self::new(s)
213 }
214}
215
216impl fmt::Display for Button {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}", self.0)
219 }
220}
221
222impl From<abbreviated::Motion> for Motion {
223 fn from(m: abbreviated::Motion) -> Self {
224 match m {
225 abbreviated::Motion::N => Self("5".to_string()),
226 abbreviated::Motion::U => Self("8".to_string()),
227 abbreviated::Motion::D => Self("2".to_string()),
228 abbreviated::Motion::B => Self("4".to_string()),
229 abbreviated::Motion::F => Self("6".to_string()),
230 abbreviated::Motion::DB => Self("1".to_string()),
231 abbreviated::Motion::DF => Self("3".to_string()),
232 abbreviated::Motion::UB => Self("7".to_string()),
233 abbreviated::Motion::UF => Self("9".to_string()),
234 abbreviated::Motion::QCF => Self("236".to_string()),
235 abbreviated::Motion::QCB => Self("214".to_string()),
236 abbreviated::Motion::HCF => Self("41236".to_string()),
237 abbreviated::Motion::HCB => Self("63214".to_string()),
238 abbreviated::Motion::DP => Self("623".to_string()),
239 abbreviated::Motion::RDP => Self("421".to_string()),
240 abbreviated::Motion::FullCircle => Self("41236987".to_string()),
241 abbreviated::Motion::Double360 => Self("4123698741236987".to_string()),
242 abbreviated::Motion::Other(o) => Self::new(o).unwrap(),
243 }
244 }
245}
246
247impl FromStr for Motion {
248 type Err = CreationError;
249
250 fn from_str(s: &str) -> Result<Self, Self::Err> {
251 Self::new(s)
252 }
253}
254
255impl fmt::Display for Motion {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{}", self.0)
258 }
259}
260
261impl PartialEq for Motion {
262 fn eq(&self, other: &Self) -> bool {
263 (self.0.is_empty() && other.0 == "5")
264 || (self.0 == "5" && other.0.is_empty())
265 || self.0 == other.0
266 }
267}
268
269impl From<abbreviated::Modifier> for Modifier {
270 fn from(m: abbreviated::Modifier) -> Self {
271 match m {
272 abbreviated::Modifier::Close => Self::Close,
273 abbreviated::Modifier::Far => Self::Far,
274 abbreviated::Modifier::Standing => Self::None,
275 abbreviated::Modifier::Crouching => Self::None,
276 abbreviated::Modifier::Jump => Self::Jump,
277 abbreviated::Modifier::SuperJump => Self::SuperJump,
278 abbreviated::Modifier::JumpCancel => Self::JumpCancel,
279 abbreviated::Modifier::TigerKnee => Self::TigerKnee,
280 abbreviated::Modifier::None => Self::None,
281 }
282 }
283}
284
285impl fmt::Display for Modifier {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 let prefix = match self {
288 Modifier::Jump => "j.",
289 Modifier::SuperJump => "sj.",
290 Modifier::JumpCancel => "jc.",
291 Modifier::Close => "c.",
292 Modifier::Far => "f.",
293 Modifier::TigerKnee => "tk.",
294 Modifier::None => "",
295 };
296 write!(f, "{prefix}")
297 }
298}
299
300impl FromStr for Modifier {
301 type Err = CreationError;
302
303 fn from_str(s: &str) -> Result<Self, Self::Err> {
304 Modifier::new(s)
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use pretty_assertions::assert_eq;
312
313 #[test]
314 fn j236h() {
315 let attack = "j.236H";
316 let created = Move::new(attack).unwrap();
317
318 assert_eq!(
319 created,
320 Move {
321 modifier: Modifier::Jump,
322 motion: Motion("236".to_string()),
323 button: Button("H".to_string())
324 }
325 )
326 }
327
328 #[test]
329 fn heavy_dp() {
330 let attack = "623Hp";
331 let created = Move::new(attack).unwrap();
332
333 assert_eq!(
334 created,
335 Move {
336 modifier: Modifier::None,
337 motion: Motion("623".to_string()),
338 button: Button("Hp".to_string())
339 }
340 )
341 }
342
343 #[test]
344 fn jl() {
345 let attack = "j.L";
346 let created = Move::new(attack).unwrap();
347
348 assert_eq!(
349 created,
350 Move {
351 modifier: Modifier::Jump,
352 motion: Motion("5".to_string()),
353 button: Button("L".to_string())
354 }
355 )
356 }
357
358 #[test]
359 fn charge_move() {
360 let attack = "[4]6A";
361 let created = Move::new(attack).unwrap();
362
363 assert_eq!(
364 created,
365 Move {
366 modifier: Modifier::None,
367 motion: Motion("[4]6".to_string()),
368 button: Button("A".to_string())
369 }
370 )
371 }
372
373 #[test]
374 fn cs() {
375 let attack = "c.S";
376 let created = Move::new(attack).unwrap();
377
378 assert_eq!(
379 created,
380 Move {
381 modifier: Modifier::Close,
382 motion: Motion("5".to_string()),
383 button: Button("S".to_string())
384 }
385 )
386 }
387
388 #[test]
389 fn superjump() {
390 let attack = "sj.236S";
391 let created = Move::new(attack).unwrap();
392
393 assert_eq!(
394 created,
395 Move {
396 modifier: Modifier::SuperJump,
397 motion: Motion("236".to_string()),
398 button: Button("S".to_string())
399 }
400 )
401 }
402
403 #[test]
404 fn move_tostring() {
405 let m = Move::new("214L").unwrap();
406 assert_eq!(m.to_string(), "214L".to_string());
407 }
408
409 #[test]
410 fn button_creation() {
411 let button = "HS";
412 let created = Button::new(button).unwrap();
413
414 assert_eq!(created, Button("HS".to_string()));
415 }
416
417 #[test]
418 #[should_panic]
419 fn invalid_button_fails() {
420 let invalid = "69lol";
421
422 Button::new(invalid).unwrap();
423 }
424
425 #[test]
426 fn motion_creation() {
427 let motion = "236";
428 let created = Motion::new(motion).unwrap();
429
430 assert_eq!(created, Motion("236".to_string()));
431 }
432
433 #[test]
434 #[should_panic]
435 fn invalid_motion_fails() {
436 let invalid = "balls22";
437
438 Motion::new(invalid).unwrap();
439 }
440
441 #[test]
442 fn no_motion() {
443 let m = Motion::new("").unwrap();
444
445 println!("{m}")
446 }
447}