From a426e2f777b9ab5c978f05110ff21eb5b8604154 Mon Sep 17 00:00:00 2001 From: dece Date: Mon, 15 Jun 2020 02:33:08 +0200 Subject: [PATCH] analysis: provide "nodes" and "nps" UCI info --- src/analysis.rs | 63 ++++++++++++++++++++++++++++++++++++++++--------- src/engine.rs | 35 ++++++++++++++------------- src/uci.rs | 11 +++++++-- 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/analysis.rs b/src/analysis.rs index 0dddc93..d6ff859 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -1,8 +1,8 @@ //! Analysis functions. use std::sync::{Arc, atomic, mpsc}; +use std::time::Instant; -use crate::board; use crate::engine; use crate::movement::Move; use crate::node::Node; @@ -19,6 +19,9 @@ pub struct Analyzer { node: Node, engine_tx: mpsc::Sender, max_depth: u32, + nps_time: Instant, + num_nodes: u64, + num_nodes_in_second: u64, } /// Analysis parameters. @@ -31,16 +34,40 @@ pub struct AnalysisParams { pub black_inc: i32, } +/// Analysis info to report. +#[derive(Debug, Clone)] +pub enum AnalysisInfo { + Nodes(u64), + Nps(u64), + CurrentMove(Move), +} + impl Analyzer { /// Create a new worker to analyze from `node`. pub fn new(node: Node, engine_tx: mpsc::Sender) -> Analyzer { - Analyzer { debug: false, node, engine_tx, max_depth: 1 } + Analyzer { + debug: false, + node, + engine_tx, + max_depth: 1, + nps_time: Instant::now(), + num_nodes: 0, + num_nodes_in_second: 0, + } } fn log(&self, message: String) { self.engine_tx.send(engine::Cmd::Log(message)).unwrap(); } + fn report_info(&self, infos: Vec) { + self.engine_tx.send(engine::Cmd::WorkerInfo(infos)).unwrap(); + } + + fn report_best_move(&self, m: Option) { + self.engine_tx.send(engine::Cmd::WorkerBestMove(m)).unwrap(); + } + /// Analyse best moves for the node. /// /// - `args`: parameters provided for this analysis. @@ -60,8 +87,9 @@ impl Analyzer { self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves))); } - self.max_depth = 3; - let (max_score, best_move) = self.negamax(&self.node, MIN_F32, MAX_F32, 0); + self.nps_time = Instant::now(); + self.max_depth = 4; + let (max_score, best_move) = self.negamax(&self.node.clone(), MIN_F32, MAX_F32, 0); if best_move.is_some() { let log_str = format!( @@ -69,34 +97,50 @@ impl Analyzer { notation::move_to_string(&best_move.unwrap()), max_score ); self.log(log_str); - self.engine_tx.send(engine::Cmd::TmpBestMove(best_move)).unwrap(); + self.report_best_move(best_move); } else { // If no best move could be found, checkmate is unavoidable; send the first legal move. self.log("Checkmate is unavoidable.".to_string()); let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true); let m = if moves.len() > 0 { Some(moves[0]) } else { None }; - self.engine_tx.send(engine::Cmd::TmpBestMove(m)).unwrap(); + self.report_best_move(m); } } fn negamax( - &self, + &mut self, node: &Node, alpha: f32, beta: f32, depth: u32, ) -> (f32, Option) { + // Increment number of nodes for stats. + self.num_nodes += 1; + self.num_nodes_in_second += 1; + + // If we reached max depth, evaluate the node and stop searching. if depth == self.max_depth { let stats = node.compute_stats(); let ev = evaluate(&stats); return (ev, None) } + + // Here's a good time to get some stats! + if self.nps_time.elapsed().as_millis() >= 1000 { + self.report_info(vec![ + AnalysisInfo::Nodes(self.num_nodes), + AnalysisInfo::Nps(self.num_nodes_in_second), + ]); + self.num_nodes_in_second = 0; + self.nps_time = Instant::now(); + } + + // Get negamax for playable moves. 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 result = self.negamax(&sub_node, -beta, -alpha, depth + 1); @@ -104,14 +148,11 @@ impl Analyzer { 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 } } diff --git a/src/engine.rs b/src/engine.rs index 6b30d69..063303c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -47,30 +47,33 @@ enum Mode { #[derive(Debug)] pub enum Cmd { // Commands that can be received by the engine. - UciChannel(mpsc::Sender), // Provide a sender to UCI to start receiving commands. - UciDebug(bool), // UCI "debug" command. - UciPosition(Vec), // UCI "position" command. - UciGo(Vec), // UCI "go" command. - Stop, // Stop working ASAP. - TmpBestMove(Option), // Send best move found by analysis worker (TEMPORARY). - WorkerInfo(Vec), // Informations from a worker. + + /// Provide a sender to UCI to start receiving commands. + UciChannel(mpsc::Sender), + /// UCI "debug" command. + UciDebug(bool), + /// UCI "position" command. + UciPosition(Vec), + /// UCI "go" command. + UciGo(Vec), + /// Stop working ASAP. + Stop, + /// Informations from a worker. + WorkerInfo(Vec), + /// Send best move found by analysis worker. + WorkerBestMove(Option), // Commands that can be sent by the engine. + /// Ask for a string to be logged or printed. /// /// Note that workers can send this command to engine, expecting /// the message to be forwarded to whatever can log. Log(String), + /// Report ongoing analysis information. + Info(Vec), /// Report found best move. BestMove(Option), - /// Report ongoing analysis information. - Info(Vec), -} - -/// Information to be transmitted back to whatever is listening. -#[derive(Debug, Clone)] -pub enum Info { - CurrentMove(Move), } /// General engine implementation. @@ -120,7 +123,7 @@ impl Engine { // Workers commands. Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())), Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())), - Cmd::TmpBestMove(m) => self.reply(Cmd::BestMove(*m)), + Cmd::WorkerBestMove(m) => self.reply(Cmd::BestMove(*m)), _ => eprintln!("Not an engine input command: {:?}", cmd), } } diff --git a/src/uci.rs b/src/uci.rs index ce12f53..388d685 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -5,6 +5,7 @@ use std::io::{self, Write}; use std::sync::mpsc; use std::thread; +use crate::analysis::AnalysisInfo; use crate::engine; use crate::movement::Move; use crate::notation; @@ -259,11 +260,17 @@ impl Uci { } /// Send engine analysis information. - fn send_infos(&mut self, infos: &Vec) { + fn send_infos(&mut self, infos: &Vec) { let mut s = "info".to_string(); for i in infos { match i { - engine::Info::CurrentMove(m) => { + AnalysisInfo::Nodes(n) => { + s.push_str(&format!(" nodes {}", n)); + } + AnalysisInfo::Nps(n) => { + s.push_str(&format!(" nps {}", n)); + } + AnalysisInfo::CurrentMove(m) => { s.push_str(&format!(" currmove {}", notation::move_to_string(m))); } }