1use std::{
2 collections::HashMap,
3 hash::{BuildHasher, Hash},
4};
5
6#[derive(PartialEq, Eq, Debug)]
8pub enum Player {
9 P1,
10 P2,
11}
12
13impl Player {
14 #[must_use]
16 pub const fn opposite(&self) -> Self {
17 match self {
18 Self::P1 => Self::P2,
19 Self::P2 => Self::P1,
20 }
21 }
22}
23
24pub trait Game {
26 type Move: Copy;
28
29 type Iter: Iterator<Item = Self::Move>;
31
32 fn player(&self) -> Player;
34
35 fn score(&self) -> u32;
37
38 fn max_score(&self) -> u32;
40
41 fn min_score(&self) -> i32;
43
44 fn make_move(&mut self, m: Self::Move) -> bool;
46
47 fn possible_moves(&self) -> Self::Iter;
54
55 fn is_winning_move(&self, m: Self::Move) -> bool;
57}
58
59pub trait TranspositionTable<T: Eq + Hash + Game> {
67 fn get(&self, board: &T) -> Option<i32>;
68 fn insert(&mut self, board: T, score: i32);
69 fn has(&self, board: &T) -> bool;
70}
71
72impl<K: Eq + Hash + Game, S: BuildHasher + Default> TranspositionTable<K> for HashMap<K, i32, S> {
73 fn get(&self, board: &K) -> Option<i32> {
74 self.get(board).copied()
75 }
76
77 fn insert(&mut self, board: K, score: i32) {
78 self.insert(board, score);
79 }
80
81 fn has(&self, board: &K) -> bool {
82 self.contains_key(board)
83 }
84}
85
86fn negamax<T: Game + Clone + Eq + Hash>(
92 game: &T,
93 transposition_table: &mut dyn TranspositionTable<T>,
94 mut alpha: i32,
95 mut beta: i32,
96) -> i32 {
97 for m in &mut game.possible_moves() {
98 if game.is_winning_move(m) {
99 return game.score() as i32 - 1;
100 }
101 }
102
103 {
104 let max = transposition_table
105 .get(game)
106 .unwrap_or(game.max_score() as i32);
107 if beta > max {
108 beta = max;
109 if alpha >= beta {
110 return beta;
111 }
112 }
113 }
114
115 for m in &mut game.possible_moves() {
116 let mut board = game.clone();
117 board.make_move(m);
118
119 let score = -negamax(&board, transposition_table, -beta, -alpha);
120
121 if score >= beta {
122 return beta;
123 }
124 if score > alpha {
125 alpha = score;
126 }
127 }
128
129 transposition_table.insert(game.clone(), alpha);
130
131 alpha
132}
133
134pub fn solve<T: Game + Clone + Eq + Hash>(game: &T) -> i32 {
135 let min = game.min_score();
136 let max = game.max_score() as i32 + 1;
137
138 let mut alpha = min;
139 let mut beta = max;
140
141 while alpha < beta {
142 let med = alpha + (beta - alpha) / 2;
143 let r = negamax(game, &mut HashMap::new(), med, med + 1);
144
145 if r <= med {
146 beta = r;
147 } else {
148 alpha = r;
149 }
150 }
151
152 alpha
153}
154
155pub fn move_scores<T: Game + Clone + Eq + Hash>(
163 game: &T,
164) -> impl Iterator<Item = (<T as Game>::Move, i32)> + '_ {
165 game.possible_moves().map(move |m| {
166 let mut board = game.clone();
167 board.make_move(m);
168 (m, -solve(&board))
169 })
170}