uci: threading WIP
This commit is contained in:
parent
8c0a083cb0
commit
f6851f6ef7
95
Cargo.lock
generated
95
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -6,5 +6,4 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
nom = "5"
|
||||
rand = "0.7"
|
||||
|
|
127
src/engine.rs
127
src/engine.rs
|
@ -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: ¬ation::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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
261
src/uci.rs
261
src/uci.rs
|
@ -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)
|
||||
}
|
||||
|
|
Reference in a new issue