1use std::ops::Not;
2
3use serde::Serialize;
4use serde_json::{Value, json};
5use serde_with::skip_serializing_none;
6
7use crate::{Bonus, Config, Player, Rules};
8
9#[derive(Debug, Clone, Serialize)]
11#[serde(into = "Value")]
12#[expect(
13 clippy::large_enum_variant,
14 reason = "Boxing AnalysisRequest would be inconvenient, and very little would be gained"
15)]
16pub enum Request {
17 Analyze(AnalysisRequest),
19
20 QueryVersion {
22 id: String,
24 },
25
26 ClearCache {
28 id: String,
30 },
31
32 Terminate {
34 id: String,
36
37 terminate_id: String,
39
40 turn_numbers: Option<Vec<usize>>,
42 },
43
44 TerminateAll {
46 id: String,
48
49 turn_numbers: Option<Vec<usize>>,
51 },
52
53 QueryModels {
55 id: String,
57 },
58}
59
60impl From<Request> for Value {
61 fn from(request: Request) -> Self {
62 match request {
63 Request::Analyze(request) => {
64 serde_json::to_value(request).expect("request should be serializable")
65 }
66 Request::QueryVersion { id } => json!({
67 "id": id,
68 "action": "query_version",
69 }),
70 Request::ClearCache { id } => json!({
71 "id": id,
72 "action": "clear_cache",
73 }),
74 Request::Terminate {
75 id,
76 terminate_id,
77 turn_numbers,
78 } => {
79 let mut value = json!({
80 "id": id,
81 "action": "terminate",
82 "terminateId": terminate_id,
83 }
84 );
85 if let Some(turn_numbers) = turn_numbers {
86 value
87 .as_object_mut()
88 .expect("value should be an object")
89 .insert("turnNumbers".to_string(), json!(turn_numbers));
90 }
91 value
92 }
93 Request::TerminateAll { id, turn_numbers } => {
94 let mut value = json!({
95 "id": id,
96 "action": "terminate_all",
97 }
98 );
99 if let Some(turn_numbers) = turn_numbers {
100 value
101 .as_object_mut()
102 .expect("value should be an object")
103 .insert("turnNumbers".to_string(), json!(turn_numbers));
104 }
105 value
106 }
107 Request::QueryModels { id } => json!({
108 "id": id,
109 "action": "query_models",
110 }),
111 }
112 }
113}
114
115#[skip_serializing_none]
117#[derive(Debug, Clone, Serialize)]
118#[serde(rename_all = "camelCase")]
119pub struct AnalysisRequest {
120 pub id: String,
122
123 pub rules: Rules,
125
126 pub komi: Option<f64>,
128
129 pub white_handicap_bonus: Option<Bonus>,
131
132 pub board_x_size: u8,
134
135 pub board_y_size: u8,
137
138 pub initial_stones: Option<Vec<(Player, String)>>,
140
141 pub initial_player: Option<Player>,
143
144 pub moves: Vec<(Player, String)>,
147
148 pub analyze_turns: Option<Vec<usize>>,
152
153 pub max_visits: Option<u32>,
155
156 pub root_policy_temperature: Option<f64>,
158
159 pub root_fpu_reduction_max: Option<f64>,
161
162 #[serde(rename = "analysisPVLen")]
164 pub analysis_pv_len: Option<usize>,
165
166 #[serde(skip_serializing_if = "Not::not")]
168 pub include_ownership: bool,
169
170 #[serde(skip_serializing_if = "Not::not")]
172 pub include_ownership_stdev: bool,
173
174 #[serde(skip_serializing_if = "Not::not")]
176 pub include_moves_ownership: bool,
177
178 #[serde(skip_serializing_if = "Not::not")]
180 pub include_moves_ownership_stdev: bool,
181
182 #[serde(skip_serializing_if = "Not::not")]
184 pub include_policy: bool,
185
186 #[serde(rename = "includePVVisits", skip_serializing_if = "Not::not")]
188 pub include_pv_visits: bool,
189
190 #[serde(skip_serializing_if = "Not::not")]
192 pub include_no_result_value: bool,
193
194 pub avoid_moves: Option<Vec<RestrictedMoves>>,
196
197 pub allow_moves: Option<Vec<RestrictedMoves>>,
199
200 pub override_settings: Option<Config>,
202
203 pub report_during_search_every: Option<f64>,
205
206 pub priority: Option<i32>,
208
209 pub priorities: Option<Vec<i32>>,
211}
212
213impl AnalysisRequest {
214 pub fn new(
216 id: String,
217 rules: Rules,
218 board_x_size: u8,
219 board_y_size: u8,
220 moves: Vec<(Player, String)>,
221 ) -> Self {
222 Self {
223 id,
224 rules,
225 komi: None,
226 white_handicap_bonus: None,
227 board_x_size,
228 board_y_size,
229 initial_stones: None,
230 initial_player: None,
231 moves,
232 analyze_turns: None,
233 max_visits: None,
234 root_policy_temperature: None,
235 root_fpu_reduction_max: None,
236 analysis_pv_len: None,
237 include_ownership: false,
238 include_ownership_stdev: false,
239 include_moves_ownership: false,
240 include_moves_ownership_stdev: false,
241 include_policy: false,
242 include_pv_visits: false,
243 include_no_result_value: false,
244 avoid_moves: None,
245 allow_moves: None,
246 override_settings: None,
247 report_during_search_every: None,
248 priority: None,
249 priorities: None,
250 }
251 }
252
253 pub fn with_komi(mut self, komi: f64) -> Self {
255 self.komi = Some(komi);
256 self
257 }
258
259 pub fn with_white_handicap_bonus(mut self, bonus: Bonus) -> Self {
261 self.white_handicap_bonus = Some(bonus);
262 self
263 }
264
265 pub fn with_initial_stones(mut self, initial_stones: Vec<(Player, String)>) -> Self {
267 self.initial_stones = Some(initial_stones);
268 self
269 }
270
271 pub fn with_initial_player(mut self, initial_player: Player) -> Self {
273 self.initial_player = Some(initial_player);
274 self
275 }
276
277 pub fn with_analyze_turns(mut self, analyze_turns: Vec<usize>) -> Self {
279 self.analyze_turns = Some(analyze_turns);
280 self
281 }
282
283 pub fn with_max_visits(mut self, max_visits: u32) -> Self {
285 self.max_visits = Some(max_visits);
286 self
287 }
288
289 pub fn with_root_policy_temperature(mut self, root_policy_temperature: f64) -> Self {
291 self.root_policy_temperature = Some(root_policy_temperature);
292 self
293 }
294
295 pub fn with_root_fpu_reduction_max(mut self, root_fpu_reduction_max: f64) -> Self {
297 self.root_fpu_reduction_max = Some(root_fpu_reduction_max);
298 self
299 }
300
301 pub fn with_analysis_pv_len(mut self, analysis_pv_len: usize) -> Self {
303 self.analysis_pv_len = Some(analysis_pv_len);
304 self
305 }
306
307 pub fn with_ownership(mut self) -> Self {
309 self.include_ownership = true;
310 self
311 }
312
313 pub fn with_ownership_stdev(mut self) -> Self {
315 self.include_ownership_stdev = true;
316 self
317 }
318
319 pub fn with_moves_ownership(mut self) -> Self {
321 self.include_moves_ownership = true;
322 self
323 }
324
325 pub fn with_moves_ownership_stdev(mut self) -> Self {
327 self.include_moves_ownership_stdev = true;
328 self
329 }
330
331 pub fn with_policy(mut self) -> Self {
333 self.include_policy = true;
334 self
335 }
336
337 pub fn with_pv_visits(mut self) -> Self {
339 self.include_pv_visits = true;
340 self
341 }
342
343 pub fn with_no_result_value(mut self) -> Self {
345 self.include_no_result_value = true;
346 self
347 }
348
349 pub fn with_avoid_moves(mut self, avoid_moves: Vec<RestrictedMoves>) -> Self {
351 self.avoid_moves = Some(avoid_moves);
352 self
353 }
354
355 pub fn with_allow_moves(mut self, allow_moves: Vec<RestrictedMoves>) -> Self {
357 self.allow_moves = Some(allow_moves);
358 self
359 }
360
361 pub fn with_override_settings(mut self, config: Config) -> Self {
363 self.override_settings = Some(config);
364 self
365 }
366
367 pub fn with_report_during_search_every(mut self, seconds: f64) -> Self {
369 self.report_during_search_every = Some(seconds);
370 self
371 }
372
373 pub fn with_priority(mut self, priority: i32) -> Self {
375 self.priority = Some(priority);
376 self
377 }
378
379 pub fn with_priorities(mut self, priorities: Vec<i32>) -> Self {
381 self.priorities = Some(priorities);
382 self
383 }
384}
385
386#[derive(Debug, Clone, Serialize)]
389#[serde(rename_all = "camelCase")]
390pub struct RestrictedMoves {
391 pub player: Player,
393
394 pub moves: Vec<String>,
396
397 pub until_depth: u32,
399}