c4_e5_chess/cmd/
cli.rs

1use super::time_management::TimeManagement;
2use crate::engine::game::Game;
3use crate::misc::types::*;
4use cozy_chess::{util, Board, Color};
5use log::{error, info};
6use std::{
7    io::stdin,
8    str::{FromStr, SplitWhitespace},
9};
10
11/// An UCI interface to be used with a chess GUI.
12/// See https://en.wikipedia.org/wiki/Universal_Chess_Interface .
13pub struct Cli {
14    game: Game,
15    tm: TimeManagement,
16}
17
18impl Cli {
19    /// Constructor
20    pub fn new() -> Cli {
21        Cli {
22            game: Default::default(),
23            tm: TimeManagement::default(),
24        }
25    }
26
27    /// Main execution loop.
28    pub fn execute(&mut self) {
29        loop {
30            let mut input = String::new();
31            stdin().read_line(&mut input).unwrap();
32            let mut input_bak = input.clone();
33            input_bak.pop();
34            let input_bak_str = input_bak.as_str();
35            let mut words = input.split_whitespace();
36
37            match words.next() {
38                Some(command) => {
39                    let args = words;
40                    info!("| {input_bak_str}");
41                    match command {
42                        "uci" => {
43                            self.send_id();
44                            self.send_options();
45                            self.send_uci_ok();
46                        }
47
48                        "isready" => {
49                            self.send_ready_ok();
50                        }
51
52                        "position" => {
53                            self.position(args);
54                        }
55
56                        "go" => {
57                            self.go(args);
58                        }
59
60                        "quit" => return,
61
62                        _ => continue,
63                    }
64                }
65                None => continue,
66            }
67        }
68    }
69
70    /// UCI `position` command
71    fn position(&mut self, mut args: SplitWhitespace) {
72        while let Some(cmd) = args.next() {
73            match cmd {
74                "fen" => {
75                    let mut fen: String = "".to_string();
76                    for i in 0..6 {
77                        match args.next() {
78                            Some(s) => {
79                                fen = fen + s + " ";
80                                if i == 5 {
81                                    // move count
82                                    match s.parse::<MoveNumber>() {
83                                        Ok(n) => self.game.move_number = n,
84                                        Err(_) => error!("No move number in FEN"),
85                                    }
86                                }
87                            }
88                            None => {
89                                error!("No FEN found");
90                                return;
91                            }
92                        }
93                    }
94                    fen = fen.trim_end().to_string();
95                    match Board::from_str(fen.as_str()) {
96                        Ok(b) => self.game.board = b,
97                        Err(e) => {
98                            error!("FEN not valid: {e}");
99                            return;
100                        }
101                    }
102                }
103
104                "startpos" => {
105                    self.game = Game::default();
106                }
107
108                "moves" => loop {
109                    match args.next() {
110                        Some(move_string) => {
111                            match util::parse_uci_move(&self.game.board, move_string) {
112                                Ok(m) => {
113                                    info!("Move: {move_string}");
114                                    self.game.game_history.inc(&self.game.board);
115                                    self.game.board.play_unchecked(m);
116                                    if self.game.board.side_to_move() == Color::Black {
117                                        self.game.move_number += 1;
118                                    }
119                                }
120                                Err(_) => {
121                                    error!("Illegal move");
122                                    return;
123                                }
124                            }
125                        }
126                        None => return,
127                    }
128                },
129
130                _ => break,
131            }
132        }
133    }
134
135    /// UCI `go` command
136    fn go(&mut self, mut args: SplitWhitespace) {
137        while let Some(cmd) = args.next() {
138            match cmd {
139                "searchmoves" => {}
140
141                "ponder" => {}
142
143                "wtime" => match args.next() {
144                    Some(arg) => match arg.parse() {
145                        Ok(a) => self.tm.white_time = a,
146                        Err(_) => break,
147                    },
148                    None => break,
149                },
150
151                "btime" => match args.next() {
152                    Some(arg) => match arg.parse() {
153                        Ok(a) => self.tm.black_time = a,
154                        Err(_) => break,
155                    },
156                    None => break,
157                },
158
159                "winc" => match args.next() {
160                    Some(arg) => match arg.parse() {
161                        Ok(a) => self.tm.white_inc = a,
162                        Err(_) => break,
163                    },
164                    None => break,
165                },
166
167                "binc" => match args.next() {
168                    Some(arg) => match arg.parse() {
169                        Ok(a) => self.tm.black_inc = a,
170                        Err(_) => break,
171                    },
172                    None => break,
173                },
174
175                "movestogo" => match args.next() {
176                    Some(arg) => match arg.parse() {
177                        Ok(a) => self.tm.moves_to_go = a,
178                        Err(_) => break,
179                    },
180                    None => break,
181                },
182
183                "depth" => match args.next() {
184                    Some(arg) => match arg.parse() {
185                        Ok(a) => self.game.max_depth = a,
186                        Err(_) => break,
187                    },
188                    None => break,
189                },
190
191                "nodes" => {}
192
193                "mate" => {}
194
195                "movetime" => match args.next() {
196                    Some(arg) => match arg.parse::<u64>() {
197                        Ok(a) => {
198                            self.game.move_time = a * 9 / 10;
199                        }
200                        Err(_) => break,
201                    },
202                    None => break,
203                },
204
205                _ => break,
206            }
207        }
208        self.tm.set_game_time(&mut self.game);
209        self.get_move_from_engine();
210    }
211
212    /// Get best move from the engine module.
213    fn get_move_from_engine(&mut self) {
214        match self.game.find_move() {
215            Some(m) => {
216                let result_uci = util::display_uci_move(&self.game.board, m);
217                self.game.game_history.inc(&self.game.board);
218                self.game.board.play_unchecked(m);
219                let result = format!("bestmove {result_uci}");
220                info!("{} nodes examined.", self.game.node_count);
221                self.send_string(result.as_str());
222            }
223            None => error!("No valid move found"),
224        }
225    }
226
227    /// Send name and author.
228    fn send_id(&self) {
229        self.send_string("id name C4-E5 Chess");
230        self.send_string("id author Eugen Lindorfer");
231    }
232
233    /// Send `options`.
234    fn send_options(&self) {
235        self.send_string("option"); //TODO extend this
236    }
237
238    /// Send `uci ok`.
239    fn send_uci_ok(&self) {
240        self.send_string("uciok");
241    }
242
243    /// Send `readyok`.
244    fn send_ready_ok(&self) {
245        self.send_string("readyok");
246    }
247
248    /// Output and log a string.
249    fn send_string(&self, s: &str) {
250        println!("{s}");
251        info!("|   {s}");
252    }
253}
254
255impl Default for Cli {
256    fn default() -> Self {
257        Self::new()
258    }
259}