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