diff --git a/Cargo.toml b/Cargo.toml index 533e2ed..5c7b4cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,7 @@ -[package] -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" +[workspace] +members = [ + "chess_uci", + "chess_uci_demo" + ] +exclude = [] diff --git a/chess_uci/Cargo.toml b/chess_uci/Cargo.toml new file mode 100644 index 0000000..e77a387 --- /dev/null +++ b/chess_uci/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "chess_uci" +version = "0.1.0" +edition = "2021" + +[lib] +path="src/lib.rs" + +[dependencies] +chess = "3.2.0" +itertools = "0.10.5" +log = "0.4.17" + diff --git a/src/lib.rs b/chess_uci/src/lib.rs similarity index 63% rename from src/lib.rs rename to chess_uci/src/lib.rs index eefd09d..34563c3 100644 --- a/src/lib.rs +++ b/chess_uci/src/lib.rs @@ -9,6 +9,28 @@ 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: Fi, destination: Fo, @@ -19,6 +41,10 @@ pub struct UciEngine { } 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, destination, @@ -29,6 +55,9 @@ impl UciEngine { } } + /// Launch uci engine initialisation + /// + /// Retrieve data from uci engine (until uciok command from engine) pub fn init(&mut self){ self.push(GuiCommand::Uci); @@ -38,89 +67,73 @@ impl UciEngine { } } + /// 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() }) } - pub fn terminate(&mut self, reason: &str) { + + 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() } - pub fn update(&mut self, id: &Id){ + 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>){ + 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){ + fn uciok(&mut self){ self.uciok = true; } + /// Tell whether the uci engine has initialized pub fn is_uciok(&self) -> bool { self.uciok } - /// Parse UCI commands + /// Execute a uci command /// - /// * 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); - /// ``` + /// 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), @@ -131,6 +144,7 @@ impl UciEngine { } } + /// 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) { @@ -143,6 +157,7 @@ impl UciEngine { } } + /// 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) { @@ -152,7 +167,8 @@ impl UciEngine { } } - pub fn push(&mut self, command: GuiCommand) { + /// 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}"), @@ -161,6 +177,7 @@ impl UciEngine { } } + /// 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}"), @@ -169,3 +186,5 @@ impl UciEngine { } } } + +// LocalWords: uci diff --git a/src/uci_command.rs b/chess_uci/src/uci_command.rs similarity index 100% rename from src/uci_command.rs rename to chess_uci/src/uci_command.rs diff --git a/chess_uci_demo/Cargo.toml b/chess_uci_demo/Cargo.toml new file mode 100644 index 0000000..d2f08cf --- /dev/null +++ b/chess_uci_demo/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "chess_uci_demo" +version = "0.1.0" + +[[bin]] +name="chess" +path="src/main.rs" + +[dependencies] +chess = "3.2.0" +chess_uci = { version = "0.1.0", path = "../chess_uci" } +env_logger = "0.10.0" + diff --git a/src/main.rs b/chess_uci_demo/src/main.rs similarity index 98% rename from src/main.rs rename to chess_uci_demo/src/main.rs index 1e3a8aa..e09822d 100644 --- a/src/main.rs +++ b/chess_uci_demo/src/main.rs @@ -1,3 +1,4 @@ +extern crate chess; extern crate chess_uci; use chess::ChessMove;