Add support for setting max difficulty.
TODO is to support for min difficulty.
This commit is contained in:
parent
6df119d4c9
commit
fa370ed9a9
3 changed files with 881 additions and 630 deletions
|
@ -3,6 +3,59 @@ use rand::prelude::*;
|
||||||
use sudoku_solver::grid::{Grid, CellValue};
|
use sudoku_solver::grid::{Grid, CellValue};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use sudoku_solver::solver::SolveController;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Clone)] // Needed for argparse
|
||||||
|
enum Difficulty {
|
||||||
|
Hard,
|
||||||
|
Medium,
|
||||||
|
Easy
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Difficulty {
|
||||||
|
fn map_to_solve_controller(&self) -> SolveController {
|
||||||
|
let mut controller = SolveController{
|
||||||
|
determine_uniqueness: true,
|
||||||
|
search_singles: true,
|
||||||
|
search_hidden_singles: true,
|
||||||
|
find_possibility_groups: true,
|
||||||
|
search_useful_constraint: true,
|
||||||
|
make_guesses: true
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Difficulty::Hard => {} // Do nothing, already hard
|
||||||
|
Difficulty::Medium => {
|
||||||
|
controller.make_guesses = false;
|
||||||
|
},
|
||||||
|
Difficulty::Easy => {
|
||||||
|
controller.make_guesses = false;
|
||||||
|
controller.search_useful_constraint = false;
|
||||||
|
controller.find_possibility_groups = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Difficulty { // Needed for argparse
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|
||||||
|
if s.eq_ignore_ascii_case("EASY"){
|
||||||
|
return Ok(Difficulty::Easy);
|
||||||
|
} else if s.eq_ignore_ascii_case("MEDIUM"){
|
||||||
|
return Ok(Difficulty::Medium);
|
||||||
|
} else if s.eq_ignore_ascii_case("HARD"){
|
||||||
|
return Ok(Difficulty::Hard);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(format!("{} is not a valid difficulty", s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
||||||
|
@ -13,6 +66,7 @@ fn main() {
|
||||||
let mut max_hints = 81;
|
let mut max_hints = 81;
|
||||||
let mut max_attempts = 100;
|
let mut max_attempts = 100;
|
||||||
let mut filename : Option<String> = None;
|
let mut filename : Option<String> = None;
|
||||||
|
let mut difficulty = Difficulty::Hard;
|
||||||
|
|
||||||
{ // this block limits scope of borrows by ap.refer() method
|
{ // this block limits scope of borrows by ap.refer() method
|
||||||
let mut ap = argparse::ArgumentParser::new();
|
let mut ap = argparse::ArgumentParser::new();
|
||||||
|
@ -21,7 +75,7 @@ fn main() {
|
||||||
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
||||||
|
|
||||||
ap.refer(&mut seed)
|
ap.refer(&mut seed)
|
||||||
.add_option(&["--seed"], argparse::Store, "Provide seed for puzzle generation");
|
.add_option(&["-s", "--seed"], argparse::Store, "Provide seed for puzzle generation");
|
||||||
|
|
||||||
ap.refer(&mut max_hints)
|
ap.refer(&mut max_hints)
|
||||||
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints");
|
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints");
|
||||||
|
@ -32,6 +86,9 @@ fn main() {
|
||||||
ap.refer(&mut filename)
|
ap.refer(&mut filename)
|
||||||
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV");
|
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV");
|
||||||
|
|
||||||
|
ap.refer(&mut difficulty)
|
||||||
|
.add_option(&["-d", "--difficulty"], argparse::Store, "Max difficulty setting; values are EASY, MEDIUM, or HARD");
|
||||||
|
|
||||||
ap.parse_args_or_exit();
|
ap.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +103,10 @@ fn main() {
|
||||||
if debug {
|
if debug {
|
||||||
println!("Using seed {}", seed);
|
println!("Using seed {}", seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let solve_controller = difficulty.map_to_solve_controller();
|
||||||
|
|
||||||
|
|
||||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
|
|
||||||
let mut num_attempts = 0;
|
let mut num_attempts = 0;
|
||||||
|
@ -56,7 +117,7 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (grid, num_hints) = sudoku_solver::generator::generate_grid(&mut rng);
|
let (grid, num_hints) = sudoku_solver::generator::generate_grid(&mut rng, &solve_controller);
|
||||||
num_attempts = num_attempts + 1;
|
num_attempts = num_attempts + 1;
|
||||||
|
|
||||||
if num_hints <= max_hints {
|
if num_hints <= max_hints {
|
||||||
|
|
324
src/generator.rs
324
src/generator.rs
|
@ -1,56 +1,11 @@
|
||||||
use crate::grid::{Cell, Grid, CellValue, Line};
|
use crate::grid::{Cell, Grid, CellValue, Line};
|
||||||
use crate::solver::{solve_grid_no_guess, SolveStatus, find_smallest_cell};
|
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
pub static mut DEBUG : bool = false;
|
pub static mut DEBUG : bool = false;
|
||||||
|
|
||||||
// Extension of SolveStatus
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
|
||||||
pub enum GenerateStatus {
|
|
||||||
UniqueSolution,
|
|
||||||
Unfinished,
|
|
||||||
NoSolution,
|
|
||||||
NotUniqueSolution
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenerateStatus {
|
|
||||||
fn increment(self, new_status : GenerateStatus) -> GenerateStatus {
|
|
||||||
match self {
|
|
||||||
GenerateStatus::UniqueSolution => {
|
|
||||||
match new_status {
|
|
||||||
GenerateStatus::UniqueSolution => GenerateStatus::NotUniqueSolution, // We now have two completes, so the solutions are not unique
|
|
||||||
GenerateStatus::NoSolution => GenerateStatus::UniqueSolution, // We already have a complete, so no issue with another guess being invalid
|
|
||||||
GenerateStatus::Unfinished => {panic!("Should not have encountered an UNFINISHED status")},
|
|
||||||
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution // That solver found multiple solutions so no need to keep checking
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GenerateStatus::Unfinished => {
|
|
||||||
match new_status {
|
|
||||||
GenerateStatus::UniqueSolution => GenerateStatus::UniqueSolution,
|
|
||||||
GenerateStatus::NoSolution => GenerateStatus::Unfinished,
|
|
||||||
GenerateStatus::Unfinished => {panic!("Should not have encountered an UNFINISHED status")},
|
|
||||||
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution // That solver found multiple solutions so no need to keep checking
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution,
|
|
||||||
GenerateStatus::NoSolution => GenerateStatus::NoSolution // This guess didn't pan out
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SolveStatus {
|
|
||||||
fn map_to_generate_status(self) -> GenerateStatus {
|
|
||||||
match self {
|
|
||||||
SolveStatus::Complete => {GenerateStatus::UniqueSolution }
|
|
||||||
SolveStatus::Unfinished => {GenerateStatus::Unfinished }
|
|
||||||
SolveStatus::Invalid => {GenerateStatus::NoSolution }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
fn get_random_empty_cell(&self, rng : &mut ChaCha8Rng) -> Result<Rc<Cell>, &str> {
|
fn get_random_empty_cell(&self, rng : &mut ChaCha8Rng) -> Result<Rc<Cell>, &str> {
|
||||||
// Idea - put all empty cells into a vector and choose one at random
|
// Idea - put all empty cells into a vector and choose one at random
|
||||||
|
@ -153,94 +108,17 @@ impl Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_grid(rng: &mut ChaCha8Rng) -> (Grid, i32) {
|
pub fn generate_grid(rng: &mut ChaCha8Rng, solve_controller: &SolveController) -> (Grid, i32) {
|
||||||
|
|
||||||
let mut num_hints;
|
let mut grid = generate_completed_grid(rng);
|
||||||
let mut grid : Grid = loop {
|
let mut num_hints = 81;
|
||||||
// First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution
|
|
||||||
// We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle
|
|
||||||
// We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it
|
|
||||||
let mut grid = Grid::new();
|
|
||||||
num_hints = 0;
|
|
||||||
|
|
||||||
let digit_excluded = rng.gen_range(1, 10);
|
// We now trim down cells; first going to put them in a vector and shuffle them
|
||||||
|
|
||||||
for digit in 1..10 {
|
|
||||||
if digit != digit_excluded {
|
|
||||||
let cell = grid.get_random_empty_cell(rng);
|
|
||||||
cell.unwrap().set(digit);
|
|
||||||
num_hints = num_hints + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = solve_grid(&mut grid);
|
|
||||||
match status {
|
|
||||||
GenerateStatus::UniqueSolution => { // very surprising result, given that the smallest puzzles found have 14 guesses
|
|
||||||
eprintln!("Wow! A puzzle with only 8 guesses have been found");
|
|
||||||
return (grid, num_hints);
|
|
||||||
}
|
|
||||||
GenerateStatus::Unfinished => {panic!("solve_grid should never return UNFINISHED")}
|
|
||||||
GenerateStatus::NoSolution => {continue;} // unlucky; try again
|
|
||||||
GenerateStatus::NotUniqueSolution => {break grid;}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
|
||||||
grid =
|
|
||||||
'outer: loop {
|
|
||||||
num_hints = num_hints + 1;
|
|
||||||
let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
|
|
||||||
let cell = &*cell;
|
|
||||||
let mut cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities");
|
|
||||||
|
|
||||||
// Let's scramble the order
|
|
||||||
cell_possibilities.shuffle(rng);
|
|
||||||
|
|
||||||
for (_index, digit) in cell_possibilities.iter().enumerate() {
|
|
||||||
|
|
||||||
let grid_clone = grid.clone();
|
|
||||||
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
|
||||||
|
|
||||||
cell.set(*digit);
|
|
||||||
|
|
||||||
let status = solve_grid(&grid_clone);
|
|
||||||
match status {
|
|
||||||
GenerateStatus::UniqueSolution => { // We're done!
|
|
||||||
break 'outer grid_clone;
|
|
||||||
}
|
|
||||||
GenerateStatus::Unfinished => {
|
|
||||||
panic!("solve_grid should never return UNFINISHED")
|
|
||||||
}
|
|
||||||
GenerateStatus::NoSolution => { // Try another guess
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
GenerateStatus::NotUniqueSolution => { // We need more guesses
|
|
||||||
grid = grid_clone;
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we reach this point in the loop, then none of the possibilities for cell provided any solution
|
|
||||||
// Which means something serious happened before in the solving process - reason for panic
|
|
||||||
eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y);
|
|
||||||
panic!("Unable to continue as puzzle is invalid");
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// At this point we have a valid puzzle, but from experience it has way too many guesses, and many of them
|
|
||||||
// are likely not needed. Let's now try removing a bunch.
|
|
||||||
let mut non_empty_cells = Vec::new();
|
let mut non_empty_cells = Vec::new();
|
||||||
for x in 0..9 {
|
for x in 0..9 {
|
||||||
for y in 0..9 {
|
for y in 0..9 {
|
||||||
let cell = grid.get(x, y).unwrap();
|
let cell = grid.get(x, y).unwrap();
|
||||||
let value = &*cell.value.borrow();
|
non_empty_cells.push(Rc::clone(&cell));
|
||||||
match value {
|
|
||||||
CellValue::Fixed(_) => {non_empty_cells.push(Rc::clone(&cell))}
|
|
||||||
CellValue::Unknown(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Need to randomly reorder non_empty_cells
|
// Need to randomly reorder non_empty_cells
|
||||||
|
@ -253,92 +131,132 @@ pub fn generate_grid(rng: &mut ChaCha8Rng) -> (Grid, i32) {
|
||||||
|
|
||||||
cell_clone.delete_value();
|
cell_clone.delete_value();
|
||||||
|
|
||||||
let status = solve_grid(&mut grid_clone);
|
|
||||||
|
let status = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
|
||||||
match status {
|
match status {
|
||||||
GenerateStatus::UniqueSolution => { // great; that cell value was not needed
|
SolveStatus::Complete(uniqueness) => {
|
||||||
|
let uniqueness = uniqueness.unwrap();
|
||||||
|
match uniqueness {
|
||||||
|
Uniqueness::Unique => {
|
||||||
num_hints = num_hints - 1;
|
num_hints = num_hints - 1;
|
||||||
grid = grid_clone;
|
grid = grid_clone;
|
||||||
|
|
||||||
}
|
}
|
||||||
GenerateStatus::Unfinished => {panic!("solve_grid should never return UNFINISHED")}
|
Uniqueness::NotUnique => continue // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller)
|
||||||
GenerateStatus::NoSolution => {panic!("Removing constraints should not have set the # of solutions to zero")}
|
}
|
||||||
GenerateStatus::NotUniqueSolution => {continue;} // We can't remove this cell; continue onto the next one (note that grid hasn't been modified)
|
}
|
||||||
};
|
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED"),
|
||||||
|
SolveStatus::Invalid => panic!("Removing constraints should not have set the # of solutions to zero")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (grid, num_hints);
|
return (grid, num_hints);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve_grid(grid: &Grid) -> GenerateStatus{
|
// We generate a completed grid with no mind for difficulty; afterward generate_puzzle will take out as many fields as it can with regards to the difficulty
|
||||||
// Code is kind of messy so here it goes - solve_grid first tries to solve without any guesses
|
fn generate_completed_grid(rng: &mut ChaCha8Rng) -> Grid {
|
||||||
// If that's not enough and a guess is required, then solve_grid_guess is called
|
let solve_controller = SolveController{
|
||||||
// solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them
|
determine_uniqueness: true,
|
||||||
// through calling this function.
|
search_singles: true,
|
||||||
// solve_grid_no_guess tries to solve without any guesses.
|
search_hidden_singles: true,
|
||||||
|
find_possibility_groups: true,
|
||||||
let mut grid = grid.clone(); // We're generating a result and don't want to make changes to our input
|
search_useful_constraint: true,
|
||||||
|
make_guesses: true
|
||||||
let mut status = solve_grid_no_guess(&mut grid).map_to_generate_status();
|
|
||||||
status = match status {
|
|
||||||
GenerateStatus::Unfinished => {
|
|
||||||
solve_grid_guess(&mut grid)
|
|
||||||
},
|
|
||||||
_ => {status}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut grid : Grid = loop {
|
||||||
|
// First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution
|
||||||
|
// We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle
|
||||||
|
// We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it
|
||||||
|
let grid = Grid::new();
|
||||||
|
|
||||||
|
let digit_excluded = rng.gen_range(1, 10);
|
||||||
|
|
||||||
|
for digit in 1..10 {
|
||||||
|
if digit != digit_excluded {
|
||||||
|
let cell = grid.get_random_empty_cell(rng);
|
||||||
|
cell.unwrap().set(digit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = evaluate_grid_with_solve_controller(&grid, &solve_controller);
|
||||||
match status {
|
match status {
|
||||||
GenerateStatus::Unfinished => panic!("solve_grid_guess should never return UNFINISHED"),
|
SolveStatus::Complete(uniqueness) => {
|
||||||
_ => return status
|
let uniqueness = uniqueness.unwrap();
|
||||||
|
match uniqueness {
|
||||||
|
Uniqueness::Unique => {
|
||||||
|
eprintln!("Wow! A puzzle with only 8 guesses have been found");
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
Uniqueness::NotUnique => {break grid;} // What we expect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SolveStatus::Unfinished => {panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if we are making guesses")}
|
||||||
|
SolveStatus::Invalid => {continue;} // unlucky; try again
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn solve_grid_guess(grid: &Grid) -> GenerateStatus{
|
|
||||||
let smallest_cell = find_smallest_cell(grid);
|
|
||||||
let smallest_cell = match smallest_cell {
|
|
||||||
Some(cell) => cell,
|
|
||||||
None => return GenerateStatus::NoSolution
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
||||||
|
grid =
|
||||||
|
'outer: loop {
|
||||||
|
let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
|
||||||
|
let cell = &*cell;
|
||||||
|
let mut cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities");
|
||||||
|
|
||||||
let mut current_status = GenerateStatus::Unfinished;
|
// Let's scramble the order
|
||||||
|
cell_possibilities.shuffle(rng);
|
||||||
|
|
||||||
for (_index, &digit) in possibilities.iter().enumerate() {
|
for (_index, digit) in cell_possibilities.iter().enumerate() {
|
||||||
let mut grid_copy = grid.clone();
|
|
||||||
grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit);
|
|
||||||
let status = solve_grid(&mut grid_copy);
|
|
||||||
current_status = current_status.increment(status);
|
|
||||||
|
|
||||||
match current_status {
|
let mut grid_clone = grid.clone();
|
||||||
GenerateStatus::NotUniqueSolution => return GenerateStatus::NotUniqueSolution, // We have our answer; return it
|
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
||||||
GenerateStatus::UniqueSolution => {continue}, // Still looking to see if solution is unique
|
|
||||||
GenerateStatus::NoSolution => {panic!("current_status should not be NO_SOLUTION at this point")},
|
cell.set(*digit);
|
||||||
GenerateStatus::Unfinished => {continue} // Still looking for a solution
|
|
||||||
|
let status = evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
|
||||||
|
match status {
|
||||||
|
SolveStatus::Complete(uniqueness) => {
|
||||||
|
let uniqueness = uniqueness.unwrap();
|
||||||
|
match uniqueness {
|
||||||
|
Uniqueness::Unique => {break 'outer grid_clone;} // We're done!
|
||||||
|
Uniqueness::NotUnique => {// We need more guesses
|
||||||
|
grid = grid_clone;
|
||||||
|
continue 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// We've tried all the possibilities for this guess
|
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
|
||||||
match current_status {
|
SolveStatus::Invalid => continue // Try another guess
|
||||||
GenerateStatus::NotUniqueSolution => return current_status,
|
|
||||||
GenerateStatus::Unfinished => return GenerateStatus::NoSolution, // nothing panned out; last guess is a bust
|
|
||||||
GenerateStatus::UniqueSolution => return current_status, // Hey! Looks good!
|
|
||||||
GenerateStatus::NoSolution => {panic!("current_status should not be NO_SOLUTION at this point")}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we reach this point in the loop, then none of the possibilities for cell provided any solution
|
||||||
|
// Which means something serious happened before in the solving process - reason for panic
|
||||||
|
eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y);
|
||||||
|
panic!("Unable to continue as puzzle is invalid");
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::solver::solve_grid(&mut grid);
|
||||||
|
|
||||||
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::grid::*;
|
use crate::grid::*;
|
||||||
use crate::generator::{solve_grid, GenerateStatus};
|
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus};
|
||||||
|
use crate::generator::generate_grid;
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use rand_chacha::rand_core::SeedableRng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unique_detection() {
|
fn test_unique_detection() {
|
||||||
// A puzzle was generated that didn't actually have a unique solution; this is to make sure that the
|
// A puzzle was generated that didn't actually have a unique solution; this is to make sure that the
|
||||||
// modified solving code can actually detect this case
|
// modified solving code can actually detect this case
|
||||||
let grid = Grid::new();
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
grid.get(0, 0).unwrap().set(9);
|
grid.get(0, 0).unwrap().set(9);
|
||||||
grid.get(0, 7).unwrap().set(4);
|
grid.get(0, 7).unwrap().set(4);
|
||||||
|
@ -372,9 +290,51 @@ mod tests {
|
||||||
|
|
||||||
grid.get(8, 2).unwrap().set(6);
|
grid.get(8, 2).unwrap().set(6);
|
||||||
|
|
||||||
let status = solve_grid(&grid);
|
let status = solve_grid_with_solve_controller(&mut grid, &SolveController{
|
||||||
|
determine_uniqueness: true,
|
||||||
|
search_singles: true,
|
||||||
|
search_hidden_singles: true,
|
||||||
|
find_possibility_groups: true,
|
||||||
|
search_useful_constraint: true,
|
||||||
|
make_guesses: true
|
||||||
|
});
|
||||||
|
|
||||||
assert_eq!(status, GenerateStatus::NotUniqueSolution);
|
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was a bug where even though mutate_grid was set to false, the end result was still solved
|
||||||
|
#[test]
|
||||||
|
fn ensure_grid_not_complete(){
|
||||||
|
let solve_controller = SolveController{
|
||||||
|
determine_uniqueness: true,
|
||||||
|
search_singles: true,
|
||||||
|
search_hidden_singles: true,
|
||||||
|
find_possibility_groups: true,
|
||||||
|
search_useful_constraint: true,
|
||||||
|
make_guesses: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note that the puzzle itself doesn't matter
|
||||||
|
let (grid, _num_hints) = generate_grid(&mut ChaCha8Rng::seed_from_u64(123), &solve_controller);
|
||||||
|
|
||||||
|
let mut observed_empty_cell = false;
|
||||||
|
'outer : for x in 0..9 {
|
||||||
|
for y in 0..9 {
|
||||||
|
let cell = grid.get(x, y).unwrap();
|
||||||
|
let value = cell.get_value_copy();
|
||||||
|
|
||||||
|
match value {
|
||||||
|
CellValue::Fixed(_) => {}
|
||||||
|
CellValue::Unknown(_) => {
|
||||||
|
observed_empty_cell = true;
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(observed_empty_cell);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
432
src/solver.rs
432
src/solver.rs
|
@ -1,19 +1,135 @@
|
||||||
|
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::Rc;
|
||||||
use std::cell::{RefCell};
|
use crate::grid::{Cell, Line, Grid, CellValue};
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::grid::{Cell, Line, Grid, CellValue, LineType};
|
|
||||||
|
|
||||||
pub static mut DEBUG: bool = false;
|
pub static mut DEBUG: bool = false;
|
||||||
|
|
||||||
struct FauxCell{
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum Uniqueness {
|
||||||
|
Unique,
|
||||||
|
NotUnique
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Debug)]
|
||||||
|
pub enum SolveStatus {
|
||||||
|
Complete(Option<Uniqueness>),
|
||||||
|
Unfinished,
|
||||||
|
Invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolveStatus {
|
||||||
|
|
||||||
|
fn increment(self, additional_status : SolveStatus) -> SolveStatus {
|
||||||
|
match self {
|
||||||
|
SolveStatus::Complete(uniqueness_option) => {
|
||||||
|
if uniqueness_option.is_none() {
|
||||||
|
return SolveStatus::Complete(None);
|
||||||
|
} else {
|
||||||
|
match uniqueness_option.unwrap() {
|
||||||
|
Uniqueness::NotUnique => SolveStatus::Complete(Some(Uniqueness::NotUnique)),
|
||||||
|
Uniqueness::Unique => match additional_status {
|
||||||
|
SolveStatus::Complete(_) => SolveStatus::Complete(Some(Uniqueness::NotUnique)),
|
||||||
|
SolveStatus::Unfinished => SolveStatus::Complete(Some(Uniqueness::Unique)),
|
||||||
|
SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
SolveStatus::Unfinished => match additional_status {
|
||||||
|
SolveStatus::Invalid => SolveStatus::Unfinished,
|
||||||
|
_ => additional_status
|
||||||
|
},
|
||||||
|
SolveStatus::Invalid => panic!("increment() shouldn't be called on SolveStatus::Invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SolveController {
|
||||||
|
pub determine_uniqueness: bool,
|
||||||
|
pub search_singles: bool,
|
||||||
|
pub search_hidden_singles: bool,
|
||||||
|
pub find_possibility_groups: bool,
|
||||||
|
pub search_useful_constraint: bool,
|
||||||
|
pub make_guesses: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolveController {
|
||||||
|
fn determine_uniqueness(&self) -> bool {
|
||||||
|
self.determine_uniqueness
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_singles(&self) -> bool {
|
||||||
|
self.search_singles
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_hidden_singles(&self) -> bool {
|
||||||
|
// search_hidden_singles is a special case of find_possibility_groups, so if find_possibility_groups
|
||||||
|
// is enabled then it's a waste of resources to keep this on
|
||||||
|
self.search_hidden_singles && !self.find_possibility_groups
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_possibility_groups(&self) -> bool {
|
||||||
|
self.find_possibility_groups
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_useful_constraint(&self) -> bool {
|
||||||
|
self.search_useful_constraint
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_guesses(&self) -> bool {
|
||||||
|
self.make_guesses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{
|
||||||
|
// Find a cell of smallest size (in terms of possibilities) and make a guess
|
||||||
|
// Can assume that no cells of only possibility 1 exist
|
||||||
|
|
||||||
|
let mut smallest_cell : Option<Rc<Cell>> = None;
|
||||||
|
let mut smallest_size = usize::MAX;
|
||||||
|
|
||||||
|
'outer: for x in 0..9 {
|
||||||
|
for y in 0..9 {
|
||||||
|
let cell_rc = grid.get(x, y).unwrap();
|
||||||
|
let cell = &*grid.get(x, y).unwrap();
|
||||||
|
let cell_value = &*cell.value.borrow();
|
||||||
|
|
||||||
|
match cell_value {
|
||||||
|
CellValue::Unknown(possibilities) => {
|
||||||
|
if (possibilities.len() < smallest_size) && (possibilities.len() > 0){
|
||||||
|
smallest_size = possibilities.len();
|
||||||
|
smallest_cell = Some(cell_rc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if smallest_size <= 2 {
|
||||||
|
break 'outer; // We aren't going to get smaller
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smallest_cell
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Code for identify_and_process_possibility_groups (it uses it's own structs)
|
||||||
|
mod process_possibility_groups {
|
||||||
|
use crate::grid::{Line, CellValue};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
struct FauxCell{
|
||||||
index: usize,
|
index: usize,
|
||||||
possibilities: HashSet<u8>,
|
possibilities: HashSet<u8>,
|
||||||
in_group: bool
|
in_group: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FauxCell {
|
impl FauxCell {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.possibilities.len()
|
self.possibilities.len()
|
||||||
}
|
}
|
||||||
|
@ -21,11 +137,11 @@ impl FauxCell {
|
||||||
fn remove(&mut self, to_remove: &HashSet<u8>){
|
fn remove(&mut self, to_remove: &HashSet<u8>){
|
||||||
to_remove.iter().for_each(|digit| {self.possibilities.remove(digit);});
|
to_remove.iter().for_each(|digit| {self.possibilities.remove(digit);});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FauxLine (Vec<FauxCell>);
|
struct FauxLine (Vec<FauxCell>);
|
||||||
|
|
||||||
impl FauxLine {
|
impl FauxLine {
|
||||||
|
|
||||||
fn num_in_group(&self) -> usize {
|
fn num_in_group(&self) -> usize {
|
||||||
self.0.iter().filter(|faux_cell| faux_cell.in_group).count()
|
self.0.iter().filter(|faux_cell| faux_cell.in_group).count()
|
||||||
|
@ -34,21 +150,21 @@ impl FauxLine {
|
||||||
fn num_out_group(&self) -> usize {
|
fn num_out_group(&self) -> usize {
|
||||||
self.0.len() - self.num_in_group()
|
self.0.len() - self.num_in_group()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if there's a set of cells with possibilities that exclude those possibilities from other cells.
|
// See if there's a set of cells with possibilities that exclude those possibilities from other cells.
|
||||||
// Runs recursively on each group to identify all groups in case there's more than 2.
|
// Runs recursively on each group to identify all groups in case there's more than 2.
|
||||||
fn identify_and_process_possibility_groups(line: &Line){
|
pub fn identify_and_process_possibility_groups(line: &Line){
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if super::DEBUG {
|
||||||
println!("Looking for possibility groups on line {:?} {}", line.line_type, line.index);
|
println!("Looking for possibility groups on line {:?} {}", line.line_type, line.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bisect_possibility_groups(line, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
bisect_possibility_groups(line, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Algorithm -
|
Algorithm -
|
||||||
|
@ -215,6 +331,7 @@ fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
||||||
bisect_possibility_groups(line, in_group_indices);
|
bisect_possibility_groups(line, in_group_indices);
|
||||||
bisect_possibility_groups(line, out_group_indices);
|
bisect_possibility_groups(line, out_group_indices);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for a cell with only one possibility so that we can set it to FIXED
|
// Search for a cell with only one possibility so that we can set it to FIXED
|
||||||
|
@ -238,28 +355,78 @@ fn search_single_possibility(line: &Line){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PossibilityLines {
|
// Count up how many times each possibility occurs in the Line. If it only occurs once, that's a hidden single that we can set
|
||||||
|
fn search_hidden_single(line: &Line){
|
||||||
|
enum Count {
|
||||||
|
None,
|
||||||
|
One(Rc<Cell>),
|
||||||
|
Many
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Count {
|
||||||
|
fn increment(&self, cell: Rc<Cell>) -> Count{
|
||||||
|
match self {
|
||||||
|
Count::None => {Count::One(cell)}
|
||||||
|
Count::One(_) => {Count::Many}
|
||||||
|
Count::Many => {Count::Many}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut counts = [Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None];
|
||||||
|
|
||||||
|
for (_index, cell) in line.vec.iter().enumerate() {
|
||||||
|
let value = &*cell.value.borrow();
|
||||||
|
match value {
|
||||||
|
CellValue::Unknown(possibilities) => {
|
||||||
|
for digit in 1..10 {
|
||||||
|
if possibilities.contains(&(digit as u8)){
|
||||||
|
counts[digit-1] = counts[digit-1].increment(Rc::clone(cell));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CellValue::Fixed(_) => {} // do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (digit, count) in counts.iter().enumerate() {
|
||||||
|
match count {
|
||||||
|
Count::One(cell) => {
|
||||||
|
cell.set((digit + 1) as u8);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod search_useful_constraint{
|
||||||
|
use crate::grid::{Grid, Line, LineType, CellValue};
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
enum PossibilityLines {
|
||||||
Unique(usize),
|
Unique(usize),
|
||||||
Invalid,
|
Invalid,
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PossibilityLines {
|
impl PossibilityLines {
|
||||||
fn is_invalid(&self) -> bool {
|
fn is_invalid(&self) -> bool {
|
||||||
match &self {
|
match &self {
|
||||||
PossibilityLines::Invalid => true,
|
PossibilityLines::Invalid => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all the cells for a particular possibility share a same other Line, they can remove that possibility from other cells in the main line.
|
// If all the cells for a particular possibility share a same other Line, they can remove that possibility from other cells in the main line.
|
||||||
// I.e. If possibility '1' only occurs in the first row for section 0, then you can remove that possibility
|
// I.e. If possibility '1' only occurs in the first row for section 0, then you can remove that possibility
|
||||||
// from row 0 across the other sections. Conversely, if the possibility only occurs in the first section
|
// from row 0 across the other sections. Conversely, if the possibility only occurs in the first section
|
||||||
// for row 0, then you can remove the possibility from the rest of section 0.
|
// for row 0, then you can remove the possibility from the rest of section 0.
|
||||||
fn search_useful_constraint(grid: &Grid, line: &Line){
|
pub fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if super::DEBUG {
|
||||||
println!("Searching for a useful constraint on line {:?} {}", line.line_type, line.index);
|
println!("Searching for a useful constraint on line {:?} {}", line.line_type, line.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,10 +492,10 @@ fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial_line_type and initial_line_index are to identify the cells that should NOT have their possibilities removed
|
// initial_line_type and initial_line_index are to identify the cells that should NOT have their possibilities removed
|
||||||
fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, initial_line_type: &LineType, initial_line_index: usize) {
|
fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, initial_line_type: &LineType, initial_line_index: usize) {
|
||||||
let line = &*(&**line).borrow();
|
let line = &*(&**line).borrow();
|
||||||
|
|
||||||
for (_index, cell) in line.vec.iter().enumerate() {
|
for (_index, cell) in line.vec.iter().enumerate() {
|
||||||
|
@ -373,10 +540,10 @@ fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, init
|
||||||
cell.set_value(new_value);
|
cell.set_value(new_value);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We detected
|
// We detected a useful constraint
|
||||||
fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefCell<Line>>) -> PossibilityLines {
|
fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefCell<Line>>) -> PossibilityLines {
|
||||||
let line = line.upgrade().unwrap();
|
let line = line.upgrade().unwrap();
|
||||||
let line = &*(&*line).borrow();
|
let line = &*(&*line).borrow();
|
||||||
|
|
||||||
|
@ -391,10 +558,12 @@ fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn solve_line(grid: &Grid, line: &Line){
|
fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("Solving {:?} {}", line.line_type, line.index);
|
println!("Solving {:?} {}", line.line_type, line.index);
|
||||||
|
@ -403,91 +572,89 @@ fn solve_line(grid: &Grid, line: &Line){
|
||||||
|
|
||||||
line.do_update.replace(false);
|
line.do_update.replace(false);
|
||||||
|
|
||||||
|
if solve_controller.search_singles() {
|
||||||
|
unsafe {
|
||||||
|
if DEBUG {
|
||||||
|
println!("Searching for singles on line {:?} of {}\n{}", line.line_type, line.index, grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
search_single_possibility(line);
|
search_single_possibility(line);
|
||||||
unsafe {
|
|
||||||
if DEBUG {
|
|
||||||
println!("{}", grid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
identify_and_process_possibility_groups(line);
|
if solve_controller.search_hidden_singles() {
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("{}", grid);
|
println!("Searching for hidden singles on line {:?} of {}\n{}", line.line_type, line.index, grid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
search_hidden_single(line);
|
||||||
|
}
|
||||||
|
|
||||||
search_useful_constraint(grid, line);
|
if solve_controller.find_possibility_groups() {
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("{}", grid);
|
println!("Searching for possibility groups on line {:?} of {}\n{}", line.line_type, line.index, grid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
process_possibility_groups::identify_and_process_possibility_groups(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if solve_controller.search_useful_constraint() {
|
||||||
|
unsafe {
|
||||||
|
if DEBUG {
|
||||||
|
println!("Searching for useful constraints on line {:?} of {}\n{}", line.line_type, line.index, grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search_useful_constraint::search_useful_constraint(grid, line);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{
|
pub fn solve_grid(grid: &mut Grid) -> SolveStatus {
|
||||||
// Find a cell of smallest size (in terms of possibilities) and make a guess
|
// By default we enable everything
|
||||||
// Can assume that no cells of only possibility 1 exist
|
let solve_controller = SolveController {
|
||||||
|
determine_uniqueness: true,
|
||||||
|
search_singles: true,
|
||||||
|
search_hidden_singles: true,
|
||||||
|
find_possibility_groups: true,
|
||||||
|
search_useful_constraint: true,
|
||||||
|
make_guesses: true
|
||||||
|
};
|
||||||
|
|
||||||
let mut smallest_cell : Option<Rc<Cell>> = None;
|
solve_grid_with_solve_controller(grid, &solve_controller)
|
||||||
let mut smallest_size = usize::MAX;
|
|
||||||
|
|
||||||
'outer: for x in 0..9 {
|
|
||||||
for y in 0..9 {
|
|
||||||
let cell_rc = grid.get(x, y).unwrap();
|
|
||||||
let cell = &*grid.get(x, y).unwrap();
|
|
||||||
let cell_value = &*cell.value.borrow();
|
|
||||||
|
|
||||||
match cell_value {
|
|
||||||
CellValue::Unknown(possibilities) => {
|
|
||||||
if (possibilities.len() < smallest_size) && (possibilities.len() > 0){
|
|
||||||
smallest_size = possibilities.len();
|
|
||||||
smallest_cell = Some(cell_rc);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if smallest_size <= 2 {
|
|
||||||
break 'outer; // We aren't going to get smaller
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
smallest_cell
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &SolveController) -> SolveStatus{
|
||||||
pub enum SolveStatus {
|
|
||||||
Complete,
|
|
||||||
Unfinished,
|
|
||||||
Invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn solve_grid(grid: &mut Grid) -> SolveStatus{
|
|
||||||
// Code is kind of messy so here it goes - solve_grid first tries to solve without any guesses
|
// Code is kind of messy so here it goes - solve_grid first tries to solve without any guesses
|
||||||
// If that's not enough and a guess is required, then solve_grid_guess is called
|
// If that's not enough and a guess is required, then solve_grid_guess is called
|
||||||
// solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them
|
// solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them
|
||||||
// through calling this function.
|
// through calling this function.
|
||||||
// solve_grid_no_guess tries to solve without any guesses.
|
// solve_grid_no_guess tries to solve without any guesses.
|
||||||
|
// Of course this is if the solve_controller lets everything be used for solving it
|
||||||
|
|
||||||
let mut status = solve_grid_no_guess(grid);
|
let mut status = solve_grid_no_guess(grid, solve_controller);
|
||||||
status = match status {
|
status = match status {
|
||||||
SolveStatus::Unfinished => {
|
SolveStatus::Unfinished => {
|
||||||
solve_grid_guess(grid)
|
if solve_controller.make_guesses() {
|
||||||
|
solve_grid_guess(grid, solve_controller)
|
||||||
|
} else {
|
||||||
|
SolveStatus::Complete(Some(Uniqueness::NotUnique)) // solve_grid_no_guess couldn't finish and we can't make guesses, so it's 'not unique' in the sense that we need more guesses
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {status}
|
_ => {status}
|
||||||
};
|
};
|
||||||
|
|
||||||
match status {
|
return status;
|
||||||
SolveStatus::Unfinished => panic!("solve_grid_guess should never return UNFINISHED"),
|
|
||||||
_ => return status
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
// Similar to solve_grid_with_solve_controller except that we don't modify the input Grid; we only determine SolveStatus
|
||||||
|
pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &SolveController) -> SolveStatus{
|
||||||
|
let mut mut_grid = grid.clone();
|
||||||
|
return solve_grid_with_solve_controller(&mut mut_grid, solve_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController) -> SolveStatus{
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut ran_something = false;
|
let mut ran_something = false;
|
||||||
|
@ -495,7 +662,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
//println!("Processing row {}", _index);
|
//println!("Processing row {}", _index);
|
||||||
let line_ref = &*(&**line_ref).borrow();
|
let line_ref = &*(&**line_ref).borrow();
|
||||||
if line_ref.do_update() {
|
if line_ref.do_update() {
|
||||||
solve_line(&grid, line_ref);
|
solve_line(&grid, line_ref, solve_controller);
|
||||||
ran_something = true;
|
ran_something = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,7 +670,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
//println!("Processing column {}", _index);
|
//println!("Processing column {}", _index);
|
||||||
let line_ref = &*(&**line_ref).borrow();
|
let line_ref = &*(&**line_ref).borrow();
|
||||||
if line_ref.do_update() {
|
if line_ref.do_update() {
|
||||||
solve_line(&grid, line_ref);
|
solve_line(&grid, line_ref, solve_controller);
|
||||||
ran_something = true;
|
ran_something = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,7 +678,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
//println!("Processing section {}", _index);
|
//println!("Processing section {}", _index);
|
||||||
let line_ref = &*(&**line_ref).borrow();
|
let line_ref = &*(&**line_ref).borrow();
|
||||||
if line_ref.do_update() {
|
if line_ref.do_update() {
|
||||||
solve_line(&grid, line_ref);
|
solve_line(&grid, line_ref, solve_controller);
|
||||||
ran_something = true;
|
ran_something = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,13 +709,15 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
}
|
}
|
||||||
|
|
||||||
if appears_complete {
|
if appears_complete {
|
||||||
return SolveStatus::Complete;
|
// Solving by logic rules only implies Uniqueness;
|
||||||
|
// may be overridden if guesses were made
|
||||||
|
return SolveStatus::Complete(Some(Uniqueness::Unique));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve_grid_guess(grid: &mut Grid) -> SolveStatus{
|
fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController) -> SolveStatus{
|
||||||
let smallest_cell = find_smallest_cell(grid);
|
let smallest_cell = find_smallest_cell(grid);
|
||||||
let smallest_cell = match smallest_cell {
|
let smallest_cell = match smallest_cell {
|
||||||
Some(cell) => cell,
|
Some(cell) => cell,
|
||||||
|
@ -556,26 +725,60 @@ fn solve_grid_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
};
|
};
|
||||||
|
|
||||||
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
||||||
|
|
||||||
|
let mut current_status = SolveStatus::Unfinished;
|
||||||
|
let mut grid_solution = None;
|
||||||
|
|
||||||
for (_index, &digit) in possibilities.iter().enumerate() {
|
for (_index, &digit) in possibilities.iter().enumerate() {
|
||||||
|
|
||||||
let mut grid_copy = grid.clone();
|
let mut grid_copy = grid.clone();
|
||||||
grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit);
|
grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit);
|
||||||
let status = solve_grid(&mut grid_copy);
|
let status = solve_grid_with_solve_controller(&mut grid_copy, solve_controller);
|
||||||
|
|
||||||
|
// Keep a copy of grid_copy in case we later mutate grid with it
|
||||||
match status {
|
match status {
|
||||||
SolveStatus::Complete => {
|
SolveStatus::Complete(_) => {
|
||||||
grid.clone_from(&grid_copy);
|
grid_solution = Some(grid_copy);
|
||||||
return SolveStatus::Complete;
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_status = current_status.increment(status);
|
||||||
|
|
||||||
|
match current_status {
|
||||||
|
SolveStatus::Complete(uniqueness) => {
|
||||||
|
if !solve_controller.determine_uniqueness() {
|
||||||
|
current_status = SolveStatus::Complete(None); // be explicit we don't know
|
||||||
|
break; // no point in continuing
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniqueness = uniqueness.expect("We're looking for uniqueness and yet an earlier function didn't make a claim");
|
||||||
|
match uniqueness {
|
||||||
|
Uniqueness::Unique => {continue;} // gotta keep on checking
|
||||||
|
Uniqueness::NotUnique => {
|
||||||
|
break; // We can stop looking as we already found at least two solutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SolveStatus::Unfinished => {continue} // Keep looking for a solution
|
||||||
|
SolveStatus::Invalid => panic!("current_status should not be INVALID at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've finished the for-loop
|
||||||
|
match current_status {
|
||||||
|
SolveStatus::Complete(_) => {
|
||||||
|
grid.clone_from(&grid_solution.expect("grid_solution should have value if we found a solution"));
|
||||||
},
|
},
|
||||||
SolveStatus::Unfinished => {
|
SolveStatus::Unfinished => {
|
||||||
panic!("solve_grid should never return UNFINISHED")
|
current_status = SolveStatus::Invalid; // We can now say Invalid
|
||||||
},
|
},
|
||||||
SolveStatus::Invalid => {
|
SolveStatus::Invalid => {}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SolveStatus::Invalid;
|
return current_status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,7 +822,7 @@ mod tests {
|
||||||
let line = grid.rows.first().unwrap();
|
let line = grid.rows.first().unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
identify_and_process_possibility_groups(line);
|
process_possibility_groups::identify_and_process_possibility_groups(line);
|
||||||
|
|
||||||
assert_eq!(CellValue::Unknown(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy());
|
||||||
}
|
}
|
||||||
|
@ -643,7 +846,7 @@ mod tests {
|
||||||
let line = grid.rows.first().unwrap();
|
let line = grid.rows.first().unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
search_useful_constraint(&grid, line);
|
search_useful_constraint::search_useful_constraint(&grid, line);
|
||||||
|
|
||||||
assert_eq!(CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy());
|
||||||
}
|
}
|
||||||
|
@ -676,11 +879,38 @@ mod tests {
|
||||||
let line = grid.columns.get(1).unwrap();
|
let line = grid.columns.get(1).unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
search_useful_constraint(&grid, line);
|
search_useful_constraint::search_useful_constraint(&grid, line);
|
||||||
|
|
||||||
assert_eq!(CellValue::Unknown(vec![1, 3, 4, 5, 9]), grid.get(1, 0).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![1, 3, 4, 5, 9]), grid.get(1, 0).unwrap().get_value_copy());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hidden_single() {
|
||||||
|
let grid = Grid::new();
|
||||||
|
|
||||||
|
// In Row 0 there should be only one spot for 1s and 2s to be set, even though every cell will
|
||||||
|
// have possibilities for values 3 - 9
|
||||||
|
|
||||||
|
grid.get(1, 5).unwrap().set(1);
|
||||||
|
grid.get(2, 6).unwrap().set(1);
|
||||||
|
grid.get(5, 2).unwrap().set(1);
|
||||||
|
grid.get(6, 1).unwrap().set(1);
|
||||||
|
|
||||||
|
grid.get(1, 6).unwrap().set(2);
|
||||||
|
grid.get(2, 5).unwrap().set(2);
|
||||||
|
grid.get(5, 1).unwrap().set(2);
|
||||||
|
grid.get(6, 0).unwrap().set(2);
|
||||||
|
|
||||||
|
let first_row = grid.rows.get(0).unwrap();
|
||||||
|
let first_row = &*(**first_row).borrow();
|
||||||
|
|
||||||
|
search_hidden_single(&first_row);
|
||||||
|
|
||||||
|
assert_eq!(CellValue::Fixed(1), grid.get(0, 0).unwrap().get_value_copy());
|
||||||
|
assert_eq!(CellValue::Fixed(2), grid.get(0, 2).unwrap().get_value_copy());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue