1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter};
use internal_iterator::InternalIterator;
use rand::Rng;
use crate::ai::Bot;
use crate::ai::minimax::{Heuristic, minimax, minimax_value};
use crate::board::{Board, Outcome, Player};
use crate::wdl::POV;
#[derive(Debug)]
pub struct SolverHeuristic;
impl<B: Board> Heuristic<B> for SolverHeuristic {
type V = i32;
fn bound(&self) -> Self::V {
i32::MAX
}
fn value(&self, board: &B, length: u32) -> i32 {
board.outcome().map_or(0, |p| {
p.pov(board.next_player()).sign::<i32>() * (i32::MAX - length as i32)
})
}
}
pub fn find_forcing_winner(board: &impl Board, depth: u32) -> Option<Player> {
let value = minimax_value(board, &SolverHeuristic, depth);
match value.cmp(&0) {
Ordering::Less => Some(board.next_player().other()),
Ordering::Equal => None,
Ordering::Greater => Some(board.next_player()),
}
}
pub fn is_double_forced_draw(board: &impl Board, depth: u32) -> Result<bool, ()> {
if board.outcome() == Some(Outcome::Draw) { return Ok(true); }
if board.outcome().is_some() { return Ok(false); }
if depth == 0 { return Err(()); }
let result = board.available_moves().find_map(|mv| {
let child = board.clone_and_play(mv);
match is_double_forced_draw(&child, depth - 1) {
Ok(true) => None,
Ok(false) => Some(false),
Err(()) => Some(true),
}
});
match result {
Some(true) => Err(()),
Some(false) => Ok(false),
None => Ok(true)
}
}
pub struct SolverBot<R: Rng> {
depth: u32,
rng: R,
}
impl<R: Rng> Debug for SolverBot<R> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "SolverBot {{ depth: {} }}", self.depth)
}
}
impl<R: Rng> SolverBot<R> {
pub fn new(depth: u32, rng: R) -> Self {
assert!(depth > 0);
SolverBot { depth, rng }
}
}
impl<B: Board, R: Rng> Bot<B> for SolverBot<R> {
fn select_move(&mut self, board: &B) -> B::Move {
minimax(board, &SolverHeuristic, self.depth, &mut self.rng).best_move.unwrap()
}
}