#![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}; pub struct UciEngine { source: Fi, destination: Fo, uciok: bool, id: Id, initial: Board, game: Game, } impl UciEngine { pub fn new(source: Fi, destination: Fo) -> UciEngine { UciEngine::{ source, destination, id: Id::new(), uciok: false, initial: Board::default(), game: Game::new() } } pub fn init(&mut self){ self.push(GuiCommand::Uci); // Consume commands until uciok messages while !self.uciok { self.pull(); } } pub fn new_game(&mut self) { self.initial = Board::default(); self.game = Game::new_with_board(self.initial); self.push(GuiCommand::UciNewGame); } 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() }) } pub fn terminate(&mut self, reason: &str) { self.uciok = false; info!("UCI termination: {}", reason); } pub fn name(&self) -> Option { self.id.name() } pub fn author(&self) -> Option { self.id.author() } pub fn update(&mut self, id: &Id){ if self.is_uciok() {warn!("Engine info should not be updated now (uciok)");} self.id.update(id); } pub 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); } pub fn uciok(&mut self){ self.uciok = true; } pub fn is_uciok(&self) -> bool { self.uciok } /// Parse UCI commands /// /// * id /// * name *x* /// /// this must be sent after receiving the "uci" command to identify the engine, /// e.g. "id name Shredder X.Y\n" /// * author *x* /// /// this must be sent after receiving the "uci" command to identify the engine, /// e.g. "id author Stefan MK\n" /// /// ```rust /// use chess_uci::*; /// use std::io; /// let mut engine = UciEngine::new(io::empty(), io::sink()); /// /// engine.exec("id name Baptiste"); /// assert_eq!(engine.name().expect(""), "Baptiste"); /// /// engine.exec("id author Monique Jouve"); /// assert_eq!(engine.author().expect(""), "Monique Jouve"); /// /// ``` /// /// * uciok /// /// Must be sent after the id and optional options to tell the GUI that the engine /// has sent all infos and is ready in uci mode. /// /// ```rust /// use chess_uci::*; /// use std::io; /// let mut engine = UciEngine::new(io::empty(), io::sink()); /// assert_eq!(engine.is_uciok(), false); /// /// assert_eq!(engine.is_uciok(), false); /// /// engine.exec(" uciok"); /// assert_eq!(engine.is_uciok(), true); /// ``` 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"), } } 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}"), } } 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}, } } pub 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}"), } } 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}"), } } }