1use std::fmt::Debug;
2
3use thiserror::Error;
4
5use crate::{
6 code::{Code, Set},
7 game::Game, verifier::VerifierOption,
8};
9
10#[derive(Copy, Clone, Debug)]
15pub struct State<'a> {
16 game: &'a Game,
17 possible_codes: Set,
19 currently_selected_code: Option<Code>,
20 currently_chosen_verifier: Option<ChosenVerifier>,
21 has_guessed_one_verifier_for_code: bool,
22 codes_guessed: u8,
23 verifiers_checked: u8,
24}
25
26#[derive(Copy, Clone, Eq, PartialEq, Debug)]
32struct StateScore(u16);
33
34#[derive(Copy, Clone, Eq, PartialEq, Debug)]
35pub struct GameScore {
36 pub codes_guessed: u8,
37 pub verifiers_checked: u8,
38}
39
40impl StateScore {
41 fn no_solution() -> Self {
42 StateScore(0)
48 }
49
50 fn solution(codes_guessed: u8, verifier_checks: u8) -> Self {
51 StateScore(u16::from(codes_guessed) << 8 | u16::from(verifier_checks))
52 }
53
54 fn useless_verifier_check() -> Self {
58 StateScore(u16::MAX)
59 }
60
61 pub fn codes_and_verifiers_checked(self) -> Option<GameScore> {
65 if self.0 == u16::MAX {
66 None
67 } else {
68 Some(GameScore {
69 codes_guessed: (self.0 >> 8) as u8,
70 verifiers_checked: (self.0 & 0b1111_1111) as u8,
71 })
72 }
73 }
74
75 fn min() -> Self {
76 StateScore(u16::MAX)
77 }
78
79 fn max() -> Self {
80 StateScore(u16::MIN)
81 }
82}
83
84#[derive(Copy, Clone, Eq, PartialEq, Debug)]
86pub enum AfterMoveInfo {
87 UselessVerifierCheck,
90}
91
92impl Ord for StateScore {
93 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
94 self.0.cmp(&other.0).reverse()
95 }
96}
97
98impl PartialOrd for StateScore {
99 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
100 Some(self.cmp(other))
101 }
102}
103
104#[derive(Eq, PartialEq, Clone, Copy, Debug)]
106pub enum VerifierSolution {
107 Cross,
108 Check,
109}
110
111#[derive(Eq, PartialEq, Copy, Clone)]
112pub struct ChosenVerifier(u8);
113
114impl From<u8> for ChosenVerifier {
115 fn from(value: u8) -> Self {
116 ChosenVerifier(value)
117 }
118}
119
120impl Debug for ChosenVerifier {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{}", ('A'..).nth(self.0.into()).unwrap())
123 }
124}
125
126#[derive(Eq, PartialEq, Clone, Copy, Debug)]
127pub enum Move {
128 ChooseNewCode(Code),
129 VerifierSolution(VerifierSolution),
130 ChooseVerifier(ChosenVerifier),
131}
132
133#[derive(Copy, Clone, Eq, PartialEq, Error, Debug)]
134pub enum AfterMoveError {
135 #[error("invalid move for game state")]
136 InvalidMoveError,
137 #[error("there are no solutions left for this game state")]
138 NoCodesLeft,
139}
140
141impl<'a> State<'a> {
142 #[must_use]
143 pub fn new(game: &'a Game) -> Self {
144 State {
145 game,
146 possible_codes: game.possible_solutions(),
147 currently_selected_code: None,
148 currently_chosen_verifier: None,
149 has_guessed_one_verifier_for_code: false,
150 codes_guessed: 0,
151 verifiers_checked: 0,
152 }
153 }
154
155 pub fn possible_codes(self) -> Set {
156 self.possible_codes
157 }
158
159 #[must_use]
160 pub fn is_solved(self) -> bool {
161 self.possible_codes.size() == 1
162 }
163
164 #[must_use]
166 pub fn solution(self) -> Option<Code> {
167 self.possible_codes
168 .into_iter()
169 .next()
170 .filter(|_| self.is_solved())
171 }
172
173 pub fn after_move(
178 mut self,
179 move_to_do: Move,
180 ) -> Result<(State<'a>, Option<AfterMoveInfo>), AfterMoveError> {
181 let mut info = None;
182 match move_to_do {
183 Move::ChooseNewCode(code) => {
184 if self.is_awaiting_result() {
185 return Err(AfterMoveError::InvalidMoveError);
186 }
187 self.currently_selected_code = Some(code);
188 self.codes_guessed += 1;
189 self.has_guessed_one_verifier_for_code = false;
190 }
191 Move::ChooseVerifier(choose_verifier_option) => {
192 if self.is_awaiting_result() {
193 return Err(AfterMoveError::InvalidMoveError);
194 }
195 self.currently_chosen_verifier = Some(choose_verifier_option);
196 self.verifiers_checked += 1;
197 }
198 Move::VerifierSolution(verifier_solution) => {
199 if let Some(chosen_verifier) = self.currently_chosen_verifier {
200 let bitmask_for_solution = self
202 .game
203 .verfier(chosen_verifier.0)
204 .options()
205 .map(VerifierOption::code_set)
206 .filter(|code_set| {
207 let would_give_check =
208 code_set.contains(self.currently_selected_code.unwrap());
209 let gives_check = verifier_solution == VerifierSolution::Check;
210 would_give_check == gives_check
211 })
212 .collect::<Set>();
213 let possible_codes = self.possible_codes;
214 let new_possible_codes = possible_codes.intersected_with(bitmask_for_solution);
215 if new_possible_codes == possible_codes {
216 info = Some(AfterMoveInfo::UselessVerifierCheck);
217 } else if new_possible_codes.size() == 0 {
218 return Err(AfterMoveError::NoCodesLeft);
219 } else {
220 self.possible_codes = new_possible_codes;
221 }
222
223 self.currently_chosen_verifier = None;
224 if self.verifiers_checked == 3 {
225 self.currently_selected_code = None;
226 }
227 } else {
228 return Err(AfterMoveError::InvalidMoveError);
229 }
230 }
231 }
232 Ok((self, info))
233 }
234
235 #[must_use]
237 pub fn is_awaiting_result(&self) -> bool {
238 self.currently_chosen_verifier.is_some()
239 }
240
241 pub fn possible_moves(&self) -> impl Iterator<Item = Move> + '_ {
246 [
250 Move::VerifierSolution(VerifierSolution::Check),
251 Move::VerifierSolution(VerifierSolution::Cross),
252 ]
253 .iter()
254 .copied()
255 .filter(|_| self.is_awaiting_result())
256 .chain(
258 (0..u8::try_from(self.game.verifier_count()).unwrap())
260 .map(|i| Move::ChooseVerifier(i.into()))
261 .filter(|_| self.currently_selected_code.is_some())
262 .chain(
263 Set::all().into_iter().map(Move::ChooseNewCode).filter(|_| {
265 self.currently_selected_code.is_none()
266 || self.has_guessed_one_verifier_for_code
267 }),
268 )
269 .filter(|_| !self.is_awaiting_result()),
270 )
271 }
272
273 fn is_maximizing_score(self) -> bool {
277 !self.is_awaiting_result()
278 }
279
280 fn alphabeta(self, mut alpha: StateScore, mut beta: StateScore) -> (StateScore, Option<Move>) {
282 if self.is_solved() {
284 (
285 StateScore::solution(self.codes_guessed, self.verifiers_checked),
286 None,
287 )
288 } else if self.is_maximizing_score() {
289 let mut highest_score = StateScore::min();
290 let mut best_move = None;
291 for move_to_do in self.possible_moves() {
292 let next_node = self.after_move(move_to_do);
293 let score = match next_node {
294 Err(AfterMoveError::NoCodesLeft) => StateScore::no_solution(),
295 Err(AfterMoveError::InvalidMoveError) => panic!("invalid move!"),
296 Ok((_, Some(AfterMoveInfo::UselessVerifierCheck))) => {
297 StateScore::useless_verifier_check()
298 }
299 Ok((state, None)) => state.alphabeta(alpha, beta).0,
300 };
301 if score > highest_score {
302 highest_score = score;
303 best_move = Some(move_to_do);
304 }
305 if score > beta {
306 break;
307 }
308 if score > alpha {
309 alpha = score;
310 }
311 }
312 (highest_score, best_move)
313 } else {
314 let mut lowest_score = StateScore::max();
315 for move_to_do in self.possible_moves() {
316 let next_node = self.after_move(move_to_do);
317 let score = match next_node {
318 Err(AfterMoveError::NoCodesLeft) => StateScore::no_solution(),
319 Err(AfterMoveError::InvalidMoveError) => panic!("invalid move"),
320 Ok((_, Some(AfterMoveInfo::UselessVerifierCheck))) => {
321 StateScore::useless_verifier_check()
322 }
323 Ok((state, None)) => state.alphabeta(alpha, beta).0,
324 };
325 if score < lowest_score {
326 lowest_score = score;
327 }
328 if score < alpha {
329 break;
330 }
331 if score < beta {
332 beta = score;
333 }
334 }
335 (lowest_score, None)
337 }
338 }
339
340 pub fn find_best_move(self) -> (GameScore, Move) {
348 assert!(!self.is_awaiting_result() && !self.is_solved());
349 let alpha = StateScore::min();
351 let beta = StateScore::max();
353 if let (score, Some(move_to_do)) = self.alphabeta(alpha, beta) {
354 (score.codes_and_verifiers_checked().unwrap(), move_to_do)
355 } else {
356 panic!("No move possible");
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use crate::gametree::GameScore;
364
365 use super::StateScore;
366
367 #[test]
368 fn test_game_score() {
369 for codes_guessed in 0..10 {
370 for verifiers_checked in codes_guessed..30 {
371 let state_score = StateScore::solution(codes_guessed, verifiers_checked);
372 assert_eq!(
373 state_score.codes_and_verifiers_checked().unwrap(),
374 GameScore {
375 codes_guessed,
376 verifiers_checked
377 }
378 );
379 }
380 }
381 }
382}