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
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! This module contains the logic for solving Sudoku.
//!
//! Most importantly, this module contains the definition of the [Solver] trait
//! and the [BacktrackingSolver] as a generally usable implementation.

use crate::{Sudoku, SudokuGrid};
use crate::constraint::Constraint;

pub mod strategy;

/// An enumeration of the different ways a Sudoku can be solveable. Note that
/// this may be relative to the solver, since an imperfect solver may be unable
/// to continue at some point, yielding `Solution::Ambiguous`, where the Sudoku
/// is actually uniquely solveable or impossible.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Solution {

    /// Indicates that the Sudoku is not solveable at all.
    Impossible,

    /// Indicates that the Sudoku has a unique solution, which is wrapped in
    /// this instance.
    Unique(SudokuGrid),

    /// Indicates that the Sudoku has multiple solutions or, at least, that the
    /// solver was unable to find a unique one or prove it is impossible.
    Ambiguous
}

impl Solution {

    /// Computes the union of two solutions. This is defined as follows:
    ///
    /// * If one solution is `Solution::Impossible`, the other one is returned.
    /// * If one solution is `Solution::Ambiguous` then the result is also
    /// ambiguous
    /// * If both solutions are `Solution::Unique` with solution grids `g1` and
    /// `g2`, then the result is `Solution::Unique(g1)` if `g1 == g2` and
    /// `Solution::Ambiguous` otherwise.
    pub fn union(self, other: Solution) -> Solution {
        match self {
            Solution::Impossible => other,
            Solution::Unique(g) =>
                match other {
                    Solution::Impossible => Solution::Unique(g),
                    Solution::Unique(other_g) =>
                        if g == other_g {
                            Solution::Unique(g)
                        }
                        else {
                            Solution::Ambiguous
                        }
                    Solution::Ambiguous => Solution::Ambiguous
                }
            Solution::Ambiguous => Solution::Ambiguous
        }
    }
}

/// A trait for structs which have the ability to solve Sudoku. Not all
/// implementers must be able to find a unique solution to every uniquely
/// solveable Sudoku, some solvers may be less powerful, similar to a less
/// experienced human solver. This may make sense to check whether some Sudoku
/// is solveable using some strategy.
pub trait Solver {

    /// Solves, or attempts to solve, the provided Sudoku. If the solver cannot
    /// prove that a Sudoku is impossible or uniquely solveable (either
    /// because it isn't or the solver is not powerful enough), they shall
    /// return `Solution::Ambiguous`.
    fn solve(&self, sudoku: &Sudoku<impl Constraint + Clone>) -> Solution;
}

/// A perfect [Solver] which solves Sudoku by recursively testing all valid
/// numbers for each cell. This means two things:
///
/// * Its worst-case runtime is exponential, i.e. it may be very slow if the
/// Sudoku has many missing digits.
/// * It can provide the correct [Solution] for any Sudoku with any constraint.
pub struct BacktrackingSolver;

impl BacktrackingSolver {
    fn solve_rec(sudoku: &mut Sudoku<impl Constraint + Clone>, column: usize,
            row: usize) -> Solution {
        let size = sudoku.grid().size();
        let last_cell = row == size;

        if last_cell {
            return Solution::Unique(sudoku.grid().clone());
        }

        let next_column = (column + 1) % size;
        let next_row = if next_column == 0 { row + 1 } else { row };

        if sudoku.grid().get_cell(column, row).unwrap().is_some() {
            BacktrackingSolver::solve_rec(sudoku, next_column, next_row)
        }
        else {
            let mut solution = Solution::Impossible;

            for number in 1..=size {
                if sudoku.is_valid_number(column, row, number).unwrap() {
                    sudoku.grid_mut().set_cell(column, row, number).unwrap();
                    let next_solution =
                        BacktrackingSolver::solve_rec(sudoku, next_column,
                            next_row);
                    sudoku.grid_mut().clear_cell(column, row).unwrap();
                    solution = solution.union(next_solution);

                    if solution == Solution::Ambiguous {
                        break;
                    }
                }
            }

            solution
        }
    }

