1use std::convert::TryFrom;
4use std::time::Instant;
5
6use crate::coretypes::{Color, PlyKind};
7use crate::error::{self, ErrorKind};
8use crate::uci::SearchControls;
9
10const TIME_RATIO: u32 = 15; const OVERHEAD: u128 = 10; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
19pub enum Mode {
20 Infinite, Standard(Standard), Depth(Depth), MoveTime(MoveTime), }
25
26impl Mode {
27 pub fn stop(&self, root_player: Color, ply: PlyKind) -> bool {
29 match self {
30 Mode::Infinite => Infinite::stop(),
31 Mode::Depth(depth_mode) => depth_mode.stop(ply),
32 Mode::MoveTime(movetime_mode) => movetime_mode.stop(ply),
33 Mode::Standard(standard_mode) => standard_mode.stop(root_player, ply),
34 }
35 }
36
37 pub fn infinite() -> Self {
39 Self::Infinite
40 }
41
42 pub fn depth(ply: PlyKind, movetime: Option<u32>) -> Self {
44 Self::Depth(Depth {
45 depth: ply,
46 instant: Instant::now(),
47 movetime,
48 })
49 }
50
51 pub fn movetime(movetime: u32, ply: Option<PlyKind>) -> Self {
53 Self::MoveTime(MoveTime {
54 movetime,
55 instant: Instant::now(),
56 depth: ply,
57 })
58 }
59
60 pub fn standard(
61 wtime: i32,
62 btime: i32,
63 winc: Option<u32>,
64 binc: Option<u32>,
65 moves_to_go: Option<u32>,
66 ply: Option<PlyKind>,
67 ) -> Self {
68 Self::Standard(Standard {
69 wtime,
70 btime,
71 winc,
72 binc,
73 moves_to_go,
74 depth: ply,
75 instant: Instant::now(),
76 })
77 }
78}
79
80impl TryFrom<SearchControls> for Mode {
81 type Error = error::Error;
82 fn try_from(controls: SearchControls) -> error::Result<Self> {
83 if Infinite::satisfied(&controls) {
84 Ok(Mode::Infinite)
85 } else if Standard::satisfied(&controls) {
86 Ok(Mode::standard(
87 controls.wtime.unwrap(),
88 controls.btime.unwrap(),
89 controls.winc,
90 controls.binc,
91 controls.moves_to_go,
92 controls.depth,
93 ))
94 } else if MoveTime::satisfied(&controls) {
95 Ok(Mode::movetime(controls.move_time.unwrap(), controls.depth))
96 } else if Depth::satisfied(&controls) {
97 Ok(Mode::depth(controls.depth.unwrap(), controls.move_time))
98 } else {
99 Err(ErrorKind::ModeNotSatisfied.into())
100 }
101 }
102}
103
104#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
105pub struct Infinite;
106
107impl Infinite {
108 fn stop() -> bool {
109 false
110 }
111 fn satisfied(search_controls: &SearchControls) -> bool {
113 search_controls.infinite
114 }
115}
116
117#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
118pub struct Depth {
119 pub depth: PlyKind,
120 instant: Instant,
121 movetime: Option<u32>,
122}
123
124impl Depth {
125 fn stop(&self, ply: PlyKind) -> bool {
127 if ply > self.depth {
128 return true;
129 }
130
131 if let Some(movetime) = self.movetime {
132 let elapsed_ms = self.instant.elapsed().as_millis();
133 if elapsed_ms >= (movetime as u128).saturating_sub(OVERHEAD) {
134 return true;
135 }
136 }
137
138 return false;
139 }
140
141 fn satisfied(search_controls: &SearchControls) -> bool {
143 search_controls.depth.is_some()
144 }
145}
146
147#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
148pub struct MoveTime {
149 movetime: u32,
150 instant: Instant,
151 depth: Option<PlyKind>,
152}
153
154impl MoveTime {
155 fn stop(&self, ply: PlyKind) -> bool {
157 let elapsed_ms = self.instant.elapsed().as_millis();
158 if elapsed_ms >= (self.movetime as u128).saturating_sub(OVERHEAD) {
159 return true;
160 }
161
162 if let Some(depth) = self.depth {
163 if ply > depth {
164 return true;
165 }
166 }
167
168 return false;
169 }
170
171 fn satisfied(search_controls: &SearchControls) -> bool {
173 search_controls.move_time.is_some()
174 }
175}
176
177#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
178pub struct Standard {
179 instant: Instant,
180 wtime: i32,
181 btime: i32,
182 winc: Option<u32>,
183 binc: Option<u32>,
184 moves_to_go: Option<u32>,
185 depth: Option<PlyKind>,
186}
187
188impl Standard {
189 fn stop(&self, root_player: Color, ply: PlyKind) -> bool {
192 let target_elapsed = self.target_elapsed_ms(root_player);
193 let elapsed_ms = self.instant.elapsed().as_millis();
194
195 if elapsed_ms >= target_elapsed {
196 return true;
197 }
198
199 if let Some(depth) = self.depth {
201 if ply > depth {
202 return true;
203 }
204 }
205
206 false
207 }
208
209 fn target_elapsed_ms(&self, root_player: Color) -> u128 {
210 let remaining_time = match root_player {
211 Color::White => self.wtime,
212 Color::Black => self.btime,
213 };
214
215 let remaining_time: u128 = if remaining_time.is_negative() {
217 0
218 } else {
219 remaining_time as u128
220 };
221
222 (remaining_time / TIME_RATIO as u128).saturating_sub(OVERHEAD)
223 }
224
225 fn satisfied(search_controls: &SearchControls) -> bool {
227 search_controls.wtime.is_some() && search_controls.btime.is_some()
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn standard() {
237 let mut controls = SearchControls::default();
238 controls.wtime = Some(5000);
239 controls.btime = Some(5000);
240
241 let mode = Mode::try_from(controls);
242
243 assert!(mode.is_ok());
244 let mode = mode.unwrap();
245 assert!(matches!(mode, Mode::Standard(_)));
246 }
247}