From 5d61db1fba067d6f0e4fbe6af2ef19b195b7ff36 Mon Sep 17 00:00:00 2001 From: dece Date: Wed, 3 Jun 2020 18:31:50 +0200 Subject: [PATCH] engine: handle position and go UCI commands --- src/engine.rs | 86 +++++++++++++++++++++++++++++++++++++++++---------- src/uci.rs | 85 ++++++++++++++++++-------------------------------- 2 files changed, 100 insertions(+), 71 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 9017133..0d464d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,6 +17,7 @@ pub struct Engine { halfmove: i32, // Current half moves. fullmove: i32, // Current full moves. mode: Mode, + listening: bool, } pub enum Mode { @@ -29,12 +30,12 @@ pub enum Mode { #[derive(Debug)] pub enum Cmd { // Commands that can be received by the engine. - Ping(String), // Test if the engine is responding. UciChannel(mpsc::Sender), // Provide a sender to UCI to start receiving commands. UciPosition(Vec), // UCI "position" command. + UciGo(Vec), // UCI "go" command. // Commands that can be sent by the engine. - + BestMove(board::Move), } pub const CASTLING_WH_K: u8 = 0b00000001; @@ -43,6 +44,7 @@ pub const CASTLING_BL_K: u8 = 0b00000100; pub const CASTLING_BL_Q: u8 = 0b00001000; pub const CASTLING_MASK: u8 = 0b00001111; +/// General engine implementation. impl Engine { pub fn new() -> Engine { Engine { @@ -53,40 +55,42 @@ impl Engine { halfmove: 0, fullmove: 1, mode: Mode::No, + listening: false, } } - /// Setup engine for UCI communication. - pub fn setup_uci(&mut self, uci_s: mpsc::Sender) { - // Create a channel to receive commands from Uci. - let (engine_s, engine_r) = mpsc::channel(); - uci_s.send(uci::Cmd::Engine(Cmd::UciChannel(engine_s))).unwrap(); - self.mode = Mode::Uci(uci_s, engine_r); - self.listen(); - } - /// 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 { + self.listening = true; + while self.listening { match &self.mode { - Mode::No => break, - Mode::Uci(tx, rx) => { + Mode::Uci(_, rx) => { match rx.recv() { - Ok(c) => eprintln!("Unhandled command: {:?}", c), + Ok(c) => self.handle_uci_command(&c), Err(e) => eprintln!("Engine recv failure: {}", e), } } + _ => break, } } } + fn reply(&mut self, cmd: Cmd) { + match &self.mode { + Mode::Uci(tx, _) => { + tx.send(uci::Cmd::Engine(cmd)).unwrap(); + } + _ => {} + } + } + /// 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) { + 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); @@ -145,3 +149,53 @@ impl Engine { *best_move } } + +/// UCI commands management. +impl Engine { + /// Setup engine for UCI communication. + pub fn setup_uci(&mut self, uci_s: mpsc::Sender) { + // Create a channel to receive commands from Uci. + let (engine_s, engine_r) = mpsc::channel(); + uci_s.send(uci::Cmd::Engine(Cmd::UciChannel(engine_s))).unwrap(); + self.mode = Mode::Uci(uci_s, engine_r); + self.listen(); + } + + /// Handle UCI commands passed as engine Cmds. + fn handle_uci_command(&mut self, cmd: &Cmd) { + match cmd { + Cmd::UciPosition(args) => self.uci_position(&args), + Cmd::UciGo(args) => self.uci_go(&args), + _ => eprintln!("Not an UCI command: {:?}", cmd), + } + } + + /// Update board state from a "position" command's args. + fn uci_position(&mut self, p_args: &Vec) { + for arg in p_args { + match arg { + uci::PositionArgs::Fen(fen) => { + self.apply_fen(&fen); + }, + uci::PositionArgs::Startpos => { + let fen = notation::parse_fen(notation::FEN_START).unwrap(); + self.apply_fen(&fen); + } + } + } + } + + /// Start working using parameters passed with a "go" command. + fn uci_go(&mut self, g_args: &Vec) { + let mut movetime = -1; + for arg in g_args { + match arg { + uci::GoArgs::MoveTime(ms) => movetime = *ms, + uci::GoArgs::Infinite => movetime = -1, + } + } + let best_move = self.work(movetime); + self.reply(Cmd::BestMove(best_move)); + } + +} diff --git a/src/uci.rs b/src/uci.rs index 633a772..11b0cfc 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -12,7 +12,11 @@ use crate::notation; const VATU_NAME: &str = env!("CARGO_PKG_NAME"); const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); -/// Hold some values related to UCI comms. +// ************************************ +// UCI manager + +/// UCI manager with means to send/receive commands and communicate +/// with the engine. pub struct Uci { state: State, // Local UCI state for consistency. cmd_channel: (mpsc::Sender, mpsc::Receiver), // Channel of Cmd, handled by Uci. @@ -56,7 +60,7 @@ pub enum PositionArgs { } /// Arguments for the go remote commands. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum GoArgs { MoveTime(i32), Infinite, @@ -143,6 +147,7 @@ impl Uci { eprintln!("Failed to read input: {:?}", e); } } + s.clear(); } } @@ -162,11 +167,14 @@ impl Uci { 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::Position(args) => if self.state == State::Ready { + let args = engine::Cmd::UciPosition(args.to_vec()); + self.engine_in.as_ref().unwrap().send(args).unwrap(); }, - UciCmd::Go(g) => if self.state == State::Ready { self.go(g) } + UciCmd::Go(args) => if self.state == State::Ready { + let args = engine::Cmd::UciGo(args.to_vec()); + self.engine_in.as_ref().unwrap().send(args).unwrap(); + } UciCmd::Quit => return false, UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); } } @@ -175,12 +183,13 @@ impl Uci { /// Handle an engine command. fn handle_engine_command(&mut self, cmd: &engine::Cmd) { + self.log(format!("ENG >>> {:?}", cmd)); match cmd { engine::Cmd::UciChannel(s) => { self.engine_in = Some(s.to_owned()); - // Send a ping to the engine to ensure communication. - let ping = engine::Cmd::Ping("test".to_string()); - self.engine_in.as_ref().unwrap().send(ping).unwrap(); + } + engine::Cmd::BestMove(m) => { + self.send_bestmove(m); } _ => {} } @@ -207,53 +216,20 @@ impl Uci { self.send("readyok"); } - /// 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); - } - } - } - } - - /// 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) { - + self.send(&format!("bestmove {:?}", m)); // TODO notation conversion } } +// ************************************ +// UCI command parsers /// Parse an UCI command. fn parse_command(s: &str) -> UciCmd { + if s.len() == 0 { + return UciCmd::Unknown("Empty command.".to_string()); + } let fields: Vec<&str> = s.split_whitespace().collect(); match fields[0] { "uci" => UciCmd::Uci, @@ -290,20 +266,19 @@ fn parse_position_command(fields: &[&str]) -> UciCmd { /// Parse an UCI "go" command. fn parse_go_command(fields: &[&str]) -> UciCmd { let num_fields = fields.len(); - let i = 0; + let mut i = 0; let mut subcommands = vec!(); - loop { - if i == num_fields { - break - } + while i < num_fields { match fields[i] { "movetime" => { - let ms = fields[i + 1].parse::().unwrap(); - subcommands.push(GoArgs::MoveTime(ms)) + i += 1; + let ms = fields[i].parse::().unwrap(); + subcommands.push(GoArgs::MoveTime(ms)); } "infinite" => subcommands.push(GoArgs::Infinite), f => return UciCmd::Unknown(format!("Unknown go subcommand: {}", f)), } + i += 1; } UciCmd::Go(subcommands) }