From f6851f6ef7362c440b79bea0ea5236871e79c2f0 Mon Sep 17 00:00:00 2001 From: dece Date: Mon, 1 Jun 2020 20:52:28 +0200 Subject: [PATCH] uci: threading WIP --- Cargo.lock | 95 +--------------- Cargo.toml | 1 - src/engine.rs | 129 ++++++++++++++++++++- src/main.rs | 2 +- src/notation.rs | 4 +- src/uci.rs | 291 +++++++++++++++++++++++++++++++++++------------- 6 files changed, 342 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8436c41..243d1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,14 +8,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "atty" version = "0.2.14" @@ -33,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -55,7 +47,7 @@ name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -68,44 +60,11 @@ dependencies = [ "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "lexical-core" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "memchr" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "nom" -version = "5.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lexical-core 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ppv-lite86" version = "0.2.8" @@ -148,37 +107,6 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "static_assertions" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "strsim" version = "0.8.0" @@ -202,7 +130,6 @@ name = "vatu" version = "0.1.0" dependencies = [ "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -211,11 +138,6 @@ name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -242,33 +164,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" -"checksum lexical-core 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f" "checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" "checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 3a39a55..c22b3a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,4 @@ edition = "2018" [dependencies] clap = "2.33" -nom = "5" rand = "0.7" diff --git a/src/engine.rs b/src/engine.rs index 60e8dfc..02f7802 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,24 +1,143 @@ //! Vatu engine. +use std::sync::mpsc; + +use rand::seq::IteratorRandom; + use crate::board; use crate::notation; +use crate::rules; +use crate::uci; pub struct Engine { - board: board::Board, + board: board::Board, // Board to analyse. + color: u8, // Color to analyse. + castling: u8, // Castling state. + en_passant: Option, // En passant state. + halfmove: i32, // Current half moves. + fullmove: i32, // Current full moves. + mode: Mode, } +pub enum Mode { + // No mode, sit here and do nothing. + No, + // UCI mode: listen to Cmds, send Uci::Cmd::Engine commands. + Uci(mpsc::Receiver, mpsc::Sender), +} + +#[derive(Debug)] +pub enum Cmd { + // Commands that can be received by the engine. + Ping(String), // Test if the engine is responding. + UciPosition(Vec), // UCI "position" command. + + // Commands that can be sent by the engine. + Pong(String), // Answer a Ping command with the same payload. +} + +pub const CASTLING_WH_K: u8 = 0b00000001; +pub const CASTLING_WH_Q: u8 = 0b00000010; +pub const CASTLING_BL_K: u8 = 0b00000100; +pub const CASTLING_BL_Q: u8 = 0b00001000; +pub const CASTLING_MASK: u8 = 0b00001111; + impl Engine { - pub fn new() -> Engine { + pub fn new(mode: Mode) -> Engine { Engine { board: board::new_empty(), + color: board::SQ_WH, + castling: CASTLING_MASK, + en_passant: None, + halfmove: 0, + fullmove: 1, + mode, } } - pub fn apply_fen(&mut self, fen: ¬ation::Fen) { - self.set_placement(&fen.placement); + /// Listen for incoming commands. + /// + /// In UCI mode, read incoming Cmds over the MPSC channel. + /// In no modes, stop listening immediately. + pub fn listen(&mut self) { + loop { + match self.mode { + Mode::No => break, + Mode::Uci(rx, tx) => { + self.recv_uci(rx, tx); + } + } + } } - fn set_placement(&mut self, placement: &str) { + /// Apply a FEN string to the engine state, replacing it. + /// + /// For speed purposes, it assumes values are always valid. + pub fn apply_fen(&mut self, fen: ¬ation::Fen) { + self.set_fen_placement(&fen.placement); + self.set_fen_color(&fen.color); + self.set_fen_castling(&fen.castling); + self.set_fen_en_passant(&fen.en_passant); + self.set_fen_halfmove(&fen.halfmove); + self.set_fen_fullmove(&fen.fullmove); + } + + fn set_fen_placement(&mut self, placement: &str) { self.board = board::new_from_fen(placement); } + + fn set_fen_color(&mut self, color: &str) { + match color.chars().next().unwrap() { + 'w' => self.color = board::SQ_WH, + 'b' => self.color = board::SQ_BL, + _ => {} + } + } + + fn set_fen_castling(&mut self, castling: &str) { + for c in castling.chars() { + match c { + 'K' => self.castling |= CASTLING_WH_K, + 'Q' => self.castling |= CASTLING_WH_Q, + 'k' => self.castling |= CASTLING_BL_K, + 'q' => self.castling |= CASTLING_BL_Q, + _ => {} + } + } + } + + fn set_fen_en_passant(&mut self, en_passant: &str) { + self.en_passant = match en_passant { + "-" => None, + p => Some(board::pos(p)), + }; + } + + fn set_fen_halfmove(&mut self, halfmove: &str) { + self.halfmove = halfmove.parse::().ok().unwrap(); + } + + fn set_fen_fullmove(&mut self, fullmove: &str) { + self.fullmove = fullmove.parse::().ok().unwrap(); + } + + /// Start working on board, returning the best move found. + /// + /// Stop working after `movetime` ms, or go on forever if it's -1. + pub fn work(&mut self, _movetime: i32) -> board::Move { + // Stupid engine! Return a random move. + let moves = rules::get_player_legal_moves(&self.board, self.color); + let mut rng = rand::thread_rng(); + let best_move = moves.iter().choose(&mut rng).unwrap(); + *best_move + } + + /// Receive a command from Uci. + pub fn recv_uci(&mut self, rx: mpsc::Receiver, tx: mpsc::Sender) { + match rx.recv() { + Ok(Cmd::Ping(s)) => tx.send(uci::Cmd::Engine(Cmd::Pong(s))).unwrap(), + Ok(c) => eprintln!("Unhandled command: {:?}", c), + Err(e) => eprintln!("Engine recv failure: {}", e), + } + } } diff --git a/src/main.rs b/src/main.rs index dbabd78..635d6ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,6 +51,6 @@ fn cmd_cli(args: &ArgMatches) -> i32 { fn cmd_uci(args: &ArgMatches) -> i32 { let output = args.value_of("output"); - uci::start(output); + uci::Uci::start(output); 0 } diff --git a/src/notation.rs b/src/notation.rs index 465d28f..ac0415d 100644 --- a/src/notation.rs +++ b/src/notation.rs @@ -15,10 +15,10 @@ pub struct Fen { pub fn parse_fen(i: &str) -> Option { let fields: Vec<&str> = i.split_whitespace().collect(); - parse_fen_fields(fields) + parse_fen_fields(&fields) } -pub fn parse_fen_fields(fields: Vec<&str>) -> Option { +pub fn parse_fen_fields(fields: &[&str]) -> Option { if fields.len() < 6 { return None } diff --git a/src/uci.rs b/src/uci.rs index 98a7f71..a367b41 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -2,7 +2,10 @@ use std::fs; use std::io::{self, Write}; +use std::sync::mpsc; +use std::thread; +use crate::board; use crate::engine; use crate::notation; @@ -11,9 +14,10 @@ const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); /// Hold some values related to UCI comms. pub struct Uci { - state: State, - engine: engine::Engine, - logfile: Option, + state: State, // Local UCI state for consistency. + cmd_channel: (mpsc::Sender, mpsc::Receiver), // Channel of Cmd, handled by Uci. + engine_in: Option>, // Sender for engine comms. + logfile: Option, // If some, write logs to it. } /// Internal UCI state. @@ -21,41 +25,82 @@ pub struct Uci { pub enum State { Init, Ready, + Working, } -/// UCI remote commands, received by engine. +/// Uci MPSC commands. #[derive(Debug)] -pub enum RemoteCmd { +pub enum Cmd { + Stdin(String), // String received from standard input. + Engine(engine::Cmd), // Engine responses. +} + +/// UCI commands. +#[derive(Debug)] +pub enum UciCmd { Uci, IsReady, UciNewGame, Stop, - Position(PositionArgs), + Position(Vec), + Go(Vec), Quit, Unknown(String), } /// Arguments for the position remote command. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum PositionArgs { Startpos, Fen(notation::Fen), } +/// Arguments for the go remote commands. +#[derive(Debug)] +pub enum GoArgs { + MoveTime(i32), + Infinite, +} + impl Uci { + /// Start a new UCI listening for standard input. + pub fn start(output: Option<&str>) { + // Create the UCI queue, both for standard IO and for engine communication. + let (uci_tx, uci_rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + thread::spawn(move || { + read_stdin(uci_tx); + }); + + let mut uci = Uci { + state: State::Init, + cmd_channel: (uci_tx, uci_rx), + engine_in: None, + logfile: None, + }; + // Configure log output, either a file or stderr. + if let Some(output) = output { + match fs::File::create(output) { + Ok(f) => { uci.logfile = Some(f) } + Err(e) => { eprintln!("Could not open log file: {}", e) } + } + } + + // Start listening for Cmds. + uci.listen(); + } + fn listen(&mut self) { loop { - if let Some(cmd) = self.receive() { - match parse_command(&cmd) { - Some(cmd) => { - if !self.handle_command(&cmd) { - break - } - } - None => { - self.log(format!("Unknown command: {}", cmd)) + match self.cmd_channel.1.recv() { + Ok(Cmd::Stdin(cmd)) => { + if !self.handle_command(&parse_command(&cmd)) { + break } } + Ok(Cmd::Engine(cmd)) => { + // TODO + } + Err(e) => self.log(format!("Can't read commands: {}", e)) } } } @@ -63,9 +108,9 @@ impl Uci { fn log(&mut self, s: String) { match self.logfile.as_ref() { Some(mut f) => { - f.write_all(s.as_bytes()).ok(); - f.write_all("\n".as_bytes()).ok(); - f.flush().ok(); + f.write_all(s.as_bytes()).unwrap(); + f.write_all("\n".as_bytes()).unwrap(); + f.flush().unwrap(); } None => { eprintln!("{}", s); @@ -82,92 +127,180 @@ impl Uci { } } - /// Send replies to the interface. + /// Send an UCI reply. fn send(&mut self, s: &str) { - self.log(format!("<<< {}", s)); + self.log(format!("UCI <<< {}", s)); println!("{}", s); } - /// Handle a remote command, return false if engine should stop listening. - fn handle_command(&mut self, cmd: &RemoteCmd) -> bool { + /// Handle an UCI command, return false if engine should stop listening. + fn handle_command(&mut self, cmd: &UciCmd) -> bool { match cmd { - RemoteCmd::Uci => if self.state == State::Init { self.identify(); }, - RemoteCmd::IsReady => if self.state == State::Ready { self.ready() }, - RemoteCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ }, - RemoteCmd::Stop => if self.state == State::Ready { /* Nothing to do. */ }, - RemoteCmd::Position(p) => if self.state == State::Ready { self.position(p) } - RemoteCmd::Quit => return false, - _ => { self.log(format!("Unknown command: {:?}", cmd)); } + UciCmd::Uci => if self.state == State::Init { + self.send_identities(); + self.setup_engine(); + }, + UciCmd::IsReady => if self.state == State::Ready { self.send_ready() }, + UciCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ }, + UciCmd::Stop => if self.state == State::Ready { /* Nothing to do. */ }, + UciCmd::Position(p) => if self.state == State::Ready { + let clone = engine::Cmd::UciPosition(p.to_vec()); + self.engine_in.as_ref().unwrap().send(clone).unwrap(); + }, + UciCmd::Go(g) => if self.state == State::Ready { self.go(g) } + UciCmd::Quit => return false, + UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); } + _ => {} } true } /// Send IDs to interface. - fn identify(&mut self) { + fn send_identities(&mut self) { self.send(&format!("id name {}", VATU_NAME)); self.send(&format!("id author {}", VATU_AUTHORS)); self.send("uciok"); + } + + fn setup_engine(&mut self) { + // Create the channel to send commands to the engine. + let (uci_out, engine_in): (mpsc::Sender, mpsc::Receiver) = + mpsc::channel(); + self.engine_in = Some(uci_out); + // Pass the general Uci receiver to the engine as well. + let engine_out = self.cmd_channel.0; + thread::spawn(move || { + let mut engine = engine::Engine::new(engine::Mode::Uci(engine_in, engine_out)); + engine.listen(); + }); + self.engine_in.as_ref().unwrap().send(engine::Cmd::Ping("test".to_string())); self.state = State::Ready; } /// Notify interface that it is ready. - fn ready(&mut self) { + fn send_ready(&mut self) { self.send("readyok"); - self.state = State::Ready; } - fn position(&mut self, p_args: &PositionArgs) { - match p_args { - PositionArgs::Fen(fen) => { - self.engine.apply_fen(fen); - }, - PositionArgs::Startpos => { - let fen = notation::parse_fen(notation::FEN_START).unwrap(); - self.engine.apply_fen(&fen); + /// Set new positions. + fn position(&mut self, p_args: &Vec) { + for arg in p_args { + match arg { + PositionArgs::Fen(fen) => { + // self.engine_in.unwrap().send(engine::Cmd::Uci(fen)); + // self.engine.apply_fen(&fen); + }, + PositionArgs::Startpos => { + let fen = notation::parse_fen(notation::FEN_START).unwrap(); + // self.engine.apply_fen(&fen); + } } - }; - } -} - -/// Create a new Uci object, ready for I/O. -pub fn start(output: Option<&str>) { - let mut uci = Uci { - state: State::Init, - engine: engine::Engine::new(), - logfile: None - }; - if let Some(output) = output { - match fs::File::create(output) { - Ok(f) => { uci.logfile = Some(f) } - Err(e) => { eprintln!("Could not open log file: {}", e) } } } - uci.listen(); + + /// Go! + fn go(&mut self, g_args: &Vec) { + let mut movetime = -1; + for arg in g_args { + match arg { + GoArgs::MoveTime(ms) => movetime = *ms, + GoArgs::Infinite => movetime = -1, + } + } + // let channel: (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); + // self.state = State::Working; + // { + // let tx = channel.0.clone(); + // let mut engine = self.engine.to_owned(); + // let work_t = thread::spawn(move || { + // let best_move = engine.work(movetime); + // tx.send(best_move).ok(); + // // self.send_bestmove(&best_move); + // }); + // } + } + + /// Send best move. + fn send_bestmove(&mut self, m: &board::Move) { + + } } -fn parse_command(s: &str) -> Option { +/// Read lines over stdin, notifying over an MPSC channel. +/// +/// As it is not trivial to add a timeout, or overly complicated to +/// break the loop with a second channel, simply stop listening when +/// the UCI "quit" command is received. +pub fn read_stdin(tx: mpsc::Sender) { + let mut s = String::new(); + loop { + match io::stdin().read_line(&mut s) { + Ok(_) => { + let s = s.trim(); + tx.send(Cmd::Stdin(s.to_string())); + if s == "quit" { + break; + } + } + Err(e) => { + eprintln!("Failed to read input: {:?}", e); + } + } + } +} + +/// Parse an UCI command. +fn parse_command(s: &str) -> UciCmd { let fields: Vec<&str> = s.split_whitespace().collect(); match fields[0] { - "uci" => Some(RemoteCmd::Uci), - "isready" => Some(RemoteCmd::IsReady), - "ucinewgame" => Some(RemoteCmd::UciNewGame), - "stop" => Some(RemoteCmd::Stop), - "position" => { - match fields[1] { - // Subcommand "fen" is followed by a FEN string. - "fen" => { - if let Some(fen) = notation::parse_fen_fields(fields[2..8].to_vec()) { - Some(RemoteCmd::Position(PositionArgs::Fen(fen))) - } else { - None - } - } - // Subcommand "startpos" assumes the board is a new game. - "startpos" => Some(RemoteCmd::Position(PositionArgs::Startpos)), - _ => None - } - } - "quit" => Some(RemoteCmd::Quit), - c => Some(RemoteCmd::Unknown(c.to_string())), + "uci" => UciCmd::Uci, + "isready" => UciCmd::IsReady, + "ucinewgame" => UciCmd::UciNewGame, + "stop" => UciCmd::Stop, + "position" => parse_position_command(&fields[1..]), + "go" => parse_go_command(&fields[1..]), + "quit" => UciCmd::Quit, + c => UciCmd::Unknown(c.to_string()), } } + +/// Parse an UCI "position" command. +fn parse_position_command(fields: &[&str]) -> UciCmd { + // Currently we only match the first subcommand; moves are not supported. + let mut subcommands = vec!(); + match fields[0] { + // Subcommand "fen" is followed by a FEN string. + "fen" => { + if let Some(fen) = notation::parse_fen_fields(&fields[1..7]) { + subcommands.push(PositionArgs::Fen(fen)) + } else { + return UciCmd::Unknown(format!("Bad format for position fen")) + } + } + // Subcommand "startpos" assumes the board is a new game. + "startpos" => subcommands.push(PositionArgs::Startpos), + f => return UciCmd::Unknown(format!("Unknown position subcommand: {}", f)), + } + UciCmd::Position(subcommands) +} + +/// Parse an UCI "go" command. +fn parse_go_command(fields: &[&str]) -> UciCmd { + let num_fields = fields.len(); + let i = 0; + let mut subcommands = vec!(); + loop { + if i == num_fields { + break + } + match fields[i] { + "movetime" => { + let ms = fields[i + 1].parse::().unwrap(); + subcommands.push(GoArgs::MoveTime(ms)) + } + "infinite" => subcommands.push(GoArgs::Infinite), + f => return UciCmd::Unknown(format!("Unknown go subcommand: {}", f)), + } + } + UciCmd::Go(subcommands) +}