codebreaker_solver/
solver.rs1use std::cmp::min;
2
3use rand::seq::SliceRandom as _;
4use rayon::iter::{IntoParallelRefIterator, ParallelIterator as _};
5
6use crate::{
7 code::{all_possible_codes, Code, NUMBER_OF_PEGS_IN_CODE},
8 hint::Hint,
9 peg::NUM_DIFFERENT_PEGS,
10};
11
12pub struct Solver {
14 remaining_unguessed_codes: Vec<Code>,
18 potential_solutions: Vec<Code>,
20}
21
22impl Solver {
23 pub fn new() -> Self {
25 let mut potential_solutions =
26 Vec::with_capacity((NUM_DIFFERENT_PEGS as usize).pow(NUMBER_OF_PEGS_IN_CODE as u32));
27 potential_solutions.extend(all_possible_codes());
28 Solver {
29 remaining_unguessed_codes: Vec::new(),
30 potential_solutions,
31 }
32 }
33
34 pub fn with_sampled_guesses(rng: &mut impl rand::Rng) -> Self {
36 let mut solver = Solver::new();
37 solver.remaining_unguessed_codes.shuffle(rng);
38 solver
39 }
40
41 pub fn guess(&mut self) -> Code {
42 let best_candidate = self
48 .potential_solutions
49 .par_iter()
50 .chain(self.remaining_unguessed_codes.par_iter())
51 .map(|&code| Candidate::new(code))
52 .reduce_with(|a, b| {
53 Candidate::better(a, b, |code, lower_bound| {
54 self.min_possibilties_eliminated(code, lower_bound)
55 })
56 })
57 .expect("Solution must be possible");
58 best_candidate.code
59 }
60
61 pub fn update(&mut self, guess: Code, hint: Hint) {
62 self.potential_solutions.retain(|&code| {
63 let canidate_hint = Hint::new(guess, code);
64 if hint != canidate_hint && code != guess {
65 self.remaining_unguessed_codes.push(code);
66 }
67 hint == canidate_hint
68 });
69 self.remaining_unguessed_codes.retain(|&code| code != guess);
70 }
71
72 fn min_possibilties_eliminated(&self, candidate_guess: Code, lower_bound: u32) -> u32 {
77 let mut current_min = u32::MAX;
78 for &possible_solution in &self.potential_solutions {
79 let hint = Hint::new(candidate_guess, possible_solution);
80 let eliminated = self.num_eliminated_possiblities(hint, candidate_guess, current_min);
81 current_min = min(current_min, eliminated);
82 if current_min <= lower_bound {
83 return lower_bound;
84 }
85 }
86 current_min
87 }
88
89 fn num_eliminated_possiblities(&self, hint: Hint, guess: Code, upper_bound: u32) -> u32 {
92 self.potential_solutions
93 .iter()
94 .filter(|&&code| hint != Hint::new(guess, code) || guess == code)
95 .take(upper_bound as usize)
96 .count() as u32
97 }
98}
99
100impl Default for Solver {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106struct Candidate {
108 code: Code,
110 eliminated: Option<u32>,
112}
113
114impl Candidate {
115 fn new(code: Code) -> Self {
116 Candidate {
117 code,
118 eliminated: None,
119 }
120 }
121
122 fn better(
124 mut a: Candidate,
125 mut b: Candidate,
126 min_eliminated_with_lower_bound: impl Fn(Code, u32) -> u32,
127 ) -> Candidate {
128 match (a.eliminated, b.eliminated) {
131 (Some(_), Some(_)) => a.replace_if_better(b),
133 (Some(a_eliminated), None) => {
134 b.eliminated = Some(min_eliminated_with_lower_bound(b.code, a_eliminated));
135 a.replace_if_better(b)
138 }
139 (None, Some(b_eliminated)) => {
140 a.eliminated = Some(min_eliminated_with_lower_bound(a.code, b_eliminated));
141 b.replace_if_better(a)
142 }
143 (None, None) => {
144 a.eliminated = Some(min_eliminated_with_lower_bound(a.code, 0));
145 b.eliminated = Some(min_eliminated_with_lower_bound(
146 b.code,
147 a.eliminated.unwrap(),
148 ));
149 a.replace_if_better(b)
150 }
151 }
152 }
153
154 fn replace_if_better(self, other: Candidate) -> Candidate {
157 if other.eliminated.unwrap() > self.eliminated.unwrap() {
158 other
159 } else {
160 self
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167
168 use crate::{code::Code, hint::Hint};
169
170 use super::Solver;
171
172 #[test]
173 fn solve_5611() {
174 let code: Code = "5611".parse().unwrap();
175
176 let mut solver = Solver::new();
177 let guesses = play_out(code, &mut solver);
178
179 assert_eq!(&["2211", "4321", "5131", "5511", "5611"], &guesses[..]);
180 }
181
182 #[test]
183 fn solve_2231() {
184 let code: Code = "2231".parse().unwrap();
185
186 let mut solver = Solver::new();
187 let guesses = play_out(code, &mut solver);
188
189 assert_eq!(&["2211", "3221", "2231"], &guesses[..]);
190 }
191
192 fn play_out(code: Code, solver: &mut Solver) -> Vec<String> {
193 let mut guesses = Vec::new();
194 let mut count = 0;
195 loop {
196 assert!(count < 5, "Too many guesses");
197
198 let guess = solver.guess();
199 guesses.push(guess.to_string());
200 let hint = Hint::new(guess, code);
201 solver.update(guess, hint);
202 if hint.is_solution() {
203 break;
204 }
205 count += 1;
206 }
207 guesses
208 }
209}