engine: handle position and go UCI commands

This commit is contained in:
dece 2020-06-03 18:31:50 +02:00
parent ee06bfef0b
commit 5d61db1fba
2 changed files with 100 additions and 71 deletions

View file

@ -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<Cmd>), // Provide a sender to UCI to start receiving commands.
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
UciGo(Vec<uci::GoArgs>), // 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<uci::Cmd>) {
// 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: &notation::Fen) {
fn apply_fen(&mut self, fen: &notation::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<uci::Cmd>) {
// 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<uci::PositionArgs>) {
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<uci::GoArgs>) {
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));
}
}

View file

@ -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<Cmd>, mpsc::Receiver<Cmd>), // 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<PositionArgs>) {
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<GoArgs>) {
let mut movetime = -1;
for arg in g_args {
match arg {
GoArgs::MoveTime(ms) => movetime = *ms,
GoArgs::Infinite => movetime = -1,
}
}
// let channel: (mpsc::Sender<board::Move>, mpsc::Receiver<board::Move>) = 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::<i32>().unwrap();
subcommands.push(GoArgs::MoveTime(ms))
i += 1;
let ms = fields[i].parse::<i32>().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)
}