analysis: provide "nodes" and "nps" UCI info
This commit is contained in:
parent
685039d98a
commit
a426e2f777
|
@ -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<engine::Cmd>,
|
||||
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<engine::Cmd>) -> 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<AnalysisInfo>) {
|
||||
self.engine_tx.send(engine::Cmd::WorkerInfo(infos)).unwrap();
|
||||
}
|
||||
|
||||
fn report_best_move(&self, m: Option<Move>) {
|
||||
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<Move>) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,30 +47,33 @@ enum Mode {
|
|||
#[derive(Debug)]
|
||||
pub enum Cmd {
|
||||
// Commands that can be received by the engine.
|
||||
UciChannel(mpsc::Sender<Cmd>), // Provide a sender to UCI to start receiving commands.
|
||||
UciDebug(bool), // UCI "debug" command.
|
||||
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
|
||||
UciGo(Vec<uci::GoArgs>), // UCI "go" command.
|
||||
Stop, // Stop working ASAP.
|
||||
TmpBestMove(Option<Move>), // Send best move found by analysis worker (TEMPORARY).
|
||||
WorkerInfo(Vec<Info>), // Informations from a worker.
|
||||
|
||||
/// Provide a sender to UCI to start receiving commands.
|
||||
UciChannel(mpsc::Sender<Cmd>),
|
||||
/// UCI "debug" command.
|
||||
UciDebug(bool),
|
||||
/// UCI "position" command.
|
||||
UciPosition(Vec<uci::PositionArgs>),
|
||||
/// UCI "go" command.
|
||||
UciGo(Vec<uci::GoArgs>),
|
||||
/// Stop working ASAP.
|
||||
Stop,
|
||||
/// Informations from a worker.
|
||||
WorkerInfo(Vec<analysis::AnalysisInfo>),
|
||||
/// Send best move found by analysis worker.
|
||||
WorkerBestMove(Option<Move>),
|
||||
|
||||
// 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<analysis::AnalysisInfo>),
|
||||
/// Report found best move.
|
||||
BestMove(Option<Move>),
|
||||
/// Report ongoing analysis information.
|
||||
Info(Vec<Info>),
|
||||
}
|
||||
|
||||
/// 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),
|
||||
}
|
||||
}
|
||||
|
|
11
src/uci.rs
11
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<engine::Info>) {
|
||||
fn send_infos(&mut self, infos: &Vec<AnalysisInfo>) {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue