1use std::cmp::Ordering;
2use std::fmt::{Debug, Formatter};
3use std::ops::Neg;
4
5use internal_iterator::InternalIterator;
6use rand::Rng;
7
8use crate::ai::minimax::{minimax, minimax_all_moves, minimax_value, Heuristic, MinimaxResult};
9use crate::ai::Bot;
10use crate::board::{Board, BoardDone, Outcome};
11use crate::pov::NonPov;
12use crate::wdl::OutcomeWDL;
13
14#[derive(Debug)]
18pub struct SolverHeuristic;
19
20#[derive(Debug, Copy, Clone, Eq, PartialEq)]
21pub enum SolverValue {
22 WinIn(u32),
23 LossIn(u32),
24 Draw,
25 Unknown,
26}
27
28impl<B: Board> Heuristic<B> for SolverHeuristic {
29 type V = SolverValue;
30
31 fn value(&self, board: &B, length: u32) -> SolverValue {
32 board
33 .outcome()
34 .map_or(SolverValue::Unknown, |p| match p.pov(board.next_player()) {
35 OutcomeWDL::Win => SolverValue::WinIn(length),
36 OutcomeWDL::Draw => SolverValue::Draw,
37 OutcomeWDL::Loss => SolverValue::LossIn(length),
38 })
39 }
40
41 fn merge(old: SolverValue, new: SolverValue) -> (SolverValue, Ordering) {
42 SolverValue::merge(old, new)
43 }
44}
45
46impl SolverValue {
47 pub fn to_i32(self) -> i32 {
48 match self {
49 SolverValue::WinIn(n) => i32::MAX - n as i32,
50 SolverValue::LossIn(n) => -i32::MAX + n as i32,
51 SolverValue::Draw | SolverValue::Unknown => 0,
52 }
53 }
54
55 pub fn to_outcome_wdl(self) -> Option<OutcomeWDL> {
56 match self {
57 SolverValue::WinIn(_) => Some(OutcomeWDL::Win),
58 SolverValue::LossIn(_) => Some(OutcomeWDL::Loss),
59 SolverValue::Draw => Some(OutcomeWDL::Draw),
60 SolverValue::Unknown => None,
61 }
62 }
63
64 pub fn merge(old: SolverValue, new: SolverValue) -> (SolverValue, Ordering) {
67 use SolverValue::*;
68
69 match (old, new) {
70 (WinIn(old_n), WinIn(new_n)) => (if new_n <= old_n { new } else { old }, new_n.cmp(&old_n).reverse()),
72 (LossIn(old_n), LossIn(new_n)) => (if new_n >= old_n { new } else { old }, new_n.cmp(&old_n)),
73
74 (WinIn(_), _) => (old, Ordering::Less),
76 (LossIn(_), _) => (new, Ordering::Greater),
77 (_, WinIn(_)) => (new, Ordering::Greater),
78 (_, LossIn(_)) => (old, Ordering::Less),
79
80 (Draw, Draw) => (Draw, Ordering::Equal),
82 (Unknown | Draw, Unknown | Draw) => (Unknown, Ordering::Equal),
83 }
84 }
85
86 pub fn could_be_optimal_child(parent: SolverValue, child: SolverValue) -> bool {
88 let best_child = match parent {
89 SolverValue::WinIn(n) => SolverValue::LossIn(n - 1),
90 SolverValue::LossIn(n) => SolverValue::WinIn(n - 1),
91 SolverValue::Draw => SolverValue::Draw,
92 SolverValue::Unknown => panic!("This function does not work for unknown values"),
93 };
94
95 SolverValue::merge(best_child, child).1.is_ge()
97 }
98}
99
100impl Neg for SolverValue {
101 type Output = SolverValue;
102
103 fn neg(self) -> Self::Output {
104 match self {
105 SolverValue::WinIn(n) => SolverValue::LossIn(n),
106 SolverValue::LossIn(n) => SolverValue::WinIn(n),
107 SolverValue::Draw => SolverValue::Draw,
108 SolverValue::Unknown => SolverValue::Unknown,
109 }
110 }
111}
112
113pub fn solve<B: Board>(board: &B, depth: u32, rng: &mut impl Rng) -> MinimaxResult<SolverValue, B::Move> {
114 minimax(board, &SolverHeuristic, depth, rng)
115}
116
117pub fn solve_all_moves<B: Board>(board: &B, depth: u32) -> MinimaxResult<SolverValue, Vec<B::Move>> {
118 minimax_all_moves(board, &SolverHeuristic, depth)
119}
120
121pub fn solve_value<B: Board>(board: &B, depth: u32) -> SolverValue {
122 minimax_value(board, &SolverHeuristic, depth)
123}
124
125pub fn is_double_forced_draw(board: &impl Board, depth: u32) -> Option<bool> {
128 if let Some(outcome) = board.outcome() {
129 return Some(outcome == Outcome::Draw);
130 }
131 if depth == 0 {
132 return None;
133 }
134
135 let mut unknown = false;
136 let draw_or_unknown = board
137 .children()
138 .unwrap()
139 .all(|(_, child)| match is_double_forced_draw(&child, depth - 1) {
140 Some(draw) => draw,
141 None => {
142 unknown = true;
143 true
144 }
145 });
146
147 if draw_or_unknown && unknown {
148 None
149 } else {
150 Some(draw_or_unknown)
151 }
152}
153
154pub struct SolverBot<R: Rng> {
155 depth: u32,
156 rng: R,
157}
158
159impl<R: Rng> Debug for SolverBot<R> {
160 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
161 write!(f, "SolverBot {{ depth: {} }}", self.depth)
162 }
163}
164
165impl<R: Rng> SolverBot<R> {
166 pub fn new(depth: u32, rng: R) -> Self {
167 assert!(depth > 0);
168 SolverBot { depth, rng }
169 }
170}
171
172impl<B: Board, R: Rng> Bot<B> for SolverBot<R> {
173 fn select_move(&mut self, board: &B) -> Result<B::Move, BoardDone> {
174 board.check_done()?;
175 Ok(minimax(board, &SolverHeuristic, self.depth, &mut self.rng)
176 .best_move
177 .unwrap())
178 }
179}