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#[derive(Debug, Default, Clone, PartialEq, Eq)]
38pub struct MoveChoice {
39 pub slot: usize,
41 pub target: Option<isize>,
43 pub mega: bool,
45 pub z_move: bool,
47 pub ultra: bool,
49 pub dyna: bool,
51 pub tera: bool,
53 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
149pub struct ItemChoice {
150 pub item: String,
152 pub target: Option<isize>,
154 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
193pub struct TeamSelectionChoice {
194 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
230pub struct SwitchChoice {
231 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
256pub struct LearnMoveChoice {
257 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#[derive(Debug, Default, Clone, PartialEq, Eq)]
277pub enum Choice {
278 #[default]
280 Pass,
281 Random,
283 RandomAll,
285 Escape,
287 Forfeit,
289 Shift,
291 Team(TeamSelectionChoice),
293 Switch(SwitchChoice),
295 Move(MoveChoice),
297 Item(ItemChoice),
299 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
359pub 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
370pub 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
383pub 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}