1use crate::{Sudoku, SudokuGrid};
7use crate::constraint::Constraint;
8
9pub mod strategy;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum Solution {
17
18 Impossible,
20
21 Unique(SudokuGrid),
24
25 Ambiguous
28}
29
30impl Solution {
31
32 pub fn union(self, other: Solution) -> Solution {
41 match self {
42 Solution::Impossible => other,
43 Solution::Unique(g) =>
44 match other {
45 Solution::Impossible => Solution::Unique(g),
46 Solution::Unique(other_g) =>
47 if g == other_g {
48 Solution::Unique(g)
49 }
50 else {
51 Solution::Ambiguous
52 }
53 Solution::Ambiguous => Solution::Ambiguous
54 }
55 Solution::Ambiguous => Solution::Ambiguous
56 }
57 }
58}
59
60pub trait Solver {
66
67 fn solve(&self, sudoku: &Sudoku<impl Constraint + Clone>) -> Solution;
72}
73
74pub struct BacktrackingSolver;
81
82impl BacktrackingSolver {
83 fn solve_rec(sudoku: &mut Sudoku<impl Constraint + Clone>, column: usize,
84 row: usize) -> Solution {
85 let size = sudoku.grid().size();
86 let last_cell = row == size;
87
88 if last_cell {
89 return Solution::Unique(sudoku.grid().clone());
90 }
91
92 let next_column = (column + 1) % size;
93 let next_row = if next_column == 0 { row + 1 } else { row };
94
95 if sudoku.grid().get_cell(column, row).unwrap().is_some() {
96 BacktrackingSolver::solve_rec(sudoku, next_column, next_row)
97 }
98 else {
99 let mut solution = Solution::Impossible;
100
101 for number in 1..=size {
102 if sudoku.is_valid_number(column, row, number).unwrap() {
103 sudoku.grid_mut().set_cell(column, row, number).unwrap();
104 let next_solution =
105 BacktrackingSolver::solve_rec(sudoku, next_column,
106 next_row);
107 sudoku.grid_mut().clear_cell(column, row).unwrap();
108 solution = solution.union(next_solution);
109
110 if solution == Solution::Ambiguous {
111 break;
112 }
113 }
114 }
115
116 solution
117 }
118 }
119
120 fn solve(sudoku: &mut Sudoku<impl Constraint + Clone>) -> Solution {
121 BacktrackingSolver::solve_rec(sudoku, 0, 0)
122 }
123}
124
125impl Solver for BacktrackingSolver {
126 fn solve(&self, sudoku: &Sudoku<impl Constraint + Clone>) -> Solution {
127 let mut clone = sudoku.clone();
128 BacktrackingSolver::solve(&mut clone)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134
135 use super::*;
136
137 use crate::constraint::{
138 AdjacentConsecutiveConstraint,
139 CompositeConstraint,
140 DefaultConstraint,
141 DiagonalsConstraint,
142 DiagonallyAdjacentConstraint,
143 KingsMoveConstraint,
144 KnightsMoveConstraint
145 };
146
147 fn test_solves_correctly<C>(puzzle: &str, solution: &str, constraint: C)
148 where
149 C: Constraint + Clone
150 {
151 let sudoku = Sudoku::parse(puzzle, constraint).unwrap();
152 let solver = BacktrackingSolver;
153 let found_solution = solver.solve(&sudoku);
154
155 if let Solution::Unique(grid) = found_solution {
156 let expected_grid = SudokuGrid::parse(solution).unwrap();
157 assert_eq!(expected_grid, grid, "Solver gave wrong grid.");
158 }
159 else {
160 panic!("Solveable sudoku marked as impossible or ambiguous.");
161 }
162 }
163
164 #[test]
183 fn backtracking_solves_classic_sudoku() {
184 let puzzle = "3x3;\
185 , , , ,8,1, , , ,\
186 , ,2, , ,7,8, , ,\
187 ,5,3, , , ,1,7, ,\
188 3,7, , , , , , , ,\
189 6, , , , , , , ,3,\
190 , , , , , , ,2,4,\
191 ,6,9, , , ,2,3, ,\
192 , ,5,9, , ,4, , ,\
193 , , ,6,5, , , , ";
194 let solution = "3x3;\
195 7,4,6,2,8,1,3,5,9,\
196 9,1,2,5,3,7,8,4,6,\
197 8,5,3,4,9,6,1,7,2,\
198 3,7,4,1,2,5,6,9,8,\
199 6,2,8,7,4,9,5,1,3,\
200 5,9,1,3,6,8,7,2,4,\
201 1,6,9,8,7,4,2,3,5,\
202 2,8,5,9,1,3,4,6,7,\
203 4,3,7,6,5,2,9,8,1";
204 test_solves_correctly(puzzle, solution, DefaultConstraint);
205 }
206
207 #[test]
208 fn backtracking_solves_diagonals_sudoku() {
209 let puzzle = "3x3;\
210 ,1,2,3,4,5,6,7, ,\
211 , , , , , , , , ,\
212 , , , , , , , , ,\
213 7, , , , , , , ,5,\
214 2, , , , , , , ,1,\
215 9, , , , , , , ,3,\
216 , , , , , , , , ,\
217 , , , , , , , , ,\
218 ,3,4,5,6,7,8,9, ";
219 let solution = "3x3;\
220 8,1,2,3,4,5,6,7,9,\
221 3,7,5,6,8,9,1,2,4,\
222 4,9,6,1,7,2,3,5,8,\
223 7,4,1,9,3,6,2,8,5,\
224 2,6,3,7,5,8,9,4,1,\
225 9,5,8,4,2,1,7,6,3,\
226 5,2,7,8,9,3,4,1,6,\
227 6,8,9,2,1,4,5,3,7,\
228 1,3,4,5,6,7,8,9,2";
229 test_solves_correctly(puzzle, solution,
230 CompositeConstraint::new(DefaultConstraint, DiagonalsConstraint));
231 }
232
233 #[test]
234 fn backtracking_solves_knights_move_sudoku() {
235 let puzzle = "3x3;\
236 ,8, ,1, ,5, , , ,\
237 4, ,7, ,9, , , , ,\
238 ,1, ,8, , , , , ,\
239 1, ,8, , , , , ,5,\
240 ,7, , , , , ,8, ,\
241 5, , , , , ,3, ,4,\
242 , , , , ,8, ,4, ,\
243 , , , ,3, ,8, ,6,\
244 , , ,5, ,4, ,3, ";
245 let solution = "3x3;\
246 2,8,3,1,4,5,6,9,7,\
247 4,5,7,3,9,6,1,2,8,\
248 9,1,6,8,2,7,4,5,3,\
249 1,3,8,4,7,2,9,6,5,\
250 6,7,4,9,5,3,2,8,1,\
251 5,2,9,6,8,1,3,7,4,\
252 3,9,1,7,6,8,5,4,2,\
253 7,4,5,2,3,9,8,1,6,\
254 8,6,2,5,1,4,7,3,9";
255 test_solves_correctly(puzzle, solution, CompositeConstraint::new(
256 DefaultConstraint, KnightsMoveConstraint));
257 }
258
259 #[test]
260 fn backtracking_solves_kings_move_sudoku() {
261 let puzzle = "3x3;\
262 , , , ,2,1, , , ,\
263 ,6,1, , , , ,3, ,\
264 , , , , ,4, ,7, ,\
265 3, ,7, , , , , , ,\
266 2, , , ,5, , , ,7,\
267 , , , , , ,5, ,8,\
268 ,8, ,1, , , , , ,\
269 ,3, , , , ,6,4, ,\
270 , , ,7,6, , , , ";
271 let solution = "3x3;\
272 5,7,3,9,2,1,4,8,6,\
273 4,6,1,5,8,7,2,3,9,\
274 8,2,9,6,3,4,1,7,5,\
275 3,5,7,2,1,8,9,6,4,\
276 2,9,8,4,5,6,3,1,7,\
277 1,4,6,3,7,9,5,2,8,\
278 6,8,5,1,4,2,7,9,3,\
279 7,3,2,8,9,5,6,4,1,\
280 9,1,4,7,6,3,8,5,2";
281
282 test_solves_correctly(puzzle, solution,
286 CompositeConstraint::new(DefaultConstraint, KingsMoveConstraint));
287 test_solves_correctly(puzzle, solution, CompositeConstraint::new(
288 DefaultConstraint, DiagonallyAdjacentConstraint));
289 }
290
291 #[test]
292 fn backtracking_solves_adjacent_consecutive_sudoku() {
293 let puzzle = "3x3;\
294 , , , , , , , ,7,\
295 , ,3,8, , , , , ,\
296 ,4,6, , , , , , ,\
297 ,7, , ,2, , , , ,\
298 , , ,9,4,7, , , ,\
299 , , , ,8, , ,5, ,\
300 , , , , , , ,9, ,\
301 , , , , ,4,6,2, ,\
302 5, , , , , , , , ";
303 let solution = "3x3;\
304 2,5,8,4,1,6,9,3,7,\
305 7,1,3,8,5,9,2,6,4,\
306 9,4,6,3,7,2,5,8,1,\
307 3,7,1,6,2,5,8,4,9,\
308 8,2,5,9,4,7,3,1,6,\
309 4,6,9,1,8,3,7,5,2,\
310 6,8,2,7,3,1,4,9,5,\
311 1,3,7,5,9,4,6,2,8,\
312 5,9,4,2,6,8,1,7,3";
313 test_solves_correctly(puzzle, solution,CompositeConstraint::new(
314 DefaultConstraint, AdjacentConsecutiveConstraint));
315 }
316}