diff --git a/Cargo.lock b/Cargo.lock index 243d1fc..8436c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,14 @@ 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" @@ -25,7 +33,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -47,7 +55,7 @@ name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (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)", ] @@ -60,11 +68,44 @@ 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" @@ -107,6 +148,37 @@ 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" @@ -130,6 +202,7 @@ 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)", ] @@ -138,6 +211,11 @@ 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" @@ -164,22 +242,33 @@ 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.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "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" diff --git a/Cargo.toml b/Cargo.toml index c22b3a5..3a39a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" [dependencies] clap = "2.33" +nom = "5" rand = "0.7" diff --git a/src/board.rs b/src/board.rs index 351e987..9b0d002 100644 --- a/src/board.rs +++ b/src/board.rs @@ -75,6 +75,7 @@ pub fn pos(s: &str) -> Pos { /// defining the state of the square. pub type Board = [u8; 64]; +/// Generate the board of a new game. pub fn new() -> Board { [ /* 1 2 3 4 5 6 7 8 */ @@ -89,10 +90,47 @@ pub fn new() -> Board { ] } +/// Generate an empty board. pub fn new_empty() -> Board { [SQ_E; 64] } +pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +/// Generate a board from a FEN string. +/// +/// This will only parse the first FEN field. +pub fn new_from_fen(fen: &str) -> Board { + let mut board = [SQ_E; 64]; + let mut f = 0; + let mut r = 7; + for c in fen.chars() { + match c { + 'r' => { set_square(&mut board, &(f, r), SQ_BL_R); f += 1 } + 'n' => { set_square(&mut board, &(f, r), SQ_BL_N); f += 1 } + 'b' => { set_square(&mut board, &(f, r), SQ_BL_B); f += 1 } + 'q' => { set_square(&mut board, &(f, r), SQ_BL_Q); f += 1 } + 'k' => { set_square(&mut board, &(f, r), SQ_BL_K); f += 1 } + 'p' => { set_square(&mut board, &(f, r), SQ_BL_P); f += 1 } + 'R' => { set_square(&mut board, &(f, r), SQ_WH_R); f += 1 } + 'N' => { set_square(&mut board, &(f, r), SQ_WH_N); f += 1 } + 'B' => { set_square(&mut board, &(f, r), SQ_WH_B); f += 1 } + 'Q' => { set_square(&mut board, &(f, r), SQ_WH_Q); f += 1 } + 'K' => { set_square(&mut board, &(f, r), SQ_WH_K); f += 1 } + 'P' => { set_square(&mut board, &(f, r), SQ_WH_P); f += 1 } + '/' => { f = 0; r -= 1; } + d if d.is_digit(10) => { f += d.to_digit(10).unwrap() as i8 } + _ => break, + } + } + board +} + +/// Return true of both boards are equal. +pub fn eq(b1: &Board, b2: &Board) -> bool { + b1.iter().zip(b2.iter()).all(|(a, b)| a == b) +} + #[inline] pub fn get_square(board: &Board, coords: &Pos) -> u8 { board[(coords.0 * 8 + coords.1) as usize] @@ -109,7 +147,9 @@ pub fn clear_square(board: &mut Board, coords: &Pos) { } #[inline] -pub fn is_empty(board: &Board, coords: &Pos) -> bool { get_square(board, coords) == SQ_E } +pub fn is_empty(board: &Board, coords: &Pos) -> bool { + get_square(board, coords) == SQ_E +} /// Count number of pieces on board pub fn num_pieces(board: &Board) -> u8 { @@ -192,6 +232,24 @@ mod tests { assert_eq!(pos("h8"), (7, 7)); } + #[test] + fn test_new_from_fen() { + let b1 = new(); + let b2 = new_from_fen(FEN_START); + assert!(eq(&b1, &b2)); + } + + #[test] + fn test_eq() { + let mut b1 = new(); + let b2 = new(); + assert!(eq(&b1, &b2)); + set_square(&mut b1, &pos("a1"), SQ_E); + assert!(!eq(&b1, &b2)); + set_square(&mut b1, &pos("a1"), SQ_WH_R); + assert!(eq(&b1, &b2)); + } + #[test] fn test_get_square() { let b = new(); diff --git a/src/main.rs b/src/main.rs index 7b2d237..125b8fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; pub mod board; pub mod cli; pub mod rules; +pub mod uci; fn main() { let matches = App::new("Vatu") @@ -15,10 +16,16 @@ fn main() { .help("Color for the player") .short("c").long("color").takes_value(true).required(false) .possible_values(&["w", "white", "b", "black"]))) + .subcommand(SubCommand::with_name("uci") + .about("Start engine in UCI mode") + .arg(Arg::with_name("output") + .help("Log file path") + .short("o").long("output").takes_value(true).required(false))) .get_matches(); process::exit(match matches.subcommand() { ("cli", Some(a)) => cmd_cli(a), + ("uci", Some(a)) => cmd_uci(a), _ => 0, }) } @@ -39,3 +46,9 @@ fn cmd_cli(args: &ArgMatches) -> i32 { cli::start_game(color); 0 } + +fn cmd_uci(args: &ArgMatches) -> i32 { + let output = args.value_of("output"); + uci::start(output); + 0 +} diff --git a/src/notation.rs b/src/notation.rs new file mode 100644 index 0000000..f0ea101 --- /dev/null +++ b/src/notation.rs @@ -0,0 +1,17 @@ +//! Functions using various notations. + +use nom::IResult; + +/// FEN notation for positions, split into fields. +pub struct Fen { + placement: String, + color: String, + castling: String, + en_passant: String, + halfmove: String, + fullmove: String, +} + +fn parse_fen(i: &str) -> IResult<&str, Fen> { + +} diff --git a/src/uci.rs b/src/uci.rs new file mode 100644 index 0000000..6d33258 --- /dev/null +++ b/src/uci.rs @@ -0,0 +1,146 @@ +//! UCI management. + +use std::fs; +use std::io::{self, Write}; + +use nom::IResult; +use nom::branch::alt; +use nom::character::is_space; +use nom::bytes::complete::{tag, take_while}; + +use crate::board; + +const VATU_NAME: &str = env!("CARGO_PKG_NAME"); +const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); + +/// Hold some values related to UCI comms. +pub struct Uci { + state: State, + board: board::Board, + logfile: Option, +} + +/// Internal UCI state. +#[derive(PartialEq)] +pub enum State { + Init, + Ready, +} + +/// UCI remote commands, received by engine. +#[derive(Debug)] +pub enum RemoteCmd { + Uci, + IsReady, + UciNewGame, + Stop, + Position(String), + Quit, + Unknown(String), +} + +impl Uci { + fn listen(&mut self) { + loop { + if let Some(cmd) = self.receive() { + match parse_command(&cmd) { + Ok((_, cmd)) => { + if !self.handle_command(&cmd) { + break + } + } + _ => { + self.log(format!("Unknown command: {}", cmd)) + } + } + } + } + } + + 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(); + } + None => { + eprintln!("{}", s); + } + } + } + + /// Read a command from the interface. + fn receive(&mut self) -> Option { + let mut s = String::new(); + match io::stdin().read_line(&mut s) { + Ok(_) => { self.log(format!(">>> {}", s.trim_end())); Some(s.trim().to_string()) } + Err(e) => { self.log(format!("Failed to read input: {:?}", e)); None } + } + } + + /// Send replies to the interface. + fn send(&mut self, s: &str) { + self.log(format!("<<< {}", s)); + println!("{}", s); + } + + /// Handle a remote command, return false if engine should stop listening. + fn handle_command(&mut self, cmd: &RemoteCmd) -> 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::Quit => return false, + _ => { self.log(format!("Unknown command: {:?}", cmd)); } + } + true + } + + /// Send IDs to interface. + fn identify(&mut self) { + self.send(&format!("id name {}", VATU_NAME)); + self.send(&format!("id author {}", VATU_AUTHORS)); + self.send("uciok"); + self.state = State::Ready; + } + + fn ready(&mut self) { + self.send("readyok"); + self.state = State::Ready; + } +} + +/// Start UCI I/O. +pub fn start(output: Option<&str>) { + let mut uci = Uci { + state: State::Init, + board: board::new_empty(), + 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) } + } + } + uci.listen(); +} + +fn take_non_space(i: &str) -> IResult<&str, &str> { + take_while(|c| c != ' ')(i) +} + +fn parse_command(i: &str) -> IResult<&str, RemoteCmd> { + let (i, cmd) = take_non_space(i)?; + match cmd { + "uci" => Ok((i, RemoteCmd::Uci)), + "isready" => Ok((i, RemoteCmd::IsReady)), + "ucinewgame" => Ok((i, RemoteCmd::UciNewGame)), + "stop" => Ok((i, RemoteCmd::Stop)), + "position" => Ok((i, RemoteCmd::Position(i.trim().to_string()))), + "quit" => Ok((i, RemoteCmd::Quit)), + c => Ok((i, RemoteCmd::Unknown(c.to_string()))), + } +}