poke_engine/
io.rs

1use crate::choices::{Choice, Choices, MoveCategory, MOVES};
2use crate::evaluate::evaluate;
3use crate::generate_instructions::{
4    calculate_both_damage_rolls, generate_instructions_from_move_pair,
5};
6use crate::instruction::{Instruction, StateInstructions};
7use crate::mcts::{perform_mcts, MctsResult};
8use crate::search::{expectiminimax_search, iterative_deepen_expectiminimax, pick_safest};
9use crate::state::{
10    MoveChoice, Pokemon, PokemonVolatileStatus, Side, SideConditions, State,
11    VolatileStatusDurations,
12};
13use clap::Parser;
14use std::io;
15use std::io::Write;
16use std::process::exit;
17use std::str::FromStr;
18use std::sync::{Arc, Mutex};
19
20struct IOData {
21    state: State,
22    instruction_list: Vec<Vec<Instruction>>,
23    last_instructions_generated: Vec<StateInstructions>,
24}
25
26#[derive(Parser)]
27struct Cli {
28    #[clap(short, long, default_value = "")]
29    state: String,
30
31    #[clap(subcommand)]
32    subcmd: Option<SubCommand>,
33}
34
35#[derive(Parser)]
36enum SubCommand {
37    Expectiminimax(Expectiminimax),
38    IterativeDeepening(IterativeDeepening),
39    MonteCarloTreeSearch(MonteCarloTreeSearch),
40    CalculateDamage(CalculateDamage),
41    GenerateInstructions(GenerateInstructions),
42}
43
44#[derive(Parser)]
45struct Expectiminimax {
46    #[clap(short, long, required = true)]
47    state: String,
48
49    #[clap(short, long, default_value_t = false)]
50    ab_prune: bool,
51
52    #[clap(short, long, default_value_t = 2)]
53    depth: i8,
54}
55
56#[derive(Parser)]
57struct IterativeDeepening {
58    #[clap(short, long, required = true)]
59    state: String,
60
61    #[clap(short, long, default_value_t = 5000)]
62    time_to_search_ms: u64,
63}
64
65#[derive(Parser)]
66struct MonteCarloTreeSearch {
67    #[clap(short, long, required = true)]
68    state: String,
69
70    #[clap(short, long, default_value_t = 5000)]
71    time_to_search_ms: u64,
72}
73
74#[derive(Parser)]
75struct CalculateDamage {
76    #[clap(short, long, required = true)]
77    state: String,
78
79    #[clap(short = 'o', long, required = true)]
80    side_one_move: String,
81
82    #[clap(short = 't', long, required = true)]
83    side_two_move: String,
84
85    #[clap(short = 'm', long, required = false, default_value_t = false)]
86    side_one_moves_first: bool,
87}
88
89#[derive(Parser)]
90struct GenerateInstructions {
91    #[clap(short, long, required = true)]
92    state: String,
93
94    #[clap(short = 'o', long, required = true)]
95    side_one_move: String,
96
97    #[clap(short = 't', long, required = true)]
98    side_two_move: String,
99}
100
101impl Default for IOData {
102    fn default() -> Self {
103        IOData {
104            state: State::default(),
105            instruction_list: Vec::new(),
106            last_instructions_generated: Vec::new(),
107        }
108    }
109}
110
111impl VolatileStatusDurations {
112    fn io_print(&self) -> String {
113        let durations = [
114            ("confusion", self.confusion),
115            ("encore", self.encore),
116            ("lockedmove", self.lockedmove),
117            ("slowstart", self.slowstart),
118            ("yawn", self.yawn),
119        ];
120
121        let mut output = String::new();
122        for (name, value) in durations {
123            if value != 0 {
124                output.push_str(&format!("\n  {}: {}", name, value));
125            }
126        }
127        if output.is_empty() {
128            return "none".to_string();
129        }
130        output
131    }
132}
133
134impl SideConditions {
135    fn io_print(&self) -> String {
136        let conditions = [
137            ("aurora_veil", self.aurora_veil),
138            ("crafty_shield", self.crafty_shield),
139            ("healing_wish", self.healing_wish),
140            ("light_screen", self.light_screen),
141            ("lucky_chant", self.lucky_chant),
142            ("lunar_dance", self.lunar_dance),
143            ("mat_block", self.mat_block),
144            ("mist", self.mist),
145            ("protect", self.protect),
146            ("quick_guard", self.quick_guard),
147            ("reflect", self.reflect),
148            ("safeguard", self.safeguard),
149            ("spikes", self.spikes),
150            ("stealth_rock", self.stealth_rock),
151            ("sticky_web", self.sticky_web),
152            ("tailwind", self.tailwind),
153            ("toxic_count", self.toxic_count),
154            ("toxic_spikes", self.toxic_spikes),
155            ("wide_guard", self.wide_guard),
156        ];
157
158        let mut output = String::new();
159        for (name, value) in conditions {
160            if value != 0 {
161                output.push_str(&format!("\n  {}: {}", name, value));
162            }
163        }
164        if output.is_empty() {
165            return "none".to_string();
166        }
167        output
168    }
169}
170
171impl Side {
172    fn io_print_boosts(&self) -> String {
173        format!(
174            "Attack:{}, Defense:{}, SpecialAttack:{}, SpecialDefense:{}, Speed:{}",
175            self.attack_boost,
176            self.defense_boost,
177            self.special_attack_boost,
178            self.special_defense_boost,
179            self.speed_boost
180        )
181    }
182    fn io_conditional_print(&self) -> String {
183        let mut output = String::new();
184        if self.baton_passing {
185            output.push_str("\n  baton_passing: true");
186        }
187        if self.wish.0 != 0 {
188            output.push_str(&format!("\n  wish: ({}, {})", self.wish.0, self.wish.1));
189        }
190        if self.future_sight.0 != 0 {
191            output.push_str(&format!(
192                "\n  future_sight: ({}, {:?})",
193                self.future_sight.0, self.pokemon[self.future_sight.1].id
194            ));
195        }
196        if self
197            .volatile_statuses
198            .contains(&PokemonVolatileStatus::SUBSTITUTE)
199        {
200            output.push_str(&format!(
201                "\n  substitute_health: {}",
202                self.substitute_health
203            ));
204        }
205
206        if !output.is_empty() {
207            output.insert_str(0, "Extras:");
208            output.push_str("\n");
209        }
210
211        output
212    }
213    fn io_print(&self, available_choices: Vec<String>) -> String {
214        let reserve = self
215            .pokemon
216            .into_iter()
217            .map(|p| p.io_print_reserve())
218            .collect::<Vec<String>>();
219        format!(
220            "\nPokemon: {}\n\
221            Active:{}\n\
222            Boosts: {}\n\
223            Last Used Move: {}\n\
224            Volatiles: {:?}\n\
225            VolatileDurations: {}\n\
226            Side Conditions: {}\n\
227            {}\
228            Available Choices: {}",
229            reserve.join(", "),
230            self.get_active_immutable().io_print_active(),
231            self.io_print_boosts(),
232            self.last_used_move.serialize(),
233            self.volatile_statuses,
234            self.volatile_status_durations.io_print(),
235            self.side_conditions.io_print(),
236            self.io_conditional_print(),
237            available_choices.join(", ")
238        )
239    }
240
241    fn option_to_string(&self, option: &MoveChoice) -> String {
242        match option {
243            MoveChoice::MoveTera(index) => {
244                format!("{}-tera", self.get_active_immutable().moves[index].id).to_lowercase()
245            }
246            MoveChoice::Move(index) => {
247                format!("{}", self.get_active_immutable().moves[index].id).to_lowercase()
248            }
249            MoveChoice::Switch(index) => format!("{}", self.pokemon[*index].id).to_lowercase(),
250            MoveChoice::None => "none".to_string(),
251        }
252    }
253
254    pub fn string_to_movechoice(&self, s: &str) -> Option<MoveChoice> {
255        let s = s.to_lowercase();
256        if s == "none" {
257            return Some(MoveChoice::None);
258        }
259
260        let mut pkmn_iter = self.pokemon.into_iter();
261        while let Some(pkmn) = pkmn_iter.next() {
262            if pkmn.id.to_string().to_lowercase() == s
263                && pkmn_iter.pokemon_index != self.active_index
264            {
265                return Some(MoveChoice::Switch(pkmn_iter.pokemon_index));
266            }
267        }
268
269        // check if s endswith `-tera`
270        // if it does, find the move with the name and return MoveChoice::MoveTera
271        // if it doesn't, find the move with the name and return MoveChoice::Move
272        let mut move_iter = self.get_active_immutable().moves.into_iter();
273        let mut move_name = s;
274        if move_name.ends_with("-tera") {
275            move_name = move_name[..move_name.len() - 5].to_string();
276            while let Some(mv) = move_iter.next() {
277                if format!("{:?}", mv.id).to_lowercase() == move_name {
278                    return Some(MoveChoice::MoveTera(move_iter.pokemon_move_index));
279                }
280            }
281        } else {
282            while let Some(mv) = move_iter.next() {
283                if format!("{:?}", mv.id).to_lowercase() == move_name {
284                    return Some(MoveChoice::Move(move_iter.pokemon_move_index));
285                }
286            }
287        }
288
289        None
290    }
291}
292
293impl Pokemon {
294    fn io_print_reserve(&self) -> String {
295        format!("{}:{}/{}", self.id, self.hp, self.maxhp)
296    }
297    fn io_print_active(&self) -> String {
298        let moves: Vec<String> = self
299            .moves
300            .into_iter()
301            .map(|m| format!("{:?}", m.id).to_lowercase())
302            .filter(|x| x != "none")
303            .collect();
304        format!(
305            "\n  Name: {}\n  HP: {}/{}\n  Status: {:?}\n  Ability: {:?}\n  Item: {:?}\n  Moves: {}",
306            self.id,
307            self.hp,
308            self.maxhp,
309            self.status,
310            self.ability,
311            self.item,
312            moves.join(", ")
313        )
314    }
315}
316
317pub fn io_get_all_options(state: &State) -> (Vec<MoveChoice>, Vec<MoveChoice>) {
318    if state.team_preview {
319        let mut s1_options = Vec::with_capacity(6);
320        let mut s2_options = Vec::with_capacity(6);
321
322        let mut pkmn_iter = state.side_one.pokemon.into_iter();
323        while let Some(_) = pkmn_iter.next() {
324            if state.side_one.pokemon[pkmn_iter.pokemon_index].hp > 0 {
325                s1_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
326            }
327        }
328        let mut pkmn_iter = state.side_two.pokemon.into_iter();
329        while let Some(_) = pkmn_iter.next() {
330            if state.side_two.pokemon[pkmn_iter.pokemon_index].hp > 0 {
331                s2_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
332            }
333        }
334        return (s1_options, s2_options);
335    }
336
337    let (mut s1_options, mut s2_options) = state.get_all_options();
338
339    if state.side_one.force_trapped {
340        s1_options.retain(|x| match x {
341            MoveChoice::Move(_) | MoveChoice::MoveTera(_) => true,
342            MoveChoice::Switch(_) => false,
343            MoveChoice::None => true,
344        });
345    }
346    if state.side_one.slow_uturn_move {
347        s1_options.clear();
348        let encored = state
349            .side_one
350            .volatile_statuses
351            .contains(&PokemonVolatileStatus::ENCORE);
352        state.side_one.get_active_immutable().add_available_moves(
353            &mut s1_options,
354            &state.side_one.last_used_move,
355            encored,
356            state.side_one.can_use_tera(),
357        );
358    }
359
360    if state.side_two.force_trapped {
361        s2_options.retain(|x| match x {
362            MoveChoice::Move(_) | MoveChoice::MoveTera(_) => true,
363            MoveChoice::Switch(_) => false,
364            MoveChoice::None => true,
365        });
366    }
367    if state.side_two.slow_uturn_move {
368        s2_options.clear();
369        let encored = state
370            .side_two
371            .volatile_statuses
372            .contains(&PokemonVolatileStatus::ENCORE);
373        state.side_two.get_active_immutable().add_available_moves(
374            &mut s2_options,
375            &state.side_two.last_used_move,
376            encored,
377            state.side_two.can_use_tera(),
378        );
379    }
380
381    if s1_options.len() == 0 {
382        s1_options.push(MoveChoice::None);
383    }
384    if s2_options.len() == 0 {
385        s2_options.push(MoveChoice::None);
386    }
387
388    (s1_options, s2_options)
389}
390
391fn pprint_expectiminimax_result(
392    result: &Vec<f32>,
393    s1_options: &Vec<MoveChoice>,
394    s2_options: &Vec<MoveChoice>,
395    safest_choice: &(usize, f32),
396    state: &State,
397) {
398    let s1_len = s1_options.len();
399    let s2_len = s2_options.len();
400
401    print!("{: <12}", " ");
402
403    for s2_move in s2_options.iter() {
404        match s2_move {
405            MoveChoice::MoveTera(m) => {
406                let s2_move_str =
407                    format!("{}-tera", state.side_two.get_active_immutable().moves[m].id);
408                print!("{: >12}", s2_move_str.to_lowercase());
409            }
410            MoveChoice::Move(m) => {
411                let s2_move_str = format!("{}", state.side_two.get_active_immutable().moves[m].id);
412                print!("{: >12}", s2_move_str.to_lowercase());
413            }
414            MoveChoice::Switch(s) => {
415                let s2_move_str = format!(
416                    "{}",
417                    state.side_two.pokemon[*s].id.to_string().to_lowercase()
418                );
419                print!("{: >12}", s2_move_str);
420            }
421            MoveChoice::None => {}
422        }
423    }
424    print!("\n");
425
426    for i in 0..s1_len {
427        let s1_move_str = s1_options[i];
428        match s1_move_str {
429            MoveChoice::MoveTera(m) => {
430                let move_id = format!(
431                    "{}-tera",
432                    state.side_one.get_active_immutable().moves[&m].id
433                );
434                print!("{:<12}", move_id.to_string().to_lowercase());
435            }
436            MoveChoice::Move(m) => {
437                let move_id = state.side_one.get_active_immutable().moves[&m].id;
438                print!("{:<12}", move_id.to_string().to_lowercase());
439            }
440            MoveChoice::Switch(s) => {
441                let pkmn_id = &state.side_one.pokemon[s].id;
442                print!("{:<12}", pkmn_id.to_string().to_lowercase());
443            }
444            MoveChoice::None => {}
445        }
446        for j in 0..s2_len {
447            let index = i * s2_len + j;
448            print!("{number:>11.2} ", number = result[index]);
449        }
450        print!("\n");
451    }
452    match s1_options[safest_choice.0] {
453        MoveChoice::MoveTera(m) => {
454            let move_id = format!(
455                "{}-tera",
456                state.side_one.get_active_immutable().moves[&m].id
457            );
458            print!(
459                "\nSafest Choice: {}, {}\n",
460                move_id.to_string().to_lowercase(),
461                safest_choice.1
462            );
463        }
464        MoveChoice::Move(m) => {
465            let move_id = state.side_one.get_active_immutable().moves[&m].id;
466            print!(
467                "\nSafest Choice: {}, {}\n",
468                move_id.to_string().to_lowercase(),
469                safest_choice.1
470            );
471        }
472        MoveChoice::Switch(s) => {
473            let pkmn_id = &state.side_one.pokemon[s].id;
474            print!(
475                "\nSafest Choice: Switch {}, {}\n",
476                pkmn_id.to_string().to_lowercase(),
477                safest_choice.1
478            );
479        }
480        MoveChoice::None => println!("No Move"),
481    }
482}
483
484fn print_mcts_result(state: &State, result: MctsResult) {
485    let s1_joined_options = result
486        .s1
487        .iter()
488        .map(|x| {
489            format!(
490                "{},{:.2},{}",
491                get_move_id_from_movechoice(&state.side_one, &x.move_choice),
492                x.total_score,
493                x.visits
494            )
495        })
496        .collect::<Vec<String>>()
497        .join("|");
498    let s2_joined_options = result
499        .s2
500        .iter()
501        .map(|x| {
502            format!(
503                "{},{:.2},{}",
504                get_move_id_from_movechoice(&state.side_two, &x.move_choice),
505                x.total_score,
506                x.visits
507            )
508        })
509        .collect::<Vec<String>>()
510        .join("|");
511
512    println!("Total Iterations: {}", result.iteration_count);
513    println!("side one: {}", s1_joined_options);
514    println!("side two: {}", s2_joined_options);
515}
516
517fn pprint_mcts_result(state: &State, result: MctsResult) {
518    println!("\nTotal Iterations: {}\n", result.iteration_count);
519    println!("Side One:");
520    println!(
521        "\t{:<25}{:>12}{:>12}{:>10}{:>10}",
522        "Move", "Total Score", "Avg Score", "Visits", "% Visits"
523    );
524    for x in result.s1.iter() {
525        println!(
526            "\t{:<25}{:>12.2}{:>12.2}{:>10}{:>10.2}",
527            get_move_id_from_movechoice(&state.side_one, &x.move_choice),
528            x.total_score,
529            x.total_score / x.visits as f32,
530            x.visits,
531            (x.visits as f32 / result.iteration_count as f32) * 100.0
532        );
533    }
534
535    println!("Side Two:");
536    println!(
537        "\t{:<25}{:>12}{:>12}{:>10}{:>10}",
538        "Move", "Total Score", "Avg Score", "Visits", "% Visits"
539    );
540    for x in result.s2.iter() {
541        println!(
542            "\t{:<25}{:>12.2}{:>12.2}{:>10}{:>10.2}",
543            get_move_id_from_movechoice(&state.side_two, &x.move_choice),
544            x.total_score,
545            x.total_score / x.visits as f32,
546            x.visits,
547            (x.visits as f32 / result.iteration_count as f32) * 100.0
548        );
549    }
550}
551
552fn pprint_state_instruction_vector(instructions: &Vec<StateInstructions>) {
553    for (i, instruction) in instructions.iter().enumerate() {
554        println!("Index: {}", i);
555        println!("StateInstruction: {:?}", instruction);
556    }
557}
558
559fn get_move_id_from_movechoice(side: &Side, move_choice: &MoveChoice) -> String {
560    match move_choice {
561        MoveChoice::MoveTera(index) => {
562            format!("{}-tera", side.get_active_immutable().moves[&index].id).to_lowercase()
563        }
564        MoveChoice::Move(index) => {
565            format!("{}", side.get_active_immutable().moves[&index].id).to_lowercase()
566        }
567        MoveChoice::Switch(index) => format!("switch {}", side.pokemon[*index].id).to_lowercase(),
568        MoveChoice::None => "No Move".to_string(),
569    }
570}
571
572fn print_subcommand_result(
573    result: &Vec<f32>,
574    side_one_options: &Vec<MoveChoice>,
575    side_two_options: &Vec<MoveChoice>,
576    state: &State,
577) {
578    let safest = pick_safest(&result, side_one_options.len(), side_two_options.len());
579    let move_choice = side_one_options[safest.0];
580
581    let joined_side_one_options = side_one_options
582        .iter()
583        .map(|x| format!("{}", get_move_id_from_movechoice(&state.side_one, x)))
584        .collect::<Vec<String>>()
585        .join(",");
586    println!("side one options: {}", joined_side_one_options);
587
588    let joined_side_two_options = side_two_options
589        .iter()
590        .map(|x| format!("{}", get_move_id_from_movechoice(&state.side_two, x)))
591        .collect::<Vec<String>>()
592        .join(",");
593    println!("side two options: {}", joined_side_two_options);
594
595    let joined = result
596        .iter()
597        .map(|x| format!("{:.2}", x))
598        .collect::<Vec<String>>()
599        .join(",");
600    println!("matrix: {}", joined);
601    match move_choice {
602        MoveChoice::MoveTera(_) => {
603            println!(
604                "choice: {}-tera",
605                get_move_id_from_movechoice(&state.side_one, &move_choice)
606            );
607        }
608        MoveChoice::Move(_) => {
609            println!(
610                "choice: {}",
611                get_move_id_from_movechoice(&state.side_one, &move_choice)
612            );
613        }
614        MoveChoice::Switch(_) => {
615            println!(
616                "choice: switch {}",
617                get_move_id_from_movechoice(&state.side_one, &move_choice)
618            );
619        }
620        MoveChoice::None => {
621            println!("no move");
622        }
623    }
624    println!("evaluation: {}", safest.1);
625}
626
627pub fn main() {
628    let args = Cli::parse();
629    let mut io_data = IOData::default();
630
631    if args.state != "" {
632        let state = State::deserialize(args.state.as_str());
633        io_data.state = state;
634    }
635
636    let result;
637    let mut state;
638    let mut side_one_options;
639    let mut side_two_options;
640    match args.subcmd {
641        None => {
642            command_loop(io_data);
643            exit(0);
644        }
645        Some(subcmd) => match subcmd {
646            SubCommand::Expectiminimax(expectiminimax) => {
647                state = State::deserialize(expectiminimax.state.as_str());
648                (side_one_options, side_two_options) = io_get_all_options(&state);
649                result = expectiminimax_search(
650                    &mut state,
651                    expectiminimax.depth,
652                    side_one_options.clone(),
653                    side_two_options.clone(),
654                    expectiminimax.ab_prune,
655                    &Arc::new(Mutex::new(true)),
656                );
657                print_subcommand_result(&result, &side_one_options, &side_two_options, &state);
658            }
659            SubCommand::IterativeDeepening(iterative_deepending) => {
660                state = State::deserialize(iterative_deepending.state.as_str());
661                (side_one_options, side_two_options) = io_get_all_options(&state);
662                (side_one_options, side_two_options, result, _) = iterative_deepen_expectiminimax(
663                    &mut state,
664                    side_one_options.clone(),
665                    side_two_options.clone(),
666                    std::time::Duration::from_millis(iterative_deepending.time_to_search_ms),
667                );
668                print_subcommand_result(&result, &side_one_options, &side_two_options, &state);
669            }
670            SubCommand::MonteCarloTreeSearch(mcts) => {
671                state = State::deserialize(mcts.state.as_str());
672                (side_one_options, side_two_options) = io_get_all_options(&state);
673                let result = perform_mcts(
674                    &mut state,
675                    side_one_options.clone(),
676                    side_two_options.clone(),
677                    std::time::Duration::from_millis(mcts.time_to_search_ms),
678                );
679                print_mcts_result(&state, result);
680            }
681            SubCommand::CalculateDamage(calculate_damage) => {
682                state = State::deserialize(calculate_damage.state.as_str());
683                let mut s1_choice = MOVES
684                    .get(&Choices::from_str(calculate_damage.side_one_move.as_str()).unwrap())
685                    .unwrap()
686                    .to_owned();
687                let mut s2_choice = MOVES
688                    .get(&Choices::from_str(calculate_damage.side_two_move.as_str()).unwrap())
689                    .unwrap()
690                    .to_owned();
691                let s1_moves_first = calculate_damage.side_one_moves_first;
692                if calculate_damage.side_one_move == "switch" {
693                    s1_choice.category = MoveCategory::Switch
694                }
695                if calculate_damage.side_two_move == "switch" {
696                    s2_choice.category = MoveCategory::Switch
697                }
698                calculate_damage_io(&state, s1_choice, s2_choice, s1_moves_first);
699            }
700            SubCommand::GenerateInstructions(generate_instructions) => {
701                state = State::deserialize(generate_instructions.state.as_str());
702                let (s1_movechoice, s2_movechoice);
703                match state
704                    .side_one
705                    .string_to_movechoice(generate_instructions.side_one_move.as_str())
706                {
707                    None => {
708                        println!(
709                            "Invalid move choice for side one: {}",
710                            generate_instructions.side_one_move
711                        );
712                        exit(1);
713                    }
714                    Some(v) => s1_movechoice = v,
715                }
716                match state
717                    .side_two
718                    .string_to_movechoice(generate_instructions.side_two_move.as_str())
719                {
720                    None => {
721                        println!(
722                            "Invalid move choice for side two: {}",
723                            generate_instructions.side_two_move
724                        );
725                        exit(1);
726                    }
727                    Some(v) => s2_movechoice = v,
728                }
729                let instructions = generate_instructions_from_move_pair(
730                    &mut state,
731                    &s1_movechoice,
732                    &s2_movechoice,
733                    true,
734                );
735                pprint_state_instruction_vector(&instructions);
736            }
737        },
738    }
739
740    exit(0);
741}
742
743fn calculate_damage_io(
744    state: &State,
745    s1_choice: Choice,
746    s2_choice: Choice,
747    side_one_moves_first: bool,
748) {
749    let (damages_dealt_s1, damages_dealt_s2) =
750        calculate_both_damage_rolls(state, s1_choice, s2_choice, side_one_moves_first);
751
752    for dmg in [damages_dealt_s1, damages_dealt_s2] {
753        match dmg {
754            Some(damages_vec) => {
755                let joined = damages_vec
756                    .iter()
757                    .map(|x| format!("{:?}", x))
758                    .collect::<Vec<String>>()
759                    .join(",");
760                println!("Damage Rolls: {}", joined);
761            }
762            None => {
763                println!("Damage Rolls: 0");
764            }
765        }
766    }
767}
768
769fn command_loop(mut io_data: IOData) {
770    loop {
771        print!("> ");
772        let _ = io::stdout().flush();
773
774        let mut input = String::new();
775        match io::stdin().read_line(&mut input) {
776            Ok(_) => {}
777            Err(error) => {
778                println!("Error reading input: {}", error);
779                continue;
780            }
781        }
782        let mut parts = input.trim().split_whitespace();
783        let command = parts.next().unwrap_or("");
784        let mut args = parts;
785
786        match command {
787            "state" | "s" => {
788                let state_string;
789                match args.next() {
790                    Some(s) => {
791                        state_string = s;
792                        let state = State::deserialize(state_string);
793                        io_data.state = state;
794                        println!("state initialized");
795                    }
796                    None => {
797                        println!("Expected state string");
798                    }
799                }
800                println!("{:?}", io_data.state);
801            }
802            "serialize" | "ser" => {
803                println!("{}", io_data.state.serialize());
804            }
805            "matchup" | "m" => {
806                let (side_one_options, side_two_options) = io_get_all_options(&io_data.state);
807
808                let mut side_one_choices = vec![];
809                for option in side_one_options {
810                    side_one_choices.push(
811                        format!("{}", io_data.state.side_one.option_to_string(&option))
812                            .to_lowercase(),
813                    );
814                }
815                let mut side_two_choices = vec![];
816                for option in side_two_options {
817                    side_two_choices.push(
818                        format!("{}", io_data.state.side_two.option_to_string(&option))
819                            .to_lowercase(),
820                    );
821                }
822                println!(
823                    "SideOne {}\n\nvs\n\nSideTwo {}\n\nState:\n  Weather: {:?},{}\n  Terrain: {:?},{}\n  TrickRoom: {},{}\n  UseLastUsedMove: {}\n  UseDamageDealt: {}",
824                    io_data.state.side_one.io_print(side_one_choices),
825                    io_data.state.side_two.io_print(side_two_choices),
826                    io_data.state.weather.weather_type,
827                    io_data.state.weather.turns_remaining,
828                    io_data.state.terrain.terrain_type,
829                    io_data.state.terrain.turns_remaining,
830                    io_data.state.trick_room.active,
831                    io_data.state.trick_room.turns_remaining,
832                    io_data.state.use_last_used_move,
833                    io_data.state.use_damage_dealt,
834                );
835            }
836            "generate-instructions" | "g" => {
837                let (s1_move, s2_move);
838                match args.next() {
839                    Some(s) => match io_data.state.side_one.string_to_movechoice(s) {
840                        Some(m) => {
841                            s1_move = m;
842                        }
843                        None => {
844                            println!("Invalid move choice for side one: {}", s);
845                            continue;
846                        }
847                    },
848                    None => {
849                        println!("Usage: generate-instructions <side-1 move> <side-2 move>");
850                        continue;
851                    }
852                }
853                match args.next() {
854                    Some(s) => match io_data.state.side_two.string_to_movechoice(s) {
855                        Some(m) => {
856                            s2_move = m;
857                        }
858                        None => {
859                            println!("Invalid move choice for side two: {}", s);
860                            continue;
861                        }
862                    },
863                    None => {
864                        println!("Usage: generate-instructions <side-1 choice> <side-2 choice>");
865                        continue;
866                    }
867                }
868                let instructions = generate_instructions_from_move_pair(
869                    &mut io_data.state,
870                    &s1_move,
871                    &s2_move,
872                    true,
873                );
874                pprint_state_instruction_vector(&instructions);
875                io_data.last_instructions_generated = instructions;
876            }
877            "calculate-damage" | "d" => {
878                let (mut s1_choice, mut s2_choice);
879                match args.next() {
880                    Some(s) => {
881                        s1_choice = MOVES
882                            .get(&Choices::from_str(s).unwrap())
883                            .unwrap()
884                            .to_owned();
885                        if s == "switch" {
886                            s1_choice.category = MoveCategory::Switch
887                        }
888                    }
889                    None => {
890                        println!("Usage: calculate-damage <side-1 move> <side-2 move> <side-1-moves-first>");
891                        continue;
892                    }
893                }
894                match args.next() {
895                    Some(s) => {
896                        s2_choice = MOVES
897                            .get(&Choices::from_str(s).unwrap())
898                            .unwrap()
899                            .to_owned();
900                        if s == "switch" {
901                            s2_choice.category = MoveCategory::Switch
902                        }
903                    }
904                    None => {
905                        println!("Usage: calculate-damage <side-1 move> <side-2 move> <side-1-moves-first>");
906                        continue;
907                    }
908                }
909                let s1_moves_first: bool;
910                match args.next() {
911                    Some(s) => {
912                        s1_moves_first = s.parse::<bool>().unwrap();
913                    }
914                    None => {
915                        println!("Usage: calculate-damage <side-1 move> <side-2 move> <side-1-moves-first>");
916                        continue;
917                    }
918                }
919                calculate_damage_io(&io_data.state, s1_choice, s2_choice, s1_moves_first);
920            }
921            "instructions" | "i" => {
922                println!("{:?}", io_data.last_instructions_generated);
923            }
924            "evaluate" | "ev" => {
925                println!("Evaluation: {}", evaluate(&io_data.state));
926            }
927            "iterative-deepening" | "id" => match args.next() {
928                Some(s) => {
929                    let max_time_ms = s.parse::<u64>().unwrap();
930                    let (side_one_options, side_two_options) = io_get_all_options(&io_data.state);
931
932                    let start_time = std::time::Instant::now();
933                    let (s1_moves, s2_moves, result, depth_searched) =
934                        iterative_deepen_expectiminimax(
935                            &mut io_data.state,
936                            side_one_options.clone(),
937                            side_two_options.clone(),
938                            std::time::Duration::from_millis(max_time_ms),
939                        );
940                    let elapsed = start_time.elapsed();
941
942                    let safest_choice = pick_safest(&result, s1_moves.len(), s2_moves.len());
943
944                    pprint_expectiminimax_result(
945                        &result,
946                        &s1_moves,
947                        &s2_moves,
948                        &safest_choice,
949                        &io_data.state,
950                    );
951                    println!("Took: {:?}", elapsed);
952                    println!("Depth Searched: {}", depth_searched);
953                }
954                None => {
955                    println!("Usage: iterative-deepening <timeout_ms>");
956                    continue;
957                }
958            },
959            "monte-carlo-tree-search" | "mcts" => match args.next() {
960                Some(s) => {
961                    let max_time_ms = s.parse::<u64>().unwrap();
962                    let (side_one_options, side_two_options) = io_get_all_options(&io_data.state);
963
964                    let start_time = std::time::Instant::now();
965                    let result = perform_mcts(
966                        &mut io_data.state,
967                        side_one_options.clone(),
968                        side_two_options.clone(),
969                        std::time::Duration::from_millis(max_time_ms),
970                    );
971                    let elapsed = start_time.elapsed();
972                    pprint_mcts_result(&io_data.state, result);
973
974                    println!("\nTook: {:?}", elapsed);
975                }
976                None => {
977                    println!("Usage: monte-carlo-tree-search <timeout_ms>");
978                    continue;
979                }
980            },
981            "apply" | "a" => match args.next() {
982                Some(s) => {
983                    let index = s.parse::<usize>().unwrap();
984                    let instructions = io_data.last_instructions_generated.remove(index);
985                    io_data
986                        .state
987                        .apply_instructions(&instructions.instruction_list);
988                    io_data.instruction_list.push(instructions.instruction_list);
989                    io_data.last_instructions_generated = Vec::new();
990                    println!("Applied instructions at index {}", index)
991                }
992                None => {
993                    println!("Usage: apply <instruction index>");
994                    continue;
995                }
996            },
997            "pop" | "p" => {
998                if io_data.instruction_list.is_empty() {
999                    println!("No instructions to pop");
1000                    continue;
1001                }
1002                let instructions = io_data.instruction_list.pop().unwrap();
1003                io_data.state.reverse_instructions(&instructions);
1004                println!("Popped last applied instructions");
1005            }
1006            "pop-all" | "pa" => {
1007                for i in io_data.instruction_list.iter().rev() {
1008                    io_data.state.reverse_instructions(i);
1009                }
1010                io_data.instruction_list.clear();
1011                println!("Popped all applied instructions");
1012            }
1013            "expectiminimax" | "e" => match args.next() {
1014                Some(s) => {
1015                    let mut ab_prune = false;
1016                    match args.next() {
1017                        Some(s) => ab_prune = s.parse::<bool>().unwrap(),
1018                        None => {}
1019                    }
1020                    let depth = s.parse::<i8>().unwrap();
1021                    let (side_one_options, side_two_options) = io_get_all_options(&io_data.state);
1022                    let start_time = std::time::Instant::now();
1023                    let result = expectiminimax_search(
1024                        &mut io_data.state,
1025                        depth,
1026                        side_one_options.clone(),
1027                        side_two_options.clone(),
1028                        ab_prune,
1029                        &Arc::new(Mutex::new(true)),
1030                    );
1031                    let elapsed = start_time.elapsed();
1032
1033                    let safest_choice =
1034                        pick_safest(&result, side_one_options.len(), side_two_options.len());
1035                    pprint_expectiminimax_result(
1036                        &result,
1037                        &side_one_options,
1038                        &side_two_options,
1039                        &safest_choice,
1040                        &io_data.state,
1041                    );
1042                    println!("\nTook: {:?}", elapsed);
1043                }
1044                None => {
1045                    println!("Usage: expectiminimax <depth> <ab_prune=false>");
1046                    continue;
1047                }
1048            },
1049            "" => {
1050                continue;
1051            }
1052            "exit" | "quit" | "q" => {
1053                break;
1054            }
1055            command => {
1056                println!("Unknown command: {}", command);
1057            }
1058        }
1059    }
1060}