1mod alpha_beta;
4mod history;
5mod ids;
6mod minimax;
7mod negamax;
8mod quiescence;
9
10pub use alpha_beta::*;
11pub use history::*;
12pub use ids::*;
13pub use minimax::*;
14pub use negamax::*;
15pub use quiescence::*;
16
17use std::fmt::{self, Display};
18use std::sync::{atomic::AtomicBool, mpsc, Arc};
19use std::thread;
20use std::time::Duration;
21
22use crate::arrayvec::display;
23use crate::coretypes::{Color, Cp, Move, PlyKind};
24use crate::movelist::Line;
25use crate::timeman::Mode;
26use crate::transposition::TranspositionTable;
27use crate::{Game, Position};
28
29#[derive(Debug, Clone)]
31pub struct SearchResult {
32 pub best_move: Move,
34 pub score: Cp,
36 pub pv: Line,
38 pub player: Color,
40 pub depth: PlyKind,
42 pub nodes: u64,
44 pub q_nodes: u64,
46 pub elapsed: Duration,
48 pub q_elapsed: Duration,
50 pub stopped: bool,
52
53 pub cut_nodes: u64,
55 pub pv_nodes: u64,
57 pub all_nodes: u64,
59 pub tt_hits: u64,
61 pub tt_cuts: u64,
63}
64
65impl SearchResult {
66 pub fn add_metrics(&mut self, other: Self) {
69 self.nodes += other.nodes;
70 self.q_nodes += other.q_nodes;
71 self.elapsed += other.elapsed;
72 self.q_elapsed += other.q_elapsed;
73
74 self.cut_nodes += other.cut_nodes;
75 self.pv_nodes += other.pv_nodes;
76 self.all_nodes += other.all_nodes;
77 self.tt_hits += other.tt_hits;
78 self.tt_cuts += other.tt_cuts;
79 }
80
81 pub fn nps(&self) -> f64 {
83 (self.nodes as f64 / self.elapsed.as_secs_f64()).round()
84 }
85
86 pub fn q_nps(&self) -> f64 {
88 (self.q_nodes as f64 / self.q_elapsed.as_secs_f64()).round()
89 }
90
91 pub fn quiescence_ratio(&self) -> f64 {
95 assert!(
96 self.q_elapsed <= self.elapsed,
97 "logical error for q_elapsed to be greater than elapsed"
98 );
99 self.q_elapsed.as_secs_f64() / self.elapsed.as_secs_f64()
100 }
101
102 pub fn tt_cut_ratio(&self) -> f64 {
104 self.tt_cuts as f64 / self.tt_hits as f64
105 }
106
107 pub fn relative_score(&self) -> Cp {
109 self.score * self.player.sign()
110 }
111
112 pub fn absolute_score(&self) -> Cp {
114 self.score
115 }
116
117 pub fn leading(&self) -> Option<Color> {
119 match self.absolute_score().signum() {
120 1 => Some(Color::White),
121 -1 => Some(Color::Black),
122 _ => None,
123 }
124 }
125}
126
127impl Default for SearchResult {
129 fn default() -> Self {
130 Self {
131 best_move: Move::illegal(),
132 score: Cp(0),
133 pv: Line::new(),
134 player: Color::White,
135 depth: 0,
136 nodes: 0,
137 q_nodes: 0,
138 elapsed: Duration::ZERO,
139 q_elapsed: Duration::ZERO,
140 stopped: false,
141 cut_nodes: 0,
142 pv_nodes: 0,
143 all_nodes: 0,
144 tt_hits: 0,
145 tt_cuts: 0,
146 }
147 }
148}
149
150impl Display for SearchResult {
151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152 let mut displayed = String::new();
153 displayed.push_str("SearchResult {\n");
154 displayed.push_str(&format!(" best_move: {}\n", self.best_move));
155 displayed.push_str(&format!(" abs_score: {}\n", self.absolute_score()));
156 displayed.push_str(&format!(" pv : {}\n", display(&self.pv)));
157 displayed.push_str(&format!(" player : {}\n", self.player));
158 displayed.push_str(&format!(" depth : {}\n", self.depth));
159 displayed.push_str(&format!(" nodes : {}\n", self.nodes));
160 displayed.push_str(&format!(" nps : {}\n", self.nps()));
161 displayed.push_str(&format!(
162 " elapsed : {}.{}s\n",
163 self.elapsed.as_secs(),
164 self.elapsed.subsec_millis()
165 ));
166 displayed.push_str(&format!(" q_ratio : {:.2}\n", self.quiescence_ratio()));
167 displayed.push_str(&format!(" stopped : {}\n", self.stopped));
168 displayed.push_str(&format!(" pv_nodes : {}\n", self.pv_nodes));
169 displayed.push_str(&format!(" cut_nodes: {}\n", self.cut_nodes));
170 displayed.push_str(&format!(" all_nodes: {}\n", self.all_nodes));
171 displayed.push_str(&format!(" tt_cuts : {}\n", self.tt_cuts));
172 displayed.push_str(&format!(" tt_hits : {}\n", self.tt_hits));
173 displayed.push_str(&format!(" tt_ratio : {:.2}\n", self.tt_cut_ratio()));
174 displayed.push_str("}\n");
175
176 write!(f, "{}", displayed)
177 }
178}
179
180pub fn search(position: Position, ply: PlyKind, tt: &TranspositionTable) -> SearchResult {
182 assert_ne!(ply, 0);
183 let mode = Mode::depth(ply, None);
184 let history = History::new(&position.into(), tt.zobrist_table());
185 ids(
186 position,
187 mode,
188 history,
189 tt,
190 Arc::new(AtomicBool::new(false)),
191 true,
192 )
193}
194
195pub fn search_nonblocking<P, T>(
207 game: P,
208 mode: Mode,
209 tt: Arc<TranspositionTable>,
210 stopper: Arc<AtomicBool>,
211 debug: bool,
212 sender: mpsc::Sender<T>,
213) -> thread::JoinHandle<()>
214where
215 T: 'static + Send + From<SearchResult>,
216 P: Into<Game>,
217{
218 let game: Game = game.into();
219 let position = game.position;
220 let history = History::new(&game, tt.zobrist_table());
221
222 thread::spawn(move || {
223 let search_result = ids(position, mode, history, &tt, stopper, debug);
224 sender.send(search_result.into()).unwrap();
225 })
226}