use super::{MoveLog, Strategy, SudokuSolver};
use crate::board::{Board, BoardSize, CellLoc};
use rayon::prelude::*;
use std::collections::{BTreeSet, HashMap};
pub struct Puzzle {
board: Board,
solution: Board,
guesses: HashMap<CellLoc, BTreeSet<u8>>,
}
impl Board {
pub fn generate(board_size: BoardSize) -> Self {
Puzzle::generate(board_size).board
}
}
impl Puzzle {
pub fn generate(board_size: BoardSize) -> Puzzle {
let mut board = Board::new(board_size);
let mut solver = SudokuSolver::new_random(&mut board);
solver
.solve()
.expect("Should always be possible to solve an empty board");
let non_guesses = solver.move_log.iter().filter_map(|mov| match mov {
MoveLog::SetValue {
strategy: Strategy::Guess,
..
} => None,
MoveLog::SetValue { cell, .. } => Some(cell),
});
for cell in non_guesses {
board.unset(cell);
}
remove_false_guesses(&mut board);
let minimal_board = board;
let mut solved_board = minimal_board.clone();
let mut solver = SudokuSolver::new(&mut solved_board);
solver.solve().expect("A generated board must be solvable");
let givens: BTreeSet<CellLoc> = minimal_board
.iter_cells()
.filter(|cell| minimal_board.get(cell).is_some())
.collect();
let mut guesses = HashMap::new();
for mov in solver.move_log {
if let MoveLog::SetValue {
cell,
value,
strategy: Strategy::Guess,
undo_candidates,
..
} = mov
{
if !givens.contains(&cell) {
let mut options = undo_candidates
.alternative_options()
.as_ref()
.unwrap()
.to_owned();
options.remove(&value);
guesses.insert(cell, options);
}
}
}
Self {
board: minimal_board,
solution: solved_board,
guesses,
}
}
pub fn board(&self) -> &Board {
&self.board
}
pub fn solution(&self) -> &Board {
&self.solution
}
pub fn is_solution_unique(&self) -> bool {
for (cell, options) in self.guesses.iter() {
let has_other_solutions = options.par_iter().any(|value| {
let mut board = self.board.clone();
board.set(cell, *value);
board.solve().is_ok()
});
if has_other_solutions {
return false;
}
}
true
}
}
fn remove_false_guesses(board: &mut Board) {
let cells: Vec<_> = board
.iter_cells()
.filter(|cell| board.get(cell).is_some())
.collect();
for cell in cells {
let value = board.unset(&cell).expect("Guaranteed by the loop above");
let mut possible_values = cell
.get_possible_values(board)
.expect("Guaranteed to be Some by the for loop");
possible_values.remove(&value);
let is_guess = possible_values.par_iter().any(|other_value| {
let mut new_board = board.clone();
new_board.set(&cell, *other_value);
new_board.solve().is_ok()
});
if is_guess {
board.set(&cell, value);
}
}
}