Skip to main content

battler_choice/
lib.rs

1#![no_std]
2#![feature(pattern)]
3
4extern crate alloc;
5
6use alloc::{
7    borrow::ToOwned,
8    collections::VecDeque,
9    format,
10    string::{
11        String,
12        ToString,
13    },
14    vec::Vec,
15};
16use core::{
17    fmt::Display,
18    str::{
19        FromStr,
20        pattern::Pattern,
21    },
22};
23
24use anyhow::{
25    Context,
26    Error,
27    Result,
28};
29use itertools::Itertools;
30use thiserror::Error;
31
32#[derive(Error, Debug)]
33#[error("invalid choice: {0}")]
34pub struct InvalidChoiceError(String);
35
36/// A choice to use a move.
37#[derive(Debug, Default, Clone, PartialEq, Eq)]
38pub struct MoveChoice {
39    /// The move slot to use.
40    pub slot: usize,
41    /// The target of the move.
42    pub target: Option<isize>,
43    /// Mega Evolve?
44    pub mega: bool,
45    /// Z-Move?
46    pub z_move: bool,
47    /// Ultra Burst?
48    pub ultra: bool,
49    /// Dynamax?
50    pub dyna: bool,
51    /// Terastallize?
52    pub tera: bool,
53    /// Force a random target to be selected?
54    pub random_target: bool,
55}
56
57impl Display for MoveChoice {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        write!(f, "{}", self.slot)?;
60        if let Some(target) = self.target {
61            write!(f, ",{target}")?;
62        }
63        if self.mega {
64            write!(f, ",mega")?;
65        }
66        if self.z_move {
67            write!(f, ",zmove")?;
68        }
69        if self.ultra {
70            write!(f, ",ultra")?;
71        }
72        if self.dyna {
73            write!(f, ",dyna")?;
74        }
75        if self.tera {
76            write!(f, ",tera")?;
77        }
78        if self.random_target {
79            write!(f, ",randomtarget")?;
80        }
81        Ok(())
82    }
83}
84
85impl FromStr for MoveChoice {
86    type Err = Error;
87    fn from_str(s: &str) -> Result<Self, Self::Err> {
88        let mut args = s
89            .split(',')
90            .map(|str| str.trim())
91            .collect::<VecDeque<&str>>();
92        let slot = args
93            .pop_front()
94            .context("missing move slot")?
95            .parse()
96            .context("invalid move slot")?;
97        let mut choice = Self {
98            slot,
99            target: None,
100            mega: false,
101            z_move: false,
102            ultra: false,
103            dyna: false,
104            tera: false,
105            random_target: false,
106        };
107
108        if let Some(target) = args
109            .front()
110            .map(|target| target.parse::<isize>().ok())
111            .flatten()
112        {
113            choice.target = Some(target);
114            args.pop_front();
115        }
116
117        while let Some(arg) = args.pop_front() {
118            match arg {
119                "mega" => {
120                    choice.mega = true;
121                }
122                "zmove" => {
123                    choice.z_move = true;
124                }
125                "ultra" => {
126                    choice.ultra = true;
127                }
128                "dyna" => {
129                    choice.dyna = true;
130                }
131                "tera" => {
132                    choice.tera = true;
133                }
134                "randomtarget" => {
135                    choice.random_target = true;
136                }
137                _ => {
138                    return Err(Error::msg(format!("invalid option in move choice: {arg}")));
139                }
140            }
141        }
142
143        Ok(choice)
144    }
145}
146
147/// A choice to use an item.
148#[derive(Debug, Default, Clone, PartialEq, Eq)]
149pub struct ItemChoice {
150    /// The item to use.
151    pub item: String,
152    /// The target of the item.
153    pub target: Option<isize>,
154    /// Any additional input.
155    ///
156    /// For example, when using a PP-healing move, the target move must be specified.
157    pub additional_input: VecDeque<String>,
158}
159
160impl Display for ItemChoice {
161    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162        write!(f, "{}", self.item)?;
163        if let Some(target) = self.target {
164            write!(f, ",{target}")?;
165        }
166        for val in &self.additional_input {
167            write!(f, ",{val}")?;
168        }
169        Ok(())
170    }
171}
172
173impl FromStr for ItemChoice {
174    type Err = Error;
175    fn from_str(s: &str) -> Result<Self, Self::Err> {
176        let mut args = s
177            .split(',')
178            .map(|str| str.trim())
179            .collect::<VecDeque<&str>>();
180        let item = args.pop_front().context("missing item")?.to_string();
181        let target = args.pop_front().map(|target| target.parse().ok()).flatten();
182        let additional_input = args.into_iter().map(|arg| arg.to_owned()).collect();
183        Ok(Self {
184            item,
185            target,
186            additional_input,
187        })
188    }
189}
190
191/// A team selection choice.
192#[derive(Debug, Default, Clone, PartialEq, Eq)]
193pub struct TeamSelectionChoice {
194    /// The Mons to select for the team, in order.
195    pub mons: Vec<usize>,
196}
197
198impl Display for TeamSelectionChoice {
199    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200        for (i, mon) in self.mons.iter().enumerate() {
201            if i > 0 {
202                write!(f, " ")?;
203            }
204            write!(f, "{mon}")?;
205        }
206        Ok(())
207    }
208}
209
210impl FromStr for TeamSelectionChoice {
211    type Err = Error;
212
213    fn from_str(s: &str) -> Result<Self, Self::Err> {
214        if s.is_empty() {
215            return Ok(Self {
216                mons: Vec::default(),
217            });
218        }
219        let mons = s
220            .split(" ")
221            .map(|str| str.trim())
222            .map(|str| str.parse::<usize>())
223            .collect::<Result<_, _>>()?;
224        Ok(Self { mons })
225    }
226}
227
228/// A choice to switch in.
229#[derive(Debug, Default, Clone, PartialEq, Eq)]
230pub struct SwitchChoice {
231    /// The Mon to switch in.
232    ///
233    /// If not specified, a random Mon will be selected.
234    pub mon: Option<usize>,
235}
236
237impl Display for SwitchChoice {
238    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
239        if let Some(mon) = self.mon {
240            write!(f, "{}", mon)?;
241        }
242        Ok(())
243    }
244}
245
246impl FromStr for SwitchChoice {
247    type Err = <usize as FromStr>::Err;
248    fn from_str(s: &str) -> Result<Self, Self::Err> {
249        let mon = (!s.is_empty()).then(|| s.parse()).transpose()?;
250        Ok(Self { mon })
251    }
252}
253
254/// A choice to learn a move.
255#[derive(Debug, Default, Clone, PartialEq, Eq)]
256pub struct LearnMoveChoice {
257    /// The index of the move slot to forget.
258    pub forget_move_slot: usize,
259}
260
261impl Display for LearnMoveChoice {
262    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
263        write!(f, "{}", self.forget_move_slot)
264    }
265}
266
267impl FromStr for LearnMoveChoice {
268    type Err = <usize as FromStr>::Err;
269    fn from_str(s: &str) -> Result<Self, Self::Err> {
270        let forget_move_slot = s.parse()?;
271        Ok(Self { forget_move_slot })
272    }
273}
274
275/// A choice, which controls how a player responds to a request in a battle.
276#[derive(Debug, Default, Clone, PartialEq, Eq)]
277pub enum Choice {
278    /// Do nothing.
279    #[default]
280    Pass,
281    /// Make a random choice, depending on the type of request.
282    Random,
283    /// Make as many random choices as required.
284    RandomAll,
285    /// Attempt to escape from the battle.
286    Escape,
287    /// Forfeit the battle.
288    Forfeit,
289    /// Shift to the center (Triples battle only).
290    Shift,
291    /// Select team during team preview.
292    Team(TeamSelectionChoice),
293    /// Switch a Mon in.
294    Switch(SwitchChoice),
295    /// Use a move.
296    Move(MoveChoice),
297    /// Use an item.
298    Item(ItemChoice),
299    /// Learn a move.
300    LearnMove(LearnMoveChoice),
301}
302
303impl Display for Choice {
304    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
305        match self {
306            Self::Pass => write!(f, "pass"),
307            Self::Random => write!(f, "random"),
308            Self::RandomAll => write!(f, "randomall"),
309            Self::Escape => write!(f, "escape"),
310            Self::Forfeit => write!(f, "forfeit"),
311            Self::Shift => write!(f, "shift"),
312            Self::Team(choice) => {
313                write!(f, "team {choice}")
314            }
315            Self::Switch(choice) => write!(f, "switch {choice}"),
316            Self::Move(choice) => {
317                write!(f, "move {choice}")
318            }
319            Self::Item(choice) => {
320                write!(f, "item {choice}")
321            }
322            Self::LearnMove(choice) => write!(f, "learnmove {choice}"),
323        }
324    }
325}
326
327fn split_once_optional<'a, P>(input: &'a str, delimiter: P) -> (&'a str, Option<&'a str>)
328where
329    P: Pattern,
330{
331    match input.split_once(delimiter) {
332        None => (input, None),
333        Some((a, b)) => (a, Some(b)),
334    }
335}
336
337impl FromStr for Choice {
338    type Err = Error;
339    fn from_str(s: &str) -> Result<Self, Self::Err> {
340        let (choice, data) = split_once_optional(s, " ");
341        let data = data.unwrap_or_default().trim();
342        match choice {
343            "pass" => Ok(Self::Pass),
344            "random" => Ok(Self::Random),
345            "randomall" => Ok(Self::RandomAll),
346            "escape" => Ok(Self::Escape),
347            "forfeit" => Ok(Self::Forfeit),
348            "shift" => Ok(Self::Shift),
349            "team" => Ok(Self::Team(TeamSelectionChoice::from_str(data)?)),
350            "switch" => Ok(Self::Switch(SwitchChoice::from_str(data)?)),
351            "move" => Ok(Self::Move(MoveChoice::from_str(data)?)),
352            "item" => Ok(Self::Item(ItemChoice::from_str(data)?)),
353            "learnmove" => Ok(Self::LearnMove(LearnMoveChoice::from_str(data)?)),
354            _ => Err(Error::new(InvalidChoiceError(choice.to_string()))),
355        }
356    }
357}
358
359/// Serializes multiple [`Choice`]s to a string.
360pub fn choices_to_string<I>(choices: I) -> String
361where
362    I: IntoIterator<Item = Choice>,
363{
364    choices
365        .into_iter()
366        .map(|choice| choice.to_string())
367        .join(";")
368}
369
370/// Deserializes multiple [`Choice`]s from a string.
371pub fn choices_from_string<S>(choices: S) -> Result<Vec<Choice>>
372where
373    S: AsRef<str>,
374{
375    choices
376        .as_ref()
377        .split(";")
378        .map(|str| str.trim())
379        .map(|str| Choice::from_str(str))
380        .collect()
381}
382
383/// Deserializes multiple [`Choice`]s from a string, returning the result of parsing for each
384/// choice.
385pub fn choice_results_from_string<S>(choices: S) -> Vec<Result<Choice>>
386where
387    S: AsRef<str>,
388{
389    choices
390        .as_ref()
391        .split(";")
392        .map(|str| str.trim())
393        .map(|str| Choice::from_str(str))
394        .collect()
395}
396
397#[cfg(test)]
398mod battler_choice_test {
399    use alloc::{
400        borrow::ToOwned,
401        collections::VecDeque,
402        string::ToString,
403        vec::Vec,
404    };
405    use core::str::FromStr;
406
407    use crate::{
408        Choice,
409        ItemChoice,
410        LearnMoveChoice,
411        MoveChoice,
412        SwitchChoice,
413        TeamSelectionChoice,
414        choice_results_from_string,
415        choices_from_string,
416        choices_to_string,
417    };
418
419    #[test]
420    fn serializes_to_string() {
421        assert_eq!(Choice::Pass.to_string(), "pass");
422        assert_eq!(Choice::Random.to_string(), "random");
423        assert_eq!(Choice::RandomAll.to_string(), "randomall");
424        assert_eq!(Choice::Escape.to_string(), "escape");
425        assert_eq!(Choice::Forfeit.to_string(), "forfeit");
426        assert_eq!(Choice::Shift.to_string(), "shift");
427        assert_eq!(
428            Choice::Team(TeamSelectionChoice {
429                mons: Vec::from_iter([0, 2, 4]),
430            })
431            .to_string(),
432            "team 0 2 4"
433        );
434        assert_eq!(
435            Choice::Switch(SwitchChoice { mon: Some(1) }).to_string(),
436            "switch 1"
437        );
438        assert_eq!(
439            Choice::Switch(SwitchChoice { mon: None }).to_string(),
440            "switch "
441        );
442        assert_eq!(
443            Choice::Move(MoveChoice {
444                slot: 0,
445                target: None,
446                mega: false,
447                z_move: false,
448                ultra: false,
449                dyna: false,
450                tera: false,
451                random_target: false,
452            })
453            .to_string(),
454            "move 0"
455        );
456        assert_eq!(
457            Choice::Move(MoveChoice {
458                slot: 1,
459                target: Some(-1),
460                mega: false,
461                z_move: false,
462                ultra: false,
463                dyna: false,
464                tera: false,
465                random_target: false,
466            })
467            .to_string(),
468            "move 1,-1"
469        );
470        assert_eq!(
471            Choice::Move(MoveChoice {
472                slot: 2,
473                target: Some(2),
474                mega: true,
475                z_move: false,
476                ultra: false,
477                dyna: false,
478                tera: false,
479                random_target: false,
480            })
481            .to_string(),
482            "move 2,2,mega"
483        );
484        assert_eq!(
485            Choice::Move(MoveChoice {
486                slot: 3,
487                target: None,
488                mega: true,
489                z_move: true,
490                ultra: true,
491                dyna: true,
492                tera: true,
493                random_target: false,
494            })
495            .to_string(),
496            "move 3,mega,zmove,ultra,dyna,tera"
497        );
498        assert_eq!(
499            Choice::Item(ItemChoice {
500                item: "ball".to_owned(),
501                target: None,
502                additional_input: VecDeque::default(),
503            })
504            .to_string(),
505            "item ball"
506        );
507        assert_eq!(
508            Choice::Item(ItemChoice {
509                item: "potion".to_owned(),
510                target: Some(-1),
511                additional_input: VecDeque::from_iter(["abc".to_owned(), "def".to_owned()]),
512            })
513            .to_string(),
514            "item potion,-1,abc,def"
515        );
516        assert_eq!(
517            Choice::LearnMove(LearnMoveChoice {
518                forget_move_slot: 5
519            })
520            .to_string(),
521            "learnmove 5"
522        );
523    }
524
525    #[test]
526    fn deserializes_from_string() {
527        assert_matches::assert_matches!(Choice::from_str("pass"), Ok(Choice::Pass));
528        assert_matches::assert_matches!(Choice::from_str("random"), Ok(Choice::Random));
529        assert_matches::assert_matches!(Choice::from_str("randomall"), Ok(Choice::RandomAll));
530        assert_matches::assert_matches!(Choice::from_str("escape"), Ok(Choice::Escape));
531        assert_matches::assert_matches!(Choice::from_str("forfeit"), Ok(Choice::Forfeit));
532        assert_matches::assert_matches!(Choice::from_str("shift"), Ok(Choice::Shift));
533        assert_matches::assert_matches!(
534            Choice::from_str("team 0 2 4"),
535            Ok(Choice::Team(choice)) => {
536                assert_eq!(choice, TeamSelectionChoice {
537                    mons: Vec::from_iter([0, 2, 4]),
538                });
539            }
540        );
541        assert_matches::assert_matches!(
542            Choice::from_str("switch 1"),
543            Ok(Choice::Switch(choice)) => {
544                assert_eq!(choice, SwitchChoice {
545                    mon: Some(1),
546                });
547            }
548        );
549        assert_matches::assert_matches!(
550            Choice::from_str("switch"),
551            Ok(Choice::Switch(choice)) => {
552                assert_eq!(choice, SwitchChoice {
553                    mon: None,
554                });
555            }
556        );
557        assert_matches::assert_matches!(
558            Choice::from_str("move 0"),
559            Ok(Choice::Move(choice)) => {
560                assert_eq!(choice, MoveChoice {
561                    slot: 0,
562                    target: None,
563                    mega: false,
564                    z_move: false,
565                    ultra: false,
566                    dyna: false,
567                    tera: false,
568                    random_target: false,
569                });
570            }
571        );
572        assert_matches::assert_matches!(
573            Choice::from_str("move 1,-1"),
574            Ok(Choice::Move(choice)) => {
575                assert_eq!(choice, MoveChoice {
576                    slot: 1,
577                    target: Some(-1),
578                    mega: false,
579                    z_move: false,
580                    ultra: false,
581                    dyna: false,
582                    tera: false,
583                    random_target: false,
584                });
585            }
586        );
587        assert_matches::assert_matches!(
588            Choice::from_str("move 2,2,mega"),
589            Ok(Choice::Move(choice)) => {
590                assert_eq!(choice, MoveChoice {
591                    slot: 2,
592                    target: Some(2),
593                    mega: true,
594                    z_move: false,
595                    ultra: false,
596                    dyna: false,
597                    tera: false,
598                    random_target: false,
599                });
600            }
601        );
602        assert_matches::assert_matches!(
603            Choice::from_str("move 3,mega,zmove,ultra,dyna,tera"),
604            Ok(Choice::Move(choice)) => {
605                assert_eq!(choice, MoveChoice {
606                    slot: 3,
607                    target: None,
608                    mega: true,
609                    z_move: true,
610                    ultra: true,
611                    dyna: true,
612                    tera: true,
613                    random_target: false,
614                });
615            }
616        );
617        assert_matches::assert_matches!(
618            Choice::from_str("item ball"),
619            Ok(Choice::Item(choice)) => {
620                assert_eq!(choice, ItemChoice {
621                    item: "ball".to_owned(),
622                    target: None,
623                    additional_input: VecDeque::default(),
624                });
625            }
626        );
627        assert_matches::assert_matches!(
628            Choice::from_str("item potion,-1,abc,def"),
629            Ok(Choice::Item(choice)) => {
630                assert_eq!(choice, ItemChoice {
631                    item: "potion".to_owned(),
632                    target: Some(-1),
633                    additional_input: VecDeque::from_iter(["abc".to_owned(), "def".to_owned()]),
634                });
635            }
636        );
637        assert_matches::assert_matches!(
638            Choice::from_str("learnmove 5"),
639            Ok(Choice::LearnMove(choice)) => {
640                assert_eq!(choice, LearnMoveChoice {
641                    forget_move_slot: 5,
642                });
643            }
644        );
645    }
646
647    #[test]
648    fn serializes_multiple_to_string() {
649        assert_eq!(
650            choices_to_string([Choice::Move(MoveChoice {
651                slot: 1,
652                target: Some(2),
653                ..Default::default()
654            })]),
655            "move 1,2"
656        );
657        assert_eq!(
658            choices_to_string([
659                Choice::Move(MoveChoice {
660                    slot: 1,
661                    target: Some(2),
662                    ..Default::default()
663                }),
664                Choice::Switch(SwitchChoice { mon: Some(3) }),
665                Choice::Forfeit,
666            ]),
667            "move 1,2;switch 3;forfeit"
668        );
669    }
670
671    #[test]
672    fn deserializes_multiple_from_string() {
673        assert_matches::assert_matches!(choices_from_string("move 1,2"), Ok(choices) => {
674            pretty_assertions::assert_eq!(choices, Vec::from_iter([Choice::Move(MoveChoice {
675                slot: 1,
676                target: Some(2),
677                ..Default::default()
678            })]));
679        });
680        assert_matches::assert_matches!(choices_from_string("move 1,2;switch 3;forfeit"), Ok(choices) => {
681            pretty_assertions::assert_eq!(choices, Vec::from_iter([
682                Choice::Move(MoveChoice {
683                    slot: 1,
684                    target: Some(2),
685                    ..Default::default()
686                }),
687                Choice::Switch(SwitchChoice { mon: Some(3) }),
688                Choice::Forfeit,
689            ]));
690        });
691    }
692
693    #[test]
694    fn deserializes_multiple_results_from_string() {
695        let choices = choice_results_from_string("move 1,2;switch abc");
696        assert_eq!(choices.len(), 2);
697        assert_matches::assert_matches!(&choices[0], Ok(choice) => {
698            pretty_assertions::assert_eq!(choice, &Choice::Move(MoveChoice {
699                slot: 1,
700                target: Some(2),
701                ..Default::default()
702            }));
703        });
704        assert_matches::assert_matches!(&choices[1], Err(_));
705    }
706}