analysis: provide "nodes" and "nps" UCI info
This commit is contained in:
parent
685039d98a
commit
a426e2f777
|
@ -1,8 +1,8 @@
|
||||||
//! Analysis functions.
|
//! Analysis functions.
|
||||||
|
|
||||||
use std::sync::{Arc, atomic, mpsc};
|
use std::sync::{Arc, atomic, mpsc};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::board;
|
|
||||||
use crate::engine;
|
use crate::engine;
|
||||||
use crate::movement::Move;
|
use crate::movement::Move;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
@ -19,6 +19,9 @@ pub struct Analyzer {
|
||||||
node: Node,
|
node: Node,
|
||||||
engine_tx: mpsc::Sender<engine::Cmd>,
|
engine_tx: mpsc::Sender<engine::Cmd>,
|
||||||
max_depth: u32,
|
max_depth: u32,
|
||||||
|
nps_time: Instant,
|
||||||
|
num_nodes: u64,
|
||||||
|
num_nodes_in_second: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Analysis parameters.
|
/// Analysis parameters.
|
||||||
|
@ -31,16 +34,40 @@ pub struct AnalysisParams {
|
||||||
pub black_inc: i32,
|
pub black_inc: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Analysis info to report.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum AnalysisInfo {
|
||||||
|
Nodes(u64),
|
||||||
|
Nps(u64),
|
||||||
|
CurrentMove(Move),
|
||||||
|
}
|
||||||
|
|
||||||
impl Analyzer {
|
impl Analyzer {
|
||||||
/// Create a new worker to analyze from `node`.
|
/// Create a new worker to analyze from `node`.
|
||||||
pub fn new(node: Node, engine_tx: mpsc::Sender<engine::Cmd>) -> Analyzer {
|
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) {
|
fn log(&self, message: String) {
|
||||||
self.engine_tx.send(engine::Cmd::Log(message)).unwrap();
|
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.
|
/// Analyse best moves for the node.
|
||||||
///
|
///
|
||||||
/// - `args`: parameters provided for this analysis.
|
/// - `args`: parameters provided for this analysis.
|
||||||
|
@ -60,8 +87,9 @@ 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 = 3;
|
self.nps_time = Instant::now();
|
||||||
let (max_score, best_move) = self.negamax(&self.node, MIN_F32, MAX_F32, 0);
|
self.max_depth = 4;
|
||||||
|
let (max_score, best_move) = self.negamax(&self.node.clone(), MIN_F32, MAX_F32, 0);
|
||||||
|
|
||||||
if best_move.is_some() {
|
if best_move.is_some() {
|
||||||
let log_str = format!(
|
let log_str = format!(
|
||||||
|
@ -69,34 +97,50 @@ impl Analyzer {
|
||||||
notation::move_to_string(&best_move.unwrap()), max_score
|
notation::move_to_string(&best_move.unwrap()), max_score
|
||||||
);
|
);
|
||||||
self.log(log_str);
|
self.log(log_str);
|
||||||
self.engine_tx.send(engine::Cmd::TmpBestMove(best_move)).unwrap();
|
self.report_best_move(best_move);
|
||||||
} else {
|
} else {
|
||||||
// If no best move could be found, checkmate is unavoidable; send the first legal move.
|
// If no best move could be found, checkmate is unavoidable; send the first legal move.
|
||||||
self.log("Checkmate is unavoidable.".to_string());
|
self.log("Checkmate is unavoidable.".to_string());
|
||||||
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true);
|
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 };
|
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(
|
fn negamax(
|
||||||
&self,
|
&mut self,
|
||||||
node: &Node,
|
node: &Node,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
beta: f32,
|
beta: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
) -> (f32, Option<Move>) {
|
) -> (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 {
|
if depth == self.max_depth {
|
||||||
let stats = node.compute_stats();
|
let stats = node.compute_stats();
|
||||||
let ev = evaluate(&stats);
|
let ev = evaluate(&stats);
|
||||||
return (ev, None)
|
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 moves = node.get_player_moves(true);
|
||||||
let mut alpha = alpha;
|
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 result = self.negamax(&sub_node, -beta, -alpha, depth + 1);
|
let result = self.negamax(&sub_node, -beta, -alpha, depth + 1);
|
||||||
|
@ -104,14 +148,11 @@ impl Analyzer {
|
||||||
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 {
|
if best_score > alpha {
|
||||||
alpha = best_score;
|
alpha = best_score;
|
||||||
self.log(format!("negamax: depth {} new alpha {}.", depth, alpha));
|
|
||||||
}
|
}
|
||||||
if alpha >= beta {
|
if alpha >= beta {
|
||||||
self.log(format!("negamax: depth {} alpha above beta {}, cut.", depth, beta));
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,30 +47,33 @@ enum Mode {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
// Commands that can be received by the engine.
|
// 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.
|
/// Provide a sender to UCI to start receiving commands.
|
||||||
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command.
|
UciChannel(mpsc::Sender<Cmd>),
|
||||||
UciGo(Vec<uci::GoArgs>), // UCI "go" command.
|
/// UCI "debug" command.
|
||||||
Stop, // Stop working ASAP.
|
UciDebug(bool),
|
||||||
TmpBestMove(Option<Move>), // Send best move found by analysis worker (TEMPORARY).
|
/// UCI "position" command.
|
||||||
WorkerInfo(Vec<Info>), // Informations from a worker.
|
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.
|
// Commands that can be sent by the engine.
|
||||||
|
|
||||||
/// Ask for a string to be logged or printed.
|
/// Ask for a string to be logged or printed.
|
||||||
///
|
///
|
||||||
/// Note that workers can send this command to engine, expecting
|
/// Note that workers can send this command to engine, expecting
|
||||||
/// the message to be forwarded to whatever can log.
|
/// the message to be forwarded to whatever can log.
|
||||||
Log(String),
|
Log(String),
|
||||||
|
/// Report ongoing analysis information.
|
||||||
|
Info(Vec<analysis::AnalysisInfo>),
|
||||||
/// Report found best move.
|
/// Report found best move.
|
||||||
BestMove(Option<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.
|
/// General engine implementation.
|
||||||
|
@ -120,7 +123,7 @@ impl Engine {
|
||||||
// Workers commands.
|
// Workers commands.
|
||||||
Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())),
|
Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())),
|
||||||
Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())),
|
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),
|
_ => 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::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
use crate::analysis::AnalysisInfo;
|
||||||
use crate::engine;
|
use crate::engine;
|
||||||
use crate::movement::Move;
|
use crate::movement::Move;
|
||||||
use crate::notation;
|
use crate::notation;
|
||||||
|
@ -259,11 +260,17 @@ impl Uci {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send engine analysis information.
|
/// 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();
|
let mut s = "info".to_string();
|
||||||
for i in infos {
|
for i in infos {
|
||||||
match i {
|
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)));
|
s.push_str(&format!(" currmove {}", notation::move_to_string(m)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue