analysis: use negamax with alpha-beta

This commit is contained in:
dece 2020-06-15 01:27:31 +02:00
parent 56e7450c7e
commit 685039d98a
5 changed files with 58 additions and 63 deletions

View file

@ -60,9 +60,8 @@ impl Analyzer {
self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves))); self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves)));
} }
self.max_depth = 2; self.max_depth = 3;
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, MIN_F32, MAX_F32, 0);
let (max_score, best_move) = self.negamax(&self.node, 0, color_factor);
if best_move.is_some() { if best_move.is_some() {
let log_str = format!( let log_str = format!(
@ -83,24 +82,37 @@ impl Analyzer {
fn negamax( fn negamax(
&self, &self,
node: &Node, node: &Node,
alpha: f32,
beta: f32,
depth: u32, depth: u32,
color_f: f32,
) -> (f32, Option<Move>) { ) -> (f32, Option<Move>) {
if depth == self.max_depth { if depth == self.max_depth {
let stats = node.compute_stats(); 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 moves = node.get_player_moves(true);
let mut alpha = alpha;
let mut best_score = MIN_F32; let mut best_score = MIN_F32;
let mut best_move = None; let mut best_move = None;
for m in moves { for m in moves {
self.log(format!("negamax: depth {} move {}...", depth, notation::move_to_string(&m)));
let mut sub_node = node.clone(); let mut sub_node = node.clone();
sub_node.apply_move(&m); sub_node.apply_move(&m);
let (score, _) = self.negamax(&mut sub_node, depth + 1, -color_f); let result = self.negamax(&sub_node, -beta, -alpha, depth + 1);
let score = -score; let score = -result.0;
if score >= best_score { if score > best_score {
best_score = score; best_score = score;
best_move = Some(m); 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) (best_score, best_move)
@ -128,32 +140,3 @@ fn evaluate(stats: &(stats::BoardStats, stats::BoardStats)) -> f32 {
) as f32 ) as f32
+ 0.1 * (player_stats.mobility - opponent_stats.mobility) 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"));
}
}

View file

@ -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. /// Handle UCI commands passed as engine Cmds.
fn handle_command(&mut self, cmd: &Cmd) { fn handle_command(&mut self, cmd: &Cmd) {
match cmd { match cmd {

View file

@ -18,6 +18,9 @@ fn main() {
.setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::ArgRequiredElseHelp)
.subcommand(SubCommand::with_name("uci") .subcommand(SubCommand::with_name("uci")
.about("Start engine in UCI mode") .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") .arg(Arg::with_name("log_file")
.help("Log file path (default is stderr)") .help("Log file path (default is stderr)")
.long("log-file").takes_value(true).required(false))) .long("log-file").takes_value(true).required(false)))
@ -30,7 +33,8 @@ fn main() {
} }
fn cmd_uci(args: &ArgMatches) -> i32 { fn cmd_uci(args: &ArgMatches) -> i32 {
let debug = args.is_present("debug");
let output = args.value_of("log_file"); let output = args.value_of("log_file");
uci::Uci::start(output); uci::Uci::start(debug, output);
0 0
} }

View file

@ -49,7 +49,7 @@ pub fn apply_move_to(
// Update board and game state. // Update board and game state.
apply_move_to_board(board, m); 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 the move is a castle, remove it from castling options.
if let Some(castle) = get_castle(m) { 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. /// Get the corresponding castling flag for this move.
pub fn get_castle(m: &Move) -> Option<u8> { pub fn get_castle(m: &Move) -> Option<u8> {
if m.0 == pos("e1") { if m.0 == pos("e1") {

View file

@ -18,10 +18,16 @@ const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
/// UCI manager with means to send/receive commands and communicate /// UCI manager with means to send/receive commands and communicate
/// with the engine. /// with the engine.
pub struct Uci { pub struct Uci {
state: State, // Local UCI state for consistency. /// Local UCI state for consistency.
cmd_channel: (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>), // Channel of Cmd, handled by Uci. state: State,
engine_in: Option<mpsc::Sender<engine::Cmd>>, // Sender for engine comms. /// Channel of Cmd, handled by Uci.
logfile: Option<fs::File>, // If some, write logs to it. cmd_channel: (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>),
/// Sender for engine comms.
engine_in: Option<mpsc::Sender<engine::Cmd>>,
/// Debug mode, if true it will override debug mode settings for the engine.
debug: bool,
/// If some, write logs to it.
logfile: Option<fs::File>,
} }
/// Internal UCI state. /// Internal UCI state.
@ -80,7 +86,7 @@ pub enum GoArgs {
impl Uci { impl Uci {
/// Start a new UCI listening for standard input. /// 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. // Create the UCI queue, both for standard IO and for engine communication.
let (uci_s, uci_r): (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>) = mpsc::channel(); let (uci_s, uci_r): (mpsc::Sender<Cmd>, mpsc::Receiver<Cmd>) = mpsc::channel();
let stdin_tx = uci_s.clone(); let stdin_tx = uci_s.clone();
@ -92,6 +98,7 @@ impl Uci {
state: State::Init, state: State::Init,
cmd_channel: (uci_s, uci_r), cmd_channel: (uci_s, uci_r),
engine_in: None, engine_in: None,
debug,
logfile: None, logfile: None,
}; };
// Configure log output, either a file or stderr. // Configure log output, either a file or stderr.
@ -225,9 +232,13 @@ impl Uci {
/// Setup engine for UCI. /// Setup engine for UCI.
fn setup_engine(&mut self) { fn setup_engine(&mut self) {
let debug = self.debug;
let uci_s = self.cmd_channel.0.clone(); let uci_s = self.cmd_channel.0.clone();
thread::spawn(move || { thread::spawn(move || {
let mut engine = engine::Engine::new(); let mut engine = engine::Engine::new();
if debug {
engine.enable_debug();
}
engine.setup_uci(uci_s); engine.setup_uci(uci_s);
}); });
self.state = State::Ready; self.state = State::Ready;