diff --git a/Cargo.toml b/Cargo.toml index cd6c313..533e2ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,18 @@ name = "chess_uci" version = "0.1.0" edition = "2021" +[lib] +path="src/lib.rs" + +[[bin]] +name="chess" +path="src/main.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chess = "3.2.0" +env_logger = "0.10.0" itertools = "0.10.5" log = "0.4.17" + diff --git a/src/lib.rs b/src/lib.rs index aa84480..829d878 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,25 +3,43 @@ /// Manage UCI messages and link into chess component mod uci_command; -use log::warn; +use log::{info, warn}; use std::io::*; use crate::uci_command::*; -pub struct UciEngine { +pub struct UciEngine { source: Fi, destination: Fo, uciok: bool, id: Id, } -impl UciEngine { +impl UciEngine { pub fn new(source: Fi, destination: Fo) -> UciEngine { UciEngine::{ source, destination, - id: Id::new(), + id: Id::new(), uciok: false} } + 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) { + unimplemented!(); + } + + pub fn terminate(&mut self, reason: &str) { + self.uciok = false; + info!("UCI termination: {}", reason); + } + pub fn name(&self) -> Option { self.id.name() } @@ -34,7 +52,7 @@ impl UciEngine { 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); @@ -84,12 +102,6 @@ impl UciEngine { /// let mut engine = UciEngine::new(io::empty(), io::sink()); /// assert_eq!(engine.is_uciok(), false); /// - /// engine.exec("id name Baptiste"); - /// assert_eq!(engine.name().expect(""), "Baptiste"); - /// assert_eq!(engine.is_uciok(), false); - /// - /// engine.exec("id author Monique Jouve"); - /// assert_eq!(engine.author().expect(""), "Monique Jouve"); /// assert_eq!(engine.is_uciok(), false); /// /// engine.exec(" uciok"); @@ -99,12 +111,30 @@ impl UciEngine { 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 next(&mut self) { - exec(self.source.read_line()); + 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 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}"), + } } } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d48d2b6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +extern crate chess_uci; + +use chess_uci::*; + +use std::process::{Command, Stdio}; +use std::io::BufReader; + +pub fn main(){ + env_logger::init(); + + println!("Launching hardcoded stockfish program (should be in PATH)"); + + // launch chess program + 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(); + + println!("Engine: {} \nby: {}", + if let Some(name) = uci.name() {name} else {"Not defined".to_string()}, + if let Some(author) = uci.author() {author} else {"Not defined".to_string()}); +} diff --git a/src/uci_command.rs b/src/uci_command.rs index a2a84aa..b9e0d78 100644 --- a/src/uci_command.rs +++ b/src/uci_command.rs @@ -1,5 +1,8 @@ use itertools::join; use std::time::Duration; +use std::fmt; + +use chess::ChessMove; mod uci_command{} @@ -18,7 +21,7 @@ impl Id { if let Some(name) = name {self.name = Some(name.to_string());} if let Some(author) = author {self.author = Some(author.to_string());} } - + pub fn name(&self) -> Option { self.name.clone() } @@ -27,7 +30,6 @@ impl Id { self.author.clone() } } -pub struct Move {} pub struct Info {} pub struct Opt{} pub struct Position{} @@ -36,7 +38,7 @@ pub enum EngineCommand { Id{id: Id}, UciOk, ReadyOk, - BestMove{best_move: Move, ponder: Move}, + BestMove{best_move: ChessMove, ponder: Option}, CopyProtection, // unimplemented Registration, // unimplemented Info{infos: Vec}, @@ -50,9 +52,9 @@ pub enum GuiCommand { SetOption{option: Opt}, Register, // unimplemented UciNewGame, - Position{position: Option, moves: Vec}, + Position{position: Option, moves: Vec}, Go, - SearchMoves{moves: Vec}, + SearchMoves{moves: Vec}, Ponder, WTime{time: Duration}, BTime{time: Duration}, @@ -90,8 +92,18 @@ pub fn parse(message: &mut String) -> Result { Some("copyprotection") => unimplemented!(), Some("registration") => unimplemented!(), Some("info") => unimplemented!(), - Some("option") => unimplemented!(), + Some("option") => Ok(EngineCommand::Opt { options: Vec::new() }), // todo!("Parse options lines") Some(_) => Err("Unknown command provided"), } } +impl fmt::Display for GuiCommand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result{ + match self { + GuiCommand::Uci => write!(f, "uci\n"), + _ => unimplemented!(), + } + + } +} +