#![allow(dead_code)] /// Manage UCI messages and link into chess component mod uci_command; use log::{info, warn}; use std::io::*; use crate::uci_command::*; use chess::{Game, Board, ChessMove}; /// Structure used to manage a chess game with a uci engine. /// /// It needs a in / out communication channel to read / push line by line uci commands ///c /// ```rust /// use chess_uci::UciEngine; /// use std::process::{Command, Stdio}; /// use std::io::BufReader; /// /// let process = match Command::new("stockfish") /// .stdin(Stdio::piped()) /// .stdout(Stdio::piped()) /// .spawn() { /// Err(why) => panic!("couldn't spawn stockfish: {}", why), /// Ok(process) => process, /// }; /// let (sf_in,sf_out) = (process.stdin.expect("Program stdin"), process.stdout.expect("Program stdout")); /// /// let mut uci = UciEngine::new(BufReader::new(sf_out), sf_in); /// // .. uci.init(); .. /// uci.push_raw("quit\n"); /// ``` pub struct UciEngine { source: BufReader, destination: Fo, uciok: bool, id: Id, initial: Board, game: Game, } impl UciEngine { /// Create new game manager /// /// Requires line by line input and output streams to communicate with uci engine pub fn new(source: Fi, destination: Fo) -> UciEngine { UciEngine::{ source: BufReader::new(source), destination, id: Id::new(), uciok: false, initial: Board::default(), game: Game::new() } } /// Launch uci engine initialisation /// /// Retrieve data from uci engine (until uciok command from engine) pub fn init(&mut self){ self.push(GuiCommand::Uci); // Consume commands until uciok messages while !self.uciok { self.pull(); } } /// Initialize a new game /// /// Set a new board internally and tell the engine pub fn new_game(&mut self) { self.initial = Board::default(); self.game = Game::new_with_board(self.initial); self.push(GuiCommand::UciNewGame); } /// Play a move /// /// Update internal game reference and tell the uci engine /// /// ```rust /// use chess::ChessMove; /// use std::str::FromStr; /// use chess_uci::*; /// use std::io; /// /// let mut uci = UciEngine::new(io::empty(), io::sink()); /// /// uci.make_move(ChessMove::from_str("e2e4").expect("error converting e2e4")); /// ``` pub fn make_move(&mut self, chess_move: ChessMove) { self.game.make_move(chess_move); self.push(GuiCommand::Position { position: Some(self.initial), moves: self.game.actions().to_vec() }) } fn terminate(&mut self, reason: &str) { self.uciok = false; info!("UCI termination: {}", reason); } /// Provides the name of the uci engine pub fn name(&self) -> Option { self.id.name() } /// Provides the author sof the uci engine pub fn author(&self) -> Option { self.id.author() } fn update(&mut self, id: &Id){ if self.is_uciok() {warn!("Engine info should not be updated now (uciok)");} self.id.update(id); } fn update_from_str(&mut self, name: Option<&str>, author: Option<&str>){ if self.is_uciok() {warn!("Engine info should not be updated now (uciok)");} self.id.update_from_str(name, author); } fn uciok(&mut self){ self.uciok = true; } /// Tell whether the uci engine has initialized pub fn is_uciok(&self) -> bool { self.uciok } /// Execute a uci command /// /// if `EngineCommand::Id`: update name or authorship of the engine /// if `EngineCommand::UciOk`: end engine initialization phase pub fn exec(&mut self, command: &str){ match parse (&mut command.to_string()) { Ok(EngineCommand::Id{id}) => self.update(&id), Ok(EngineCommand::UciOk) => self.uciok(), Ok(EngineCommand::Opt { options: _ }) => {}, Ok(_) => {unimplemented!("command not implemented")}, Err(_) => warn!("Not a command"), } } /// Retrieve a line from the uci engine input stream and parse it pub fn pull(&mut self) { let mut command = String::new(); match self.source.read_line(&mut command) { Ok(0) => self.terminate("Chess engine closed connection."), Ok(_) => { info!("← {}", command); self.exec(&command) }, Err(reason) => warn!("Unable to read from engine: {reason}"), } } /// Read a line from the uci engine input stream (do not parse) pub fn pull_raw(&mut self) -> Option { let mut data = String::new(); match self.source.read_line(&mut data) { Ok(0) => {self.terminate("Chess engine closed connection."); None}, Ok(_) => {info!("↜ {}", data); Some(data.clone())}, Err(reason) => {warn!("Unable to read from engine: {reason}"); None}, } } /// Push a Uci Gui command to the engine fn push(&mut self, command: GuiCommand) { let command_str = command.to_string(); match self.destination.write(&command_str.as_bytes()){ Ok(n) if n == command_str.len() => info!("→ gui: {command_str}"), Ok(n) => warn!("⚠ gui: {command_str} truncated at {n}"), Err(reason) => warn!("Unable to send command {command_str}: {reason}"), } } /// Push string (not a game manager integrated command) to the Uci engine output stream pub fn push_raw(&mut self, data: &str){ match self.destination.write(data.as_bytes()) { Ok(n) if n == data.len() => info!("↝ raw: {data}"), Ok(n) => warn!("⚠ raw: {data} truncated at {n}"), Err(reason) => warn!("Unable to send raw {data}: {reason}"), } } } // LocalWords: uci