From c5267d0e2b9677dd77c6691910b197a596c7ba51 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Wed, 14 Aug 2024 00:25:15 +0300 Subject: [PATCH] Fix uci_position bugs and add test --- src/uci.rs | 71 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/src/uci.rs b/src/uci.rs index ae1daed..8c0b32b 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -3,11 +3,13 @@ use std::{ str::SplitWhitespace, }; -use crate::{game::Game, r#move::Move, search::search}; +use crate::{game::Game, r#move::Move, search::search, square::Square}; +#[derive(PartialEq, Eq, Debug)] pub enum Command { Uci, IsReady, + UciNewGame, Position, Go, Quit, @@ -17,6 +19,7 @@ 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), @@ -32,14 +35,7 @@ pub enum Response { } fn write_response(handle_out: &mut impl Write, response: Response) -> Result<(), String> { - let output = match response { - Response::UciOk => "uciok".to_string(), - Response::ReadyOk => "readyok".to_string(), - Response::BestMove(best_move) => format!("bestmove {}", best_move), - Response::Info(info) => info, - }; - - writeln!(handle_out, "{}", output).map_err(|e| e.to_string())?; + writeln!(handle_out, "{}", response).map_err(|e| e.to_string())?; handle_out.flush().map_err(|e| e.to_string())?; Ok(()) } @@ -49,7 +45,7 @@ use std::fmt; impl fmt::Display for Response { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::UciOk => write!(f, "uciok"), + 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), @@ -91,7 +87,7 @@ impl Default for UciParameters { } } -pub fn uci_position(mut position: SplitWhitespace) -> Result { +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(), @@ -99,6 +95,10 @@ pub fn uci_position(mut position: SplitWhitespace) -> Result { _ => 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()); @@ -109,18 +109,28 @@ pub fn uci_position(mut position: SplitWhitespace) -> Result { const MAX_DEPTH: u8 = 5; -pub fn uci_go(mut go: SplitWhitespace, game: &mut Game) -> Result { +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" => params.add_depth(go.next().unwrap().parse::().ok().unwrap()), - "movetime" => params.add_movetime(go.next().unwrap().parse::().ok().unwrap()), + "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(0, 0)) + Ok(Move::new(Square::B8, Square::C6)) } pub fn uci_loop() -> Result<(), String> { @@ -137,13 +147,14 @@ pub fn uci_loop() -> Result<(), String> { 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(parts)?); + 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(parts, game)?; + let best_move = uci_go(&mut parts, game)?; Response::BestMove(best_move.parse_into_str()) } else { Response::Info("Going!".to_string()) @@ -161,13 +172,33 @@ pub fn uci_loop() -> Result<(), String> { #[cfg(test)] mod tests { - use crate::fen::from_fen; + use crate::{ + fen::from_fen, + uci::{parse_command, Command}, + }; - const FEN: [&str; 1] = ["r3k2r/2p1p1qp/2npb3/1p3p2/p3P1pP/P1PB1Q2/1P1PNPP1/R3K2R w KQkq - 0 1"]; + 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 mut _game = from_fen(FEN[0])?; + 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(()) }