    fn solve(sudoku: &mut Sudoku<impl Constraint + Clone>) -> Solution {
        BacktrackingSolver::solve_rec(sudoku, 0, 0)
    }
}

impl Solver for BacktrackingSolver {
    fn solve(&self, sudoku: &Sudoku<impl Constraint + Clone>) -> Solution {
        let mut clone = sudoku.clone();
        BacktrackingSolver::solve(&mut clone)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use crate::constraint::{
        AdjacentConsecutiveConstraint,
        CompositeConstraint,
        DefaultConstraint,
        DiagonalsConstraint,
        DiagonallyAdjacentConstraint,
        KingsMoveConstraint,
        KnightsMoveConstraint
    };

    fn test_solves_correctly<C>(puzzle: &str, solution: &str, constraint: C)
    where
        C: Constraint + Clone
    {
        let sudoku = Sudoku::parse(puzzle, constraint).unwrap();
        let solver = BacktrackingSolver;
        let found_solution = solver.solve(&sudoku);
        
        if let Solution::Unique(grid) = found_solution {
            let expected_grid = SudokuGrid::parse(solution).unwrap();
            assert_eq!(expected_grid, grid, "Solver gave wrong grid.");
        }
        else {
            panic!("Solveable sudoku marked as impossible or ambiguous.");
        }
    }

    // The example Sudoku are taken from the World Puzzle Federation Sudoku Grand Prix:

    // Classic + Diagonals: GP 2020 Round 8 (Puzzles 2 + 6)
    // Puzzles: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2020/2020_SudokuRound8.pdf
    // Solutions: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2020/2020_SudokuRound8_SB.pdf

    // No Knights Move: GP 2018 Round 1 (Puzzle 8)
    // Puzzle: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2018/2018_SudokuRound1.pdf
    // Solution: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2018/2018_SudokuRound1_SB.pdf

    // No Kings Move: GP 2017 Round 1 (Puzzle 11)
    // Puzzle: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2017/2017_SudokuRound1.pdf
    // Solution: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2017/2017_SudokuRound1_SB.pdf

    // No Adjacent Consecutive: GP 2019 Round 1 (Puzzle 7)
    // Puzzle: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2019/2019_SudokuRound1.pdf
    // Solution: https://gp.worldpuzzle.org/sites/default/files/Puzzles/2019/2019_SudokuRound1_SB.pdf

    #[test]
    fn backtracking_solves_classic_sudoku() {
        let puzzle = "3x3;\
             , , , ,8,1, , , ,\
             , ,2, , ,7,8, , ,\
             ,5,3, , , ,1,7, ,\
            3,7, , , , , , , ,\
            6, , , , , , , ,3,\
             , , , , , , ,2,4,\
             ,6,9, , , ,2,3, ,\
             , ,5,9, , ,4, , ,\
             , , ,6,5, , , , ";
        let solution = "3x3;\
            7,4,6,2,8,1,3,5,9,\
            9,1,2,5,3,7,8,4,6,\
            8,5,3,4,9,6,1,7,2,\
            3,7,4,1,2,5,6,9,8,\
            6,2,8,7,4,9,5,1,3,\
            5,9,1,3,6,8,7,2,4,\
            1,6,9,8,7,4,2,3,5,\
            2,8,5,9,1,3,4,6,7,\
            4,3,7,6,5,2,9,8,1";
        test_solves_correctly(puzzle, solution, DefaultConstraint);
    }

    #[test]
    fn backtracking_solves_diagonals_sudoku() {
        let puzzle = "3x3;\
             ,1,2,3,4,5,6,7, ,\
             , , , , , , , , ,\
             , , , , , , , , ,\
            7, , , , , , , ,5,\
            2, , , , , , , ,1,\
            9, , , , , , , ,3,\
             , , , , , , , , ,\
             , , , , , , , , ,\
             ,3,4,5,6,7,8,9, ";
        let solution = "3x3;\
            8,1,2,3,4,5,6,7,9,\
            3,7,5,6,8,9,1,2,4,\
            4,9,6,1,7,2,3,5,8,\
            7,4,1,9,3,6,2,8,5,\
            2,6,3,7,5,8,9,4,1,\
            9,5,8,4,2,1,7,6,3,\
            5,2,7,8,9,3,4,1,6,\
            6,8,9,2,1,4,5,3,7,\
            1,3,4,5,6,7,8,9,2";
        test_solves_correctly(puzzle, solution,
            CompositeConstraint::new(DefaultConstraint, DiagonalsConstraint));
    }

    #[test]
    fn backtracking_solves_knights_move_sudoku() {
        let puzzle = "3x3;\
             ,8, ,1, ,5, , , ,\
            4, ,7, ,9, , , , ,\
             ,1, ,8, , , , , ,\
            1, ,8, , , , , ,5,\
             ,7, , , , , ,8, ,\
            5, , , , , ,3, ,4,\
             , , , , ,8, ,4, ,\
             , , , ,3, ,8, ,6,\
             , , ,5, ,4, ,3, ";
        let solution = "3x3;\
            2,8,3,1,4,5,6,9,7,\
            4,5,7,3,9,6,1,2,8,\
            9,1,6,8,2,7,4,5,3,\
            1,3,8,4,7,2,9,6,5,\
            6,7,4,9,5,3,2,8,1,\
            5,2,9,6,8,1,3,7,4,\
            3,9,1,7,6,8,5,4,2,\
            7,4,5,2,3,9,8,1,6,\
            8,6,2,5,1,4,7,3,9";
        test_solves_correctly(puzzle, solution, CompositeConstraint::new(
            DefaultConstraint, KnightsMoveConstraint));
    }

    #[test]
    fn backtracking_solves_kings_move_sudoku() {
        let puzzle = "3x3;\
             , , , ,2,1, , , ,\
             ,6,1, , , , ,3, ,\
             , , , , ,4, ,7, ,\
            3, ,7, , , , , , ,\
            2, , , ,5, , , ,7,\
             , , , , , ,5, ,8,\
             ,8, ,1, , , , , ,\
             ,3, , , , ,6,4, ,\
             , , ,7,6, , , , ";
        let solution = "3x3;\
            5,7,3,9,2,1,4,8,6,\
            4,6,1,5,8,7,2,3,9,\
            8,2,9,6,3,4,1,7,5,\
            3,5,7,2,1,8,9,6,4,\
            2,9,8,4,5,6,3,1,7,\
            1,4,6,3,7,9,5,2,8,\
            6,8,5,1,4,2,7,9,3,\
            7,3,2,8,9,5,6,4,1,\
            9,1,4,7,6,3,8,5,2";

        // Since the default constraint is enforced, KingsMoveConstraint and
        // DiagonallyAdjacentConstraint should yield the same result.

        test_solves_correctly(puzzle, solution,
            CompositeConstraint::new(DefaultConstraint, KingsMoveConstraint));
        test_solves_correctly(puzzle, solution, CompositeConstraint::new(
            DefaultConstraint, DiagonallyAdjacentConstraint));
    }

    #[test]
    fn backtracking_solves_adjacent_consecutive_sudoku() {
        let puzzle = "3x3;\
             , , , , , , , ,7,\
             , ,3,8, , , , , ,\
             ,4,6, , , , , , ,\
             ,7, , ,2, , , , ,\
             , , ,9,4,7, , , ,\
             , , , ,8, , ,5, ,\
             , , , , , , ,9, ,\
             , , , , ,4,6,2, ,\
            5, , , , , , , , ";
        let solution = "3x3;\
            2,5,8,4,1,6,9,3,7,\
            7,1,3,8,5,9,2,6,4,\
            9,4,6,3,7,2,5,8,1,\
            3,7,1,6,2,5,8,4,9,\
            8,2,5,9,4,7,3,1,6,\
            4,6,9,1,8,3,7,5,2,\
            6,8,2,7,3,1,4,9,5,\
            1,3,7,5,9,4,6,2,8,\
            5,9,4,2,6,8,1,7,3";
        test_solves_correctly(puzzle, solution,CompositeConstraint::new(
            DefaultConstraint, AdjacentConsecutiveConstraint));
    }
}