use std::{ io::{self, BufRead, Write}, str::SplitWhitespace, }; use crate::{game::Game, r#move::Move, search::search, square::Square}; #[derive(PartialEq, Eq, Debug)] pub enum Command { Uci, IsReady, UciNewGame, Position, Go, Quit, } fn parse_command(parts: &mut SplitWhitespace) -> Result { match parts.next() { Some("uci") => Ok(Command::Uci), Some("isready") => Ok(Command::IsReady), Some("ucinewgame") => Ok(Command::UciNewGame), Some("position") => Ok(Command::Position), Some("go") => Ok(Command::Go), Some("quit") => Ok(Command::Quit), _ => Err("Unrecognised command".to_string()), } } pub enum Response { UciOk, ReadyOk, BestMove(String), Info(String), } fn write_response(handle_out: &mut impl Write, response: Response) -> Result<(), String> { writeln!(handle_out, "{}", response).map_err(|e| e.to_string())?; handle_out.flush().map_err(|e| e.to_string())?; Ok(()) } use std::fmt; impl fmt::Display for Response { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::UciOk => write!(f, "id name ippos\nid author stefiosif\nuciok"), Self::ReadyOk => write!(f, "readyok"), Self::BestMove(best_move) => write!(f, "bestmove {}", best_move), Self::Info(info) => write!(f, "{}", info), } } } struct UciParameters { movetime: Option, depth: Option, game: Option, } impl UciParameters { const fn new() -> Self { Self { movetime: None, depth: None, game: None, } } fn add_movetime(&mut self, movetime: usize) { self.movetime = Some(movetime); } fn add_depth(&mut self, depth: u8) { self.depth = Some(depth); } fn add_game(&mut self, game: Game) { self.game = Some(game); } } impl Default for UciParameters { fn default() -> Self { Self::new() } } pub fn uci_position(position: &mut SplitWhitespace) -> Result { let state = position.next().ok_or("Expected startpos or fen")?; let mut game = match state { "startpos" => Game::new(), "fen" => Game::new_from_fen(position.next().ok_or("Expected fen string")?)?, _ => return Err("Expected startpos or fen".to_string()), }; if Some("moves") != position.next() { return Ok(game); } for mv_str in position { let mv = Move::parse_from_str(mv_str)?; game.board.make_move(&mv, game.board.state.next_turn()); } Ok(game) } const MAX_DEPTH: u8 = 5; pub fn uci_go(go: &mut SplitWhitespace, game: &mut Game) -> Result { let mut params = UciParameters::new(); while let Some(subcommand) = go.next() { match subcommand { "depth" => { let depth_str = go.next().ok_or("Expected depth value")?; let depth = depth_str.parse::().map_err(|_| "Invalid depth value")?; params.add_depth(depth); } "movetime" => { let movetime_str = go.next().ok_or("Expected movetime value")?; let movetime = movetime_str .parse::() .map_err(|_| "Invalid movetime value")?; params.add_movetime(movetime); } _ => (), } } search(game, params.depth.unwrap_or(MAX_DEPTH)); Ok(Move::new(Square::B8, Square::C6)) } pub fn uci_loop() -> Result<(), String> { let stdin = io::stdin(); let stdout = io::stdout(); let handle_in = stdin.lock(); let mut handle_out = stdout.lock(); let mut params = UciParameters::new(); for line in handle_in.lines() { let line_str = line.unwrap_or_else(|_| "quit".to_string()); let mut parts = line_str.split_whitespace(); let command = parse_command(&mut parts)?; let response = match command { Command::Uci => Response::UciOk, Command::IsReady => Response::ReadyOk, Command::UciNewGame => Response::Info("Clear cached game.".to_string()), Command::Position => { params.add_game(uci_position(&mut parts)?); Response::Info("Initialized position.".to_string()) } Command::Go => { if let Some(ref mut game) = params.game { let best_move = uci_go(&mut parts, game)?; Response::BestMove(best_move.parse_into_str()) } else { Response::Info("Going!".to_string()) } } Command::Quit => break, }; write_response(&mut handle_out, response)?; handle_out.flush().map_err(|e| e.to_string())?; } Ok(()) } #[cfg(test)] mod tests { use crate::{ fen::from_fen, uci::{parse_command, Command}, }; use super::uci_position; const FEN: [&str; 2] = [ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bqkbnr/pppppppp/2n4B/8/3P4/8/PPP1PPPP/RN1QKBNR b KQkq - 2 2", ]; #[test] fn test_uci_position() -> Result<(), String> { let command_position = "position startpos"; let mut parts = command_position.split_whitespace(); let command = parse_command(&mut parts)?; assert_eq!(command, Command::Position); assert_eq!(from_fen(FEN[0]).unwrap(), uci_position(&mut parts)?); let command_position_moves = "position startpos moves d2d4 b8c6 c1h6"; let mut parts = command_position_moves.split_whitespace(); let command = parse_command(&mut parts)?; assert_eq!(command, Command::Position); assert_eq!(from_fen(FEN[1]).unwrap(), uci_position(&mut parts)?); Ok(()) } #[test] fn test_uci_go() -> Result<(), String> { let mut _game = from_fen(FEN[0])?; Ok(()) } #[test] fn test_uci_loop() -> Result<(), String> { let mut _game = from_fen(FEN[0])?; Ok(()) } }