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.
|
halfmove: i32, // Current half moves.
|
||||||
fullmove: i32, // Current full moves.
|
fullmove: i32, // Current full moves.
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
listening: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
@ -29,12 +30,12 @@ pub enum Mode {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
// Commands that can be received by the engine.
|
// 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.
|
UciChannel(mpsc::Sender<Cmd>), // Provide a sender to UCI to start receiving commands.
|
||||||
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
|
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
|
||||||
|
UciGo(Vec<uci::GoArgs>), // UCI "go" command.
|
||||||
|
|
||||||
// Commands that can be sent by the engine.
|
// Commands that can be sent by the engine.
|
||||||
|
BestMove(board::Move),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CASTLING_WH_K: u8 = 0b00000001;
|
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_BL_Q: u8 = 0b00001000;
|
||||||
pub const CASTLING_MASK: u8 = 0b00001111;
|
pub const CASTLING_MASK: u8 = 0b00001111;
|
||||||
|
|
||||||
|
/// General engine implementation.
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn new() -> Engine {
|
pub fn new() -> Engine {
|
||||||
Engine {
|
Engine {
|
||||||
|
@ -53,40 +55,42 @@ impl Engine {
|
||||||
halfmove: 0,
|
halfmove: 0,
|
||||||
fullmove: 1,
|
fullmove: 1,
|
||||||
mode: Mode::No,
|
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.
|
/// Listen for incoming commands.
|
||||||
///
|
///
|
||||||
/// In UCI mode, read incoming Cmds over the MPSC channel.
|
/// In UCI mode, read incoming Cmds over the MPSC channel.
|
||||||
/// In no modes, stop listening immediately.
|
/// In no modes, stop listening immediately.
|
||||||
pub fn listen(&mut self) {
|
pub fn listen(&mut self) {
|
||||||
loop {
|
self.listening = true;
|
||||||
|
while self.listening {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::No => break,
|
Mode::Uci(_, rx) => {
|
||||||
Mode::Uci(tx, rx) => {
|
|
||||||
match rx.recv() {
|
match rx.recv() {
|
||||||
Ok(c) => eprintln!("Unhandled command: {:?}", c),
|
Ok(c) => self.handle_uci_command(&c),
|
||||||
Err(e) => eprintln!("Engine recv failure: {}", e),
|
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.
|
/// Apply a FEN string to the engine state, replacing it.
|
||||||
///
|
///
|
||||||
/// For speed purposes, it assumes values are always valid.
|
/// 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_placement(&fen.placement);
|
||||||
self.set_fen_color(&fen.color);
|
self.set_fen_color(&fen.color);
|
||||||
self.set_fen_castling(&fen.castling);
|
self.set_fen_castling(&fen.castling);
|
||||||
|
@ -145,3 +149,53 @@ impl Engine {
|
||||||
*best_move
|
*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_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
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 {
|
pub struct Uci {
|
||||||
state: State, // Local UCI state for consistency.
|
state: State, // Local UCI state for consistency.
|
||||||
cmd_channel: (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>), // Channel of Cmd, handled by Uci.
|
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.
|
/// Arguments for the go remote commands.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum GoArgs {
|
pub enum GoArgs {
|
||||||
MoveTime(i32),
|
MoveTime(i32),
|
||||||
Infinite,
|
Infinite,
|
||||||
|
@ -143,6 +147,7 @@ impl Uci {
|
||||||
eprintln!("Failed to read input: {:?}", e);
|
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::IsReady => if self.state == State::Ready { self.send_ready() },
|
||||||
UciCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ },
|
UciCmd::UciNewGame => if self.state == State::Ready { /* Nothing to do. */ },
|
||||||
UciCmd::Stop => 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 {
|
UciCmd::Position(args) => if self.state == State::Ready {
|
||||||
let clone = engine::Cmd::UciPosition(p.to_vec());
|
let args = engine::Cmd::UciPosition(args.to_vec());
|
||||||
self.engine_in.as_ref().unwrap().send(clone).unwrap();
|
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::Quit => return false,
|
||||||
UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); }
|
UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); }
|
||||||
}
|
}
|
||||||
|
@ -175,12 +183,13 @@ impl Uci {
|
||||||
|
|
||||||
/// Handle an engine command.
|
/// Handle an engine command.
|
||||||
fn handle_engine_command(&mut self, cmd: &engine::Cmd) {
|
fn handle_engine_command(&mut self, cmd: &engine::Cmd) {
|
||||||
|
self.log(format!("ENG >>> {:?}", cmd));
|
||||||
match cmd {
|
match cmd {
|
||||||
engine::Cmd::UciChannel(s) => {
|
engine::Cmd::UciChannel(s) => {
|
||||||
self.engine_in = Some(s.to_owned());
|
self.engine_in = Some(s.to_owned());
|
||||||
// Send a ping to the engine to ensure communication.
|
}
|
||||||
let ping = engine::Cmd::Ping("test".to_string());
|
engine::Cmd::BestMove(m) => {
|
||||||
self.engine_in.as_ref().unwrap().send(ping).unwrap();
|
self.send_bestmove(m);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -207,53 +216,20 @@ impl Uci {
|
||||||
self.send("readyok");
|
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.
|
/// Send best move.
|
||||||
fn send_bestmove(&mut self, m: &board::Move) {
|
fn send_bestmove(&mut self, m: &board::Move) {
|
||||||
|
self.send(&format!("bestmove {:?}", m)); // TODO notation conversion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ************************************
|
||||||
|
// UCI command parsers
|
||||||
|
|
||||||
/// Parse an UCI command.
|
/// Parse an UCI command.
|
||||||
fn parse_command(s: &str) -> UciCmd {
|
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();
|
let fields: Vec<&str> = s.split_whitespace().collect();
|
||||||
match fields[0] {
|
match fields[0] {
|
||||||
"uci" => UciCmd::Uci,
|
"uci" => UciCmd::Uci,
|
||||||
|
@ -290,20 +266,19 @@ fn parse_position_command(fields: &[&str]) -> UciCmd {
|
||||||
/// Parse an UCI "go" command.
|
/// Parse an UCI "go" command.
|
||||||
fn parse_go_command(fields: &[&str]) -> UciCmd {
|
fn parse_go_command(fields: &[&str]) -> UciCmd {
|
||||||
let num_fields = fields.len();
|
let num_fields = fields.len();
|
||||||
let i = 0;
|
let mut i = 0;
|
||||||
let mut subcommands = vec!();
|
let mut subcommands = vec!();
|
||||||
loop {
|
while i < num_fields {
|
||||||
if i == num_fields {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
match fields[i] {
|
match fields[i] {
|
||||||
"movetime" => {
|
"movetime" => {
|
||||||
let ms = fields[i + 1].parse::<i32>().unwrap();
|
i += 1;
|
||||||
subcommands.push(GoArgs::MoveTime(ms))
|
let ms = fields[i].parse::<i32>().unwrap();
|
||||||
|
subcommands.push(GoArgs::MoveTime(ms));
|
||||||
}
|
}
|
||||||
"infinite" => subcommands.push(GoArgs::Infinite),
|
"infinite" => subcommands.push(GoArgs::Infinite),
|
||||||
f => return UciCmd::Unknown(format!("Unknown go subcommand: {}", f)),
|
f => return UciCmd::Unknown(format!("Unknown go subcommand: {}", f)),
|
||||||
}
|
}
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
UciCmd::Go(subcommands)
|
UciCmd::Go(subcommands)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue