diff --git a/src/board.rs b/src/board.rs index 9631df1..29b2a65 100644 --- a/src/board.rs +++ b/src/board.rs @@ -69,6 +69,14 @@ pub fn pos(s: &str) -> Pos { ((chars[0] - 0x61) as i8, (chars[1] - 0x31) as i8) } +/// Return string coordinates from Pos. +pub fn pos_string(p: &Pos) -> String { + let mut bytes = [0u8; 2]; + bytes[0] = (p.0 + 0x61) as u8; + bytes[1] = (p.1 + 0x31) as u8; + String::from_utf8_lossy(&bytes).to_string() +} + /// Bitboard representation of a chess board. /// /// 64 squares, from A1, A2 to H7, H8. A square is an u8, with bits @@ -229,6 +237,14 @@ mod tests { assert_eq!(pos("h8"), (7, 7)); } + #[test] + fn test_pos_string() { + assert_eq!(pos_string(&(0, 0)), "a1"); + assert_eq!(pos_string(&(0, 1)), "a2"); + assert_eq!(pos_string(&(0, 7)), "a8"); + assert_eq!(pos_string(&(7, 7)), "h8"); + } + #[test] fn test_new_from_fen() { let b1 = new(); diff --git a/src/engine.rs b/src/engine.rs index 0d464d4..3f04a71 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,8 @@ //! Vatu engine. use std::sync::mpsc; +use std::thread; +use std::time; use rand::seq::IteratorRandom; @@ -35,7 +37,7 @@ pub enum Cmd { UciGo(Vec), // UCI "go" command. // Commands that can be sent by the engine. - BestMove(board::Move), + BestMove(Option), } pub const CASTLING_WH_K: u8 = 0b00000001; @@ -138,15 +140,25 @@ impl Engine { self.fullmove = fullmove.parse::().ok().unwrap(); } + fn apply_moves(&mut self, moves: &Vec) { + moves.iter().for_each(|m| self.apply_move(m)); + } + + fn apply_move(&mut self, m: &board::Move) { + board::apply_into(&mut self.board, m); + } + /// 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 { + pub fn work(&mut self, movetime: i32) -> Option { // 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 + let best_move = moves.iter().choose(&mut rng).and_then(|m| Some(*m)); + // board::draw(&self.board); + thread::sleep(time::Duration::from_millis(movetime as u64)); + best_move } } @@ -180,6 +192,9 @@ impl Engine { uci::PositionArgs::Startpos => { let fen = notation::parse_fen(notation::FEN_START).unwrap(); self.apply_fen(&fen); + }, + uci::PositionArgs::Moves(moves) => { + self.apply_moves(&moves); } } } diff --git a/src/main.rs b/src/main.rs index dd1d253..48e5a2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,7 +50,7 @@ fn cmd_cli(args: &ArgMatches) -> i32 { } fn cmd_uci(args: &ArgMatches) -> i32 { - let output = args.value_of("output"); + let output = args.value_of("log_file"); uci::Uci::start(output); 0 } diff --git a/src/notation.rs b/src/notation.rs index ac0415d..5d0906b 100644 --- a/src/notation.rs +++ b/src/notation.rs @@ -1,5 +1,20 @@ //! Functions using various notations. +use crate::board; + +pub const NULL_MOVE: &str = "0000"; + +pub fn move_to_string(m: &board::Move) -> String { + let mut move_string = String::new(); + move_string.push_str(&board::pos_string(&m.0)); + move_string.push_str(&board::pos_string(&m.1)); + move_string +} + +pub fn parse_move(m_str: &str) -> board::Move { + (board::pos(&m_str[0..2]), board::pos(&m_str[2..4])) +} + pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; /// FEN notation for positions, split into fields. @@ -36,6 +51,17 @@ pub fn parse_fen_fields(fields: &[&str]) -> Option { mod tests { use super::*; + #[test] + fn test_move_to_string() { + assert_eq!(move_to_string(&((0, 0), (3, 3))), "a1d4"); + assert_eq!(move_to_string(&((7, 7), (0, 7))), "h8a8"); + } + + #[test] + fn test_parse_move() { + assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3))); + } + #[test] fn test_parse_fen() { let fen_start = parse_fen(FEN_START).unwrap(); diff --git a/src/uci.rs b/src/uci.rs index 11b0cfc..b577029 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -57,6 +57,7 @@ pub enum UciCmd { pub enum PositionArgs { Startpos, Fen(notation::Fen), + Moves(Vec), } /// Arguments for the go remote commands. @@ -112,11 +113,9 @@ impl Uci { } fn log(&mut self, s: String) { - match self.logfile.as_ref() { - Some(mut f) => { - f.write_all(s.as_bytes()).unwrap(); - f.write_all("\n".as_bytes()).unwrap(); - f.flush().unwrap(); + match &mut self.logfile { + Some(f) => { + writeln!(f, "{}", s).unwrap(); } None => { eprintln!("{}", s); @@ -135,6 +134,7 @@ impl Uci { pub fn read_stdin(tx: mpsc::Sender) { let mut s = String::new(); loop { + s.clear(); match io::stdin().read_line(&mut s) { Ok(_) => { let s = s.trim(); @@ -147,7 +147,6 @@ impl Uci { eprintln!("Failed to read input: {:?}", e); } } - s.clear(); } } @@ -217,8 +216,12 @@ impl Uci { } /// Send best move. - fn send_bestmove(&mut self, m: &board::Move) { - self.send(&format!("bestmove {:?}", m)); // TODO notation conversion + fn send_bestmove(&mut self, m: &Option) { + let move_str = match m { + Some(m) => notation::move_to_string(m), + None => notation::NULL_MOVE.to_string(), + }; + self.send(&format!("bestmove {}", move_str)); } } @@ -245,20 +248,34 @@ fn parse_command(s: &str) -> UciCmd { /// Parse an UCI "position" command. fn parse_position_command(fields: &[&str]) -> UciCmd { - // Currently we only match the first subcommand; moves are not supported. + let num_fields = fields.len(); + let mut i = 0; 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[1..7]) { - subcommands.push(PositionArgs::Fen(fen)) - } else { - return UciCmd::Unknown(format!("Bad format for position fen")) + while i < num_fields { + match fields[i] { + // Subcommand "fen" is followed by a FEN string. + "fen" => { + if let Some(fen) = notation::parse_fen_fields(&fields[i + 1 .. i + 7]) { + subcommands.push(PositionArgs::Fen(fen)) + } else { + return UciCmd::Unknown(format!("Bad format for position fen")) + } + i += 6; } + // Subcommand "startpos" assumes the board is a new game. + "startpos" => subcommands.push(PositionArgs::Startpos), + // Subcommand "moves" is followed by moves until the end of the command. + "moves" => { + let mut moves = vec!(); + while i + 1 < num_fields { + moves.push(notation::parse_move(fields[i + 1])); + i += 1; + } + subcommands.push(PositionArgs::Moves(moves)); + }, + f => return UciCmd::Unknown(format!("Unknown position subcommand: {}", f)), } - // Subcommand "startpos" assumes the board is a new game. - "startpos" => subcommands.push(PositionArgs::Startpos), - f => return UciCmd::Unknown(format!("Unknown position subcommand: {}", f)), + i += 1; } UciCmd::Position(subcommands) }