1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
use self::internal::PromptDiskMoveResult;
use crate::Play;
use console::Term;
use std::io::{stdin, stdout, Write};
mod internal;

pub struct TowerOfHanoi;

impl TowerOfHanoi {
    fn prompt_disk_count(&self) -> usize {
        let default: usize = 3;
        loop {
            print!("Enter disk count (left empty for default of {default}): ");
            stdout().flush().expect("Failed to flush stdout");
            let mut input = String::new();
            stdin().read_line(&mut input).expect("Failed to read line");
            if input.trim().is_empty() {
                break default;
            }
            let Ok(count) = input.trim().parse() else { continue };
            break count;
        }
    }
}

impl Play for TowerOfHanoi {
    fn name(&self) -> &'static str {
        "Tower of Hanoi"
    }

    fn start(&self) {
        let term = Term::stdout();
        let disk_count = self.prompt_disk_count();
        let mut game = internal::TowerOfHanoi::new(disk_count);

        loop {
            term.clear_screen().expect("Failed to clear screen");
            game.render();

            if let Ok(PromptDiskMoveResult { from, to }) = game.prompt_disk_move() {
                if game.move_disk(from, to).is_err() {
                    term.clear_screen().expect("Failed to clear screen");
                    continue;
                }
            } else {
                term.clear_screen().expect("Failed to clear screen");
                continue;
            }

            if game.win() {
                term.clear_screen().expect("Failed to clear screen");
                game.render();
                println!("You win!\n");
                break;
            }
        }
    }
}