uci: threading WIP

This commit is contained in:
dece 2020-06-01 20:52:28 +02:00
parent 8c0a083cb0
commit f6851f6ef7
6 changed files with 342 additions and 180 deletions

95
Cargo.lock generated
View file

@ -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"

View file

@ -6,5 +6,4 @@ edition = "2018"
[dependencies]
clap = "2.33"
nom = "5"
rand = "0.7"

View file

@ -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<board::Pos>, // 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<Cmd>, mpsc::Sender<uci::Cmd>),
}
#[derive(Debug)]
pub enum Cmd {
// Commands that can be received by the engine.
Ping(String), // Test if the engine is responding.
UciPosition(Vec<uci::PositionArgs>), // 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,
}
}
/// 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);
}
}
}
}
/// 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) {
self.set_placement(&fen.placement);
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_placement(&mut self, placement: &str) {
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::<i32>().ok().unwrap();
}
fn set_fen_fullmove(&mut self, fullmove: &str) {
self.fullmove = fullmove.parse::<i32>().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<Cmd>, tx: mpsc::Sender<uci::Cmd>) {
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),
}
}
}

View file

@ -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
}

View file

@ -15,10 +15,10 @@ pub struct Fen {
pub fn parse_fen(i: &str) -> Option<Fen> {
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<Fen> {
pub fn parse_fen_fields(fields: &[&str]) -> Option<Fen> {
if fields.len() < 6 {
return None
}

View file

@ -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<fs::File>,
state: State, // Local UCI state for consistency.
cmd_channel: (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>), // Channel of Cmd, handled by Uci.
engine_in: Option<mpsc::Sender<engine::Cmd>>, // Sender for engine comms.
logfile: Option<fs::File>, // 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<PositionArgs>),
Go(Vec<GoArgs>),
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<Cmd>, mpsc::Receiver<Cmd>) = 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) {
match self.cmd_channel.1.recv() {
Ok(Cmd::Stdin(cmd)) => {
if !self.handle_command(&parse_command(&cmd)) {
break
}
}
None => {
self.log(format!("Unknown command: {}", cmd))
}
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<engine::Cmd>, mpsc::Receiver<engine::Cmd>) =
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 {
/// Set new positions.
fn position(&mut self, p_args: &Vec<PositionArgs>) {
for arg in p_args {
match arg {
PositionArgs::Fen(fen) => {
self.engine.apply_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);
// 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) {
}
}
/// 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) }
/// 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<Cmd>) {
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);
}
}
}
uci.listen();
}
fn parse_command(s: &str) -> Option<RemoteCmd> {
/// 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] {
"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[2..8].to_vec()) {
Some(RemoteCmd::Position(PositionArgs::Fen(fen)))
if let Some(fen) = notation::parse_fen_fields(&fields[1..7]) {
subcommands.push(PositionArgs::Fen(fen))
} else {
None
return UciCmd::Unknown(format!("Bad format for position fen"))
}
}
// 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())),
"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::<i32>().unwrap();
subcommands.push(GoArgs::MoveTime(ms))
}
"infinite" => subcommands.push(GoArgs::Infinite),
f => return UciCmd::Unknown(format!("Unknown go subcommand: {}", f)),
}
}
UciCmd::Go(subcommands)
}