connect4_lib/
io.rs

1use super::game::{Board, BoardState, ChipDescrip, Game, Player};
2use std::io::{stdin, stdout, Write};
3
4pub fn draw_term_board(game: &Board) {
5    let io = TermIO::new();
6    io.draw_board(game);
7}
8
9pub trait GameIO {
10    fn draw_board(&self, game: &Board);
11    fn get_move(&self, game: &Game) -> (isize, ChipDescrip);
12    fn display_gameover(&self, ending: BoardState);
13}
14
15pub const EMPTY: char = '◻';
16pub const FILLED: char = '◼';
17pub const BRIGHTEN: isize = 60;
18pub const FG: isize = 30;
19pub const BG: isize = 40;
20pub const BLK: isize = 0;
21pub const RED: isize = 1;
22pub const GRN: isize = 2;
23pub const YEL: isize = 3;
24pub const BLU: isize = 4;
25pub const MAG: isize = 5;
26pub const CYN: isize = 6;
27pub const WHT: isize = 7;
28pub const RST: isize = 9;
29
30pub struct TermIO {
31    fg: isize,
32    bg: isize,
33}
34
35impl TermIO {
36    pub fn new() -> Self {
37        Self { fg: RST, bg: RST }
38    }
39
40    fn paint(fg: isize, bg: isize) {
41        let esc = char::from(0x1b);
42        print!("{}[{};{}m", esc, fg + FG, bg + BG)
43    }
44    fn endpaint() {
45        let esc = char::from(0x1b);
46        print!("{}[0m", esc)
47    }
48
49    fn print_with_color(&mut self, s: char, fg: isize, bg: isize) {
50        if fg != self.fg || bg == self.fg {
51            Self::paint(fg, bg);
52            self.fg = fg;
53            self.bg = bg;
54        }
55        print!("{}", s);
56    }
57}
58impl GameIO for TermIO {
59    fn draw_board(&self, game: &Board) {
60        let mut drawer = Self { fg: 0, bg: 0 };
61        let chips = game.get_layout();
62        for i in 0..chips.len() {
63            let x = i % game.width as usize;
64            let y = i / game.width as usize;
65            let y = game.height as usize - y - 1;
66            let i = x + y * game.width as usize;
67            if let Some(chip) = chips[i] {
68                drawer.print_with_color(chip.graphic, chip.fg_color, chip.bg_color);
69            } else {
70                drawer.print_with_color(EMPTY, WHT, BLK + BRIGHTEN);
71            }
72            print!(" ");
73            if (i + 1) % game.width as usize == 0 {
74                drawer.print_with_color('\n', RST, RST);
75            }
76        }
77
78        drawer.print_with_color('1', WHT, BLK + BRIGHTEN);
79        (1..game.width as usize).for_each(|i| print!(" {}", i + 1));
80        print!(" ");
81        Self::endpaint();
82        println!();
83    }
84
85    fn get_move(&self, game: &Game) -> (isize, ChipDescrip) {
86        fn read_line() -> String {
87            let mut buffer = String::new();
88            stdout().flush().expect("Failed to flush");
89            let _res = stdin().read_line(&mut buffer);
90            buffer.trim().to_string()
91        }
92        fn get_num_in_range(lb: usize, ub: usize) -> usize {
93            print!("Enter a number in range [{},{}]: ", lb, ub);
94            if let Ok(v) = read_line().parse::<usize>() {
95                if v >= lb && v <= ub {
96                    return v;
97                }
98            }
99            get_num_in_range(lb, ub)
100        }
101
102        println!(
103            "Player {} turn.",
104            game.get_turn() as usize % game.get_player_count() as usize + 1
105        );
106
107        let player = game.current_player();
108        let ch = if player.chip_options.len() == 1 {
109            player.chip_options[0]
110        } else {
111            fn get_chip_type(player: &Player) -> ChipDescrip {
112                let mut drawer = TermIO { fg: 0, bg: 0 };
113                println!("Select chip type:");
114                for chip in &player.chip_options {
115                    drawer.print_with_color(chip.graphic, chip.fg_color, chip.bg_color);
116                    drawer.print_with_color(' ', chip.fg_color, chip.bg_color)
117                }
118                TermIO::endpaint();
119                println!();
120                drawer.print_with_color('​', WHT, BLK + BRIGHTEN);
121                for i in 0..player.chip_options.len() {
122                    print!("{} ", i + 1);
123                }
124                TermIO::endpaint();
125                println!();
126
127                let l = player.chip_options.len();
128                player.chip_options[get_num_in_range(1, l) - 1]
129            }
130            get_chip_type(player)
131        };
132
133        let val = get_num_in_range(1, game.get_board().width as usize) - 1;
134        (val as isize, ch)
135    }
136
137    fn display_gameover(&self, ending: BoardState) {
138        match ending {
139            BoardState::Win(x) => println!("Player {} wins!", x),
140            BoardState::Draw => println!("It's a draw :("),
141            BoardState::Ongoing => (),
142            BoardState::Invalid => println!("Illegal move!"),
143        }
144    }
145}