From 685039d98a03fef87cdd343fa8dd078275b4c691 Mon Sep 17 00:00:00 2001 From: dece Date: Mon, 15 Jun 2020 01:27:31 +0200 Subject: [PATCH] analysis: use negamax with alpha-beta --- src/analysis.rs | 79 +++++++++++++++++++------------------------------ src/engine.rs | 5 ++++ src/main.rs | 6 +++- src/movement.rs | 10 +------ src/uci.rs | 21 +++++++++---- 5 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/analysis.rs b/src/analysis.rs index ace6913..0dddc93 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -60,9 +60,8 @@ impl Analyzer { self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves))); } - self.max_depth = 2; - let color_factor = if board::is_white(self.node.game_state.color) { 1 } else { -1 } as f32; - let (max_score, best_move) = self.negamax(&self.node, 0, color_factor); + self.max_depth = 3; + let (max_score, best_move) = self.negamax(&self.node, MIN_F32, MAX_F32, 0); if best_move.is_some() { let log_str = format!( @@ -83,24 +82,37 @@ impl Analyzer { fn negamax( &self, node: &Node, + alpha: f32, + beta: f32, depth: u32, - color_f: f32, ) -> (f32, Option) { if depth == self.max_depth { let stats = node.compute_stats(); - return (color_f * evaluate(&stats), None) + let ev = evaluate(&stats); + return (ev, None) } let moves = node.get_player_moves(true); + let mut alpha = alpha; let mut best_score = MIN_F32; let mut best_move = None; for m in moves { + self.log(format!("negamax: depth {} move {}...", depth, notation::move_to_string(&m))); let mut sub_node = node.clone(); sub_node.apply_move(&m); - let (score, _) = self.negamax(&mut sub_node, depth + 1, -color_f); - let score = -score; - if score >= best_score { + let result = self.negamax(&sub_node, -beta, -alpha, depth + 1); + let score = -result.0; + if score > best_score { best_score = score; best_move = Some(m); + self.log(format!("negamax: depth {} new best score {}.", depth, best_score)); + } + if best_score > alpha { + alpha = best_score; + self.log(format!("negamax: depth {} new alpha {}.", depth, alpha)); + } + if alpha >= beta { + self.log(format!("negamax: depth {} alpha above beta {}, cut.", depth, beta)); + break } } (best_score, best_move) @@ -116,44 +128,15 @@ fn evaluate(stats: &(stats::BoardStats, stats::BoardStats)) -> f32 { let (player_stats, opponent_stats) = stats; 200.0 * (player_stats.num_kings - opponent_stats.num_kings) as f32 - + 9.0 * (player_stats.num_queens - opponent_stats.num_queens) as f32 - + 5.0 * (player_stats.num_rooks - opponent_stats.num_rooks) as f32 - + 3.0 * (player_stats.num_bishops - opponent_stats.num_bishops) as f32 - + 3.0 * (player_stats.num_knights - opponent_stats.num_knights) as f32 - + (player_stats.num_pawns - opponent_stats.num_pawns) as f32 - - 0.5 * ( - player_stats.num_doubled_pawns - opponent_stats.num_doubled_pawns + - player_stats.num_isolated_pawns - opponent_stats.num_isolated_pawns + - player_stats.num_backward_pawns - opponent_stats.num_backward_pawns - ) as f32 - + 0.1 * (player_stats.mobility - opponent_stats.mobility) as f32 -} - -#[cfg(test)] -mod tests { - // use super::*; - - #[test] - fn test_minimax() { - // FIXME - // let mut node = Node::new(); - // node.game_state.castling = 0; - - // // White mates in 1 move, queen to d7. - // board::set_square(&mut node.board, &pos("a1"), board::SQ_WH_K); - // board::set_square(&mut node.board, &pos("c6"), board::SQ_WH_P); - // board::set_square(&mut node.board, &pos("h7"), board::SQ_WH_Q); - // board::set_square(&mut node.board, &pos("d8"), board::SQ_BL_K); - // let (_, m) = minimax(&mut node, 0, 2, true); - // assert_eq!(m.unwrap(), notation::parse_move("h7d7")); - - // // Check that it works for black as well. - // board::set_square(&mut node.board, &pos("a1"), board::SQ_BL_K); - // board::set_square(&mut node.board, &pos("c6"), board::SQ_BL_P); - // board::set_square(&mut node.board, &pos("h7"), board::SQ_BL_Q); - // board::set_square(&mut node.board, &pos("d8"), board::SQ_WH_K); - // node.game_state.color = board::SQ_BL; - // let (_, m) = minimax(&mut node, 0, 2, true); - // assert_eq!(m.unwrap(), notation::parse_move("h7d7")); - } + + 9.0 * (player_stats.num_queens - opponent_stats.num_queens) as f32 + + 5.0 * (player_stats.num_rooks - opponent_stats.num_rooks) as f32 + + 3.0 * (player_stats.num_bishops - opponent_stats.num_bishops) as f32 + + 3.0 * (player_stats.num_knights - opponent_stats.num_knights) as f32 + + (player_stats.num_pawns - opponent_stats.num_pawns) as f32 + - 0.5 * ( + player_stats.num_doubled_pawns - opponent_stats.num_doubled_pawns + + player_stats.num_isolated_pawns - opponent_stats.num_isolated_pawns + + player_stats.num_backward_pawns - opponent_stats.num_backward_pawns + ) as f32 + + 0.1 * (player_stats.mobility - opponent_stats.mobility) as f32 } diff --git a/src/engine.rs b/src/engine.rs index b703f25..6b30d69 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -104,6 +104,11 @@ impl Engine { } } + /// Enable debug output. + pub fn enable_debug(&mut self) { + self.debug = true; + } + /// Handle UCI commands passed as engine Cmds. fn handle_command(&mut self, cmd: &Cmd) { match cmd { diff --git a/src/main.rs b/src/main.rs index febdc65..d746026 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,9 @@ fn main() { .setting(AppSettings::ArgRequiredElseHelp) .subcommand(SubCommand::with_name("uci") .about("Start engine in UCI mode") + .arg(Arg::with_name("debug") + .help("Enable debug mode") + .short("d").long("debug").takes_value(false).required(false)) .arg(Arg::with_name("log_file") .help("Log file path (default is stderr)") .long("log-file").takes_value(true).required(false))) @@ -30,7 +33,8 @@ fn main() { } fn cmd_uci(args: &ArgMatches) -> i32 { + let debug = args.is_present("debug"); let output = args.value_of("log_file"); - uci::Uci::start(output); + uci::Uci::start(debug, output); 0 } diff --git a/src/movement.rs b/src/movement.rs index 59ed21d..fa8c355 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -49,7 +49,7 @@ pub fn apply_move_to( // Update board and game state. apply_move_to_board(board, m); - apply_move_to_state(game_state, m); + game_state.color = opposite(game_state.color); // If the move is a castle, remove it from castling options. if let Some(castle) = get_castle(m) { @@ -129,14 +129,6 @@ pub fn apply_move_to_board(board: &mut Board, m: &Move) { } } -/// Update `game_state` with the move `m`. -/// -/// This only updates the player turn. Castling should be updated in a -/// context where the corresponding board is available. -pub fn apply_move_to_state(game_state: &mut rules::GameState, _m: &Move) { - game_state.color = opposite(game_state.color); -} - /// Get the corresponding castling flag for this move. pub fn get_castle(m: &Move) -> Option { if m.0 == pos("e1") { diff --git a/src/uci.rs b/src/uci.rs index 1d083a1..ce12f53 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -18,10 +18,16 @@ const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); /// UCI manager with means to send/receive commands and communicate /// with the engine. pub struct Uci { - state: State, // Local UCI state for consistency. - cmd_channel: (mpsc::Sender, mpsc::Receiver), // Channel of Cmd, handled by Uci. - engine_in: Option>, // Sender for engine comms. - logfile: Option, // If some, write logs to it. + /// Local UCI state for consistency. + state: State, + /// Channel of Cmd, handled by Uci. + cmd_channel: (mpsc::Sender, mpsc::Receiver), + /// Sender for engine comms. + engine_in: Option>, + /// Debug mode, if true it will override debug mode settings for the engine. + debug: bool, + /// If some, write logs to it. + logfile: Option, } /// Internal UCI state. @@ -80,7 +86,7 @@ pub enum GoArgs { impl Uci { /// Start a new UCI listening for standard input. - pub fn start(output: Option<&str>) { + pub fn start(debug: bool, output: Option<&str>) { // Create the UCI queue, both for standard IO and for engine communication. let (uci_s, uci_r): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); let stdin_tx = uci_s.clone(); @@ -92,6 +98,7 @@ impl Uci { state: State::Init, cmd_channel: (uci_s, uci_r), engine_in: None, + debug, logfile: None, }; // Configure log output, either a file or stderr. @@ -225,9 +232,13 @@ impl Uci { /// Setup engine for UCI. fn setup_engine(&mut self) { + let debug = self.debug; let uci_s = self.cmd_channel.0.clone(); thread::spawn(move || { let mut engine = engine::Engine::new(); + if debug { + engine.enable_debug(); + } engine.setup_uci(uci_s); }); self.state = State::Ready;