engine: handle position and go UCI commands
This commit is contained in:
parent
ee06bfef0b
commit
5d61db1fba
|
@ -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: ¬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<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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
85
src/uci.rs
85
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<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)
|
||||
}
|
||||
|
|
Reference in a new issue