Run cargo fmt
This commit is contained in:
parent
7d4e474731
commit
8dc7954f53
7 changed files with 642 additions and 413 deletions
|
@ -1,13 +1,13 @@
|
|||
use rand::prelude::*;
|
||||
use sudoku_solver::grid::{Grid, CellValue};
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::process::exit;
|
||||
use std::thread;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::thread;
|
||||
use sudoku_solver::grid::{CellValue, Grid};
|
||||
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
||||
|
||||
/*
|
||||
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
|
||||
|
@ -24,7 +24,7 @@ enum Difficulty {
|
|||
Challenge,
|
||||
Hard,
|
||||
Medium,
|
||||
Easy
|
||||
Easy,
|
||||
}
|
||||
|
||||
impl Difficulty {
|
||||
|
@ -35,17 +35,17 @@ impl Difficulty {
|
|||
search_hidden_singles: true,
|
||||
find_possibility_groups: true,
|
||||
search_useful_constraint: true,
|
||||
make_guesses: true
|
||||
make_guesses: true,
|
||||
};
|
||||
|
||||
match self {
|
||||
Difficulty::Challenge => {}, // Do nothing, already hard
|
||||
Difficulty::Challenge => {} // Do nothing, already hard
|
||||
Difficulty::Hard => {
|
||||
controller.make_guesses = false;
|
||||
}
|
||||
Difficulty::Medium => {
|
||||
controller.make_guesses = false;
|
||||
},
|
||||
}
|
||||
Difficulty::Easy => {
|
||||
controller.make_guesses = false;
|
||||
controller.search_useful_constraint = false;
|
||||
|
@ -59,24 +59,28 @@ impl Difficulty {
|
|||
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
|
||||
match self {
|
||||
Difficulty::Challenge => {
|
||||
(solve_statistics.guesses > 0) && (solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
|
||||
(solve_statistics.guesses > 0)
|
||||
&& (solve_statistics.possibility_groups > 20)
|
||||
&& (solve_statistics.useful_constraints > 20)
|
||||
}
|
||||
Difficulty::Hard => {
|
||||
(solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
|
||||
(solve_statistics.possibility_groups > 20)
|
||||
&& (solve_statistics.useful_constraints > 20)
|
||||
}
|
||||
Difficulty::Medium => {
|
||||
(solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10)
|
||||
(solve_statistics.possibility_groups > 10)
|
||||
&& (solve_statistics.useful_constraints > 10)
|
||||
}
|
||||
Difficulty::Easy => {true} // easy has no minimum
|
||||
Difficulty::Easy => true, // easy has no minimum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Difficulty { // Needed for argparse
|
||||
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") {
|
||||
|
@ -92,7 +96,6 @@ impl FromStr for Difficulty { // Needed for argparse
|
|||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
let mut debug = false;
|
||||
let mut max_hints = 81;
|
||||
let mut max_attempts = 100;
|
||||
|
@ -100,41 +103,67 @@ fn main() {
|
|||
let mut difficulty = Difficulty::Challenge;
|
||||
let mut threads = 1;
|
||||
|
||||
{ // 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();
|
||||
ap.set_description("Generate Sudoku puzzles");
|
||||
ap.refer(&mut debug)
|
||||
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
||||
|
||||
ap.refer(&mut max_hints)
|
||||
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of 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",
|
||||
);
|
||||
|
||||
ap.refer(&mut max_attempts)
|
||||
.add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100");
|
||||
|
||||
ap.refer(&mut filename)
|
||||
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV");
|
||||
ap.refer(&mut filename).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, HARD, or CHALLENGE");
|
||||
ap.refer(&mut difficulty).add_option(
|
||||
&["-d", "--difficulty"],
|
||||
argparse::Store,
|
||||
"Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE",
|
||||
);
|
||||
|
||||
ap.refer(&mut threads)
|
||||
.add_option(&["--threads"], argparse::Store, "Number of threads to use when generating possible puzzles");
|
||||
ap.refer(&mut threads).add_option(
|
||||
&["--threads"],
|
||||
argparse::Store,
|
||||
"Number of threads to use when generating possible puzzles",
|
||||
);
|
||||
|
||||
ap.parse_args_or_exit();
|
||||
}
|
||||
|
||||
let solve_controller = difficulty.map_to_solve_controller();
|
||||
|
||||
let (result, num_attempts) =
|
||||
if threads < 1 {
|
||||
let (result, num_attempts) = if threads < 1 {
|
||||
eprintln!("--threads must be at least 1");
|
||||
exit(1);
|
||||
} else if threads == 1 {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints, &AtomicBool::new(false))
|
||||
get_puzzle_matching_conditions(
|
||||
&mut rng,
|
||||
&difficulty,
|
||||
&solve_controller,
|
||||
max_attempts,
|
||||
max_hints,
|
||||
&AtomicBool::new(false),
|
||||
)
|
||||
} else {
|
||||
run_multi_threaded(max_attempts, max_hints, threads, debug, solve_controller, difficulty)
|
||||
run_multi_threaded(
|
||||
max_attempts,
|
||||
max_hints,
|
||||
threads,
|
||||
debug,
|
||||
solve_controller,
|
||||
difficulty,
|
||||
)
|
||||
};
|
||||
|
||||
let (grid, solve_statistics, num_hints) = match result {
|
||||
|
@ -145,16 +174,27 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
println!("{}", grid);
|
||||
println!("Puzzle has {} hints and was found in {} attempts.", num_hints, num_attempts);
|
||||
println!(
|
||||
"Puzzle has {} hints and was found in {} attempts.",
|
||||
num_hints, num_attempts
|
||||
);
|
||||
|
||||
if debug {
|
||||
println!("Solving this puzzle involves roughly:");
|
||||
println!("\t{} SINGLE actions", solve_statistics.singles);
|
||||
println!("\t{} HIDDEN_SINGLE actions", solve_statistics.hidden_singles);
|
||||
println!("\t{} USEFUL_CONSTRAINT actions", solve_statistics.useful_constraints);
|
||||
println!("\t{} POSSIBILITY_GROUP actions", solve_statistics.possibility_groups);
|
||||
println!(
|
||||
"\t{} HIDDEN_SINGLE actions",
|
||||
solve_statistics.hidden_singles
|
||||
);
|
||||
println!(
|
||||
"\t{} USEFUL_CONSTRAINT actions",
|
||||
solve_statistics.useful_constraints
|
||||
);
|
||||
println!(
|
||||
"\t{} POSSIBILITY_GROUP actions",
|
||||
solve_statistics.possibility_groups
|
||||
);
|
||||
println!("\t{} GUESS actions", solve_statistics.guesses);
|
||||
}
|
||||
|
||||
|
@ -168,13 +208,19 @@ fn main() {
|
|||
save_grid_csv(&grid, &filename).unwrap();
|
||||
println!("Grid saved as CSV to {}", filename);
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bool, solve_controller: SolveController, difficulty: Difficulty) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
||||
fn run_multi_threaded(
|
||||
max_attempts: i32,
|
||||
max_hints: i32,
|
||||
threads: i32,
|
||||
debug: bool,
|
||||
solve_controller: SolveController,
|
||||
difficulty: Difficulty,
|
||||
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
|
||||
let mut thread_rng = thread_rng();
|
||||
let (transmitter, receiver) = mpsc::channel();
|
||||
let mut remaining_attempts = max_attempts;
|
||||
|
@ -195,11 +241,18 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
|||
}
|
||||
|
||||
let should_stop = &*should_stop;
|
||||
let (result, num_attempts) = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, thread_attempts, max_hints, should_stop);
|
||||
let (result, num_attempts) = get_puzzle_matching_conditions(
|
||||
&mut rng,
|
||||
&difficulty,
|
||||
&solve_controller,
|
||||
thread_attempts,
|
||||
max_hints,
|
||||
should_stop,
|
||||
);
|
||||
|
||||
let mut result_was_some = false;
|
||||
let result = match result {
|
||||
None => {None}
|
||||
None => None,
|
||||
Some((grid, solve_statistics, num_hints)) => {
|
||||
result_was_some = true;
|
||||
Some((SafeGridWrapper(grid), solve_statistics, num_hints))
|
||||
|
@ -209,7 +262,10 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
|||
cloned_transmitter.send((result, num_attempts)).unwrap();
|
||||
|
||||
if debug {
|
||||
println!("Thread {}, terminated having run {} attempts; did send result: {}", i, num_attempts, result_was_some);
|
||||
println!(
|
||||
"Thread {}, terminated having run {} attempts; did send result: {}",
|
||||
i, num_attempts, result_was_some
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -237,12 +293,19 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
|||
return (result_to_return, attempt_count);
|
||||
}
|
||||
|
||||
fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32, should_stop: &AtomicBool) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
||||
fn get_puzzle_matching_conditions(
|
||||
rng: &mut SmallRng,
|
||||
difficulty: &Difficulty,
|
||||
solve_controller: &SolveController,
|
||||
max_attempts: i32,
|
||||
max_hints: i32,
|
||||
should_stop: &AtomicBool,
|
||||
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
|
||||
let mut num_attempts = 0;
|
||||
|
||||
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) {
|
||||
|
||||
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
||||
let (grid, num_hints, solve_statistics) =
|
||||
sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
||||
num_attempts += 1;
|
||||
|
||||
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
|
||||
|
@ -261,10 +324,9 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
|||
for y in 0..9 {
|
||||
let cell = grid.get(x, y).unwrap();
|
||||
let value = &*cell.value.borrow();
|
||||
let digit =
|
||||
match value {
|
||||
CellValue::Fixed(digit) => {*digit}
|
||||
CellValue::Unknown(_) => {0}
|
||||
let digit = match value {
|
||||
CellValue::Fixed(digit) => *digit,
|
||||
CellValue::Unknown(_) => 0,
|
||||
};
|
||||
|
||||
let mut text = digit.to_string();
|
||||
|
@ -272,7 +334,6 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
|||
text.push(',');
|
||||
}
|
||||
file.write(text.as_bytes())?;
|
||||
|
||||
}
|
||||
file.write(b"\n")?;
|
||||
}
|
||||
|
|
|
@ -3,19 +3,21 @@ use std::str::FromStr;
|
|||
use sudoku_solver::grid::Grid;
|
||||
use sudoku_solver::solver::solve_grid;
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut debug = false;
|
||||
let mut filename = String::new();
|
||||
{ // 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();
|
||||
ap.set_description("Solve Sudoku puzzles");
|
||||
ap.refer(&mut debug)
|
||||
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
||||
|
||||
ap.refer(&mut filename)
|
||||
.required()
|
||||
.add_argument("filename", argparse::Store, "Path to puzzle CSV file");
|
||||
ap.refer(&mut filename).required().add_argument(
|
||||
"filename",
|
||||
argparse::Store,
|
||||
"Path to puzzle CSV file",
|
||||
);
|
||||
|
||||
ap.parse_args_or_exit();
|
||||
}
|
||||
|
@ -27,7 +29,6 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
let mut grid = match read_grid(&filename) {
|
||||
Ok(grid) => grid,
|
||||
Err(e) => {
|
||||
|
@ -42,8 +43,6 @@ fn main() {
|
|||
solve_grid(&mut grid);
|
||||
|
||||
println!("Solved grid:\n{}", grid);
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn read_grid(filename: &str) -> Result<Grid, String> {
|
||||
|
@ -78,18 +77,15 @@ fn read_grid(filename: &str) -> Result<Grid, String> {
|
|||
if digit > 0 {
|
||||
grid.get(row, column).unwrap().set(digit);
|
||||
}
|
||||
|
||||
},
|
||||
Err(_error) => {return Err("Invalid cell value".to_string())}
|
||||
}
|
||||
Err(_error) => return Err("Invalid cell value".to_string()),
|
||||
};
|
||||
|
||||
},
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
row = row + 1;
|
||||
|
||||
}
|
||||
|
||||
return Ok(grid);
|
||||
|
|
129
src/generator.rs
129
src/generator.rs
|
@ -1,7 +1,9 @@
|
|||
use crate::grid::{Cell, Grid, CellValue, Section};
|
||||
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics};
|
||||
use std::rc::Rc;
|
||||
use crate::grid::{Cell, CellValue, Grid, Section};
|
||||
use crate::solver::{
|
||||
evaluate_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub static mut DEBUG: bool = false;
|
||||
|
||||
|
@ -16,11 +18,10 @@ impl Grid {
|
|||
let cell = self.get(x, y).unwrap();
|
||||
let add_cell = {
|
||||
let cell_value = &*cell.value.borrow();
|
||||
match cell_value { // May cause issues with borrow rules
|
||||
CellValue::Fixed(_) => {false}
|
||||
CellValue::Unknown(_) => {
|
||||
true
|
||||
}
|
||||
match cell_value {
|
||||
// May cause issues with borrow rules
|
||||
CellValue::Fixed(_) => false,
|
||||
CellValue::Unknown(_) => true,
|
||||
}
|
||||
};
|
||||
if add_cell {
|
||||
|
@ -31,7 +32,7 @@ impl Grid {
|
|||
|
||||
match empty_cells.iter().choose(rng) {
|
||||
Some(cell) => Ok(Rc::clone(cell)),
|
||||
None => Err("Unable to find an empty cell")
|
||||
None => Err("Unable to find an empty cell"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,10 +48,21 @@ impl Cell {
|
|||
self.set_value_exact(CellValue::Unknown(vec![])); // placeholder
|
||||
|
||||
// This will reset all the possibilities for this cell and the ones that might have been limited by this cell
|
||||
self.section.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
||||
self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
||||
self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
||||
|
||||
self.section
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.recalculate_and_set_possibilities();
|
||||
self.row
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.recalculate_and_set_possibilities();
|
||||
self.column
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.recalculate_and_set_possibilities();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,9 +92,21 @@ impl Cell {
|
|||
}
|
||||
}
|
||||
|
||||
eliminate_possibilities(&mut possibilities, &self.section.upgrade().unwrap().borrow(), self);
|
||||
eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self);
|
||||
eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self);
|
||||
eliminate_possibilities(
|
||||
&mut possibilities,
|
||||
&self.section.upgrade().unwrap().borrow(),
|
||||
self,
|
||||
);
|
||||
eliminate_possibilities(
|
||||
&mut possibilities,
|
||||
&self.row.upgrade().unwrap().borrow(),
|
||||
self,
|
||||
);
|
||||
eliminate_possibilities(
|
||||
&mut possibilities,
|
||||
&self.column.upgrade().unwrap().borrow(),
|
||||
self,
|
||||
);
|
||||
|
||||
return possibilities;
|
||||
}
|
||||
|
@ -95,10 +119,10 @@ impl Section {
|
|||
let new_possibilities = {
|
||||
let cell_value = &*cell.value.borrow();
|
||||
match cell_value {
|
||||
CellValue::Fixed(_) => { continue; }
|
||||
CellValue::Unknown(_) => {
|
||||
cell.calculate_possibilities()
|
||||
CellValue::Fixed(_) => {
|
||||
continue;
|
||||
}
|
||||
CellValue::Unknown(_) => cell.calculate_possibilities(),
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -107,8 +131,10 @@ impl Section {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> (Grid, i32, SolveStatistics) {
|
||||
|
||||
pub fn generate_grid(
|
||||
rng: &mut SmallRng,
|
||||
solve_controller: &SolveController,
|
||||
) -> (Grid, i32, SolveStatistics) {
|
||||
let mut grid = generate_completed_grid(rng);
|
||||
let mut num_hints = 81;
|
||||
|
||||
|
@ -132,8 +158,8 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
|
|||
|
||||
cell_clone.delete_value();
|
||||
|
||||
|
||||
let (status, statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
|
||||
let (status, statistics) =
|
||||
evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
|
||||
match status {
|
||||
SolveStatus::Complete(uniqueness) => {
|
||||
let uniqueness = uniqueness.unwrap();
|
||||
|
@ -142,17 +168,20 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
|
|||
num_hints = num_hints - 1;
|
||||
grid = grid_clone;
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
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")
|
||||
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")
|
||||
}
|
||||
}
|
||||
statistics_option = Some(statistics);
|
||||
}
|
||||
|
||||
return (grid, num_hints, statistics_option.unwrap());
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -163,7 +192,7 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
|||
search_hidden_singles: true,
|
||||
find_possibility_groups: true,
|
||||
search_useful_constraint: true,
|
||||
make_guesses: true
|
||||
make_guesses: true,
|
||||
};
|
||||
|
||||
let mut grid: Grid = loop {
|
||||
|
@ -199,23 +228,24 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
|||
};
|
||||
|
||||
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
||||
grid =
|
||||
'outer: loop {
|
||||
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 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 mut grid_clone = grid.clone();
|
||||
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
||||
|
||||
cell.set(*digit);
|
||||
|
||||
let (status, _statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
|
||||
let (status, _statistics) =
|
||||
evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
|
||||
match status {
|
||||
SolveStatus::Complete(uniqueness) => {
|
||||
let uniqueness = uniqueness.unwrap();
|
||||
|
@ -230,14 +260,15 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
|||
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
|
||||
SolveStatus::Invalid => continue // Try another guess
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
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);
|
||||
|
@ -245,12 +276,13 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
|||
return grid;
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::grid::*;
|
||||
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
|
||||
use crate::generator::generate_grid;
|
||||
use crate::grid::*;
|
||||
use crate::solver::{
|
||||
solve_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
|
||||
};
|
||||
use rand::prelude::SmallRng;
|
||||
use rand::SeedableRng;
|
||||
|
||||
|
@ -292,17 +324,20 @@ mod tests {
|
|||
|
||||
grid.get(8, 2).unwrap().set(6);
|
||||
|
||||
let status = solve_grid_with_solve_controller(&mut grid, &SolveController{
|
||||
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
|
||||
}, &mut SolveStatistics::new());
|
||||
make_guesses: true,
|
||||
},
|
||||
&mut SolveStatistics::new(),
|
||||
);
|
||||
|
||||
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
|
||||
|
@ -314,11 +349,12 @@ mod tests {
|
|||
search_hidden_singles: true,
|
||||
find_possibility_groups: true,
|
||||
search_useful_constraint: true,
|
||||
make_guesses: true
|
||||
make_guesses: true,
|
||||
};
|
||||
|
||||
// Note that the puzzle itself doesn't matter
|
||||
let (grid, _num_hints, _statistics) = generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
|
||||
let (grid, _num_hints, _statistics) =
|
||||
generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
|
||||
|
||||
let mut observed_empty_cell = false;
|
||||
'outer: for x in 0..9 {
|
||||
|
@ -337,6 +373,5 @@ mod tests {
|
|||
}
|
||||
|
||||
assert!(observed_empty_cell);
|
||||
|
||||
}
|
||||
}
|
69
src/grid.rs
69
src/grid.rs
|
@ -1,13 +1,13 @@
|
|||
use std::rc::{Rc, Weak};
|
||||
use std::cell::{RefCell};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Formatter;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
pub static mut DEBUG: bool = false;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum CellValue {
|
||||
Fixed(u8),
|
||||
Unknown(Vec<u8>)
|
||||
Unknown(Vec<u8>),
|
||||
}
|
||||
|
||||
/// A representation of a single cell in a Sudoku grid. Don't make this directly; make a Grid.
|
||||
|
@ -81,7 +81,7 @@ impl Cell {
|
|||
CellValue::Fixed(digit) => {
|
||||
self.set(digit);
|
||||
return;
|
||||
},
|
||||
}
|
||||
CellValue::Unknown(_) => {
|
||||
self.set_value_exact(value);
|
||||
} // continue on
|
||||
|
@ -111,7 +111,10 @@ impl Cell {
|
|||
pub fn set_value_exact(&self, value: CellValue) {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Cell {}, {} was set with CellValue exact {:?}", self.x, self.y, value);
|
||||
println!(
|
||||
"Cell {}, {} was set with CellValue exact {:?}",
|
||||
self.x, self.y, value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +127,7 @@ impl Cell {
|
|||
let value = &*self.value.borrow();
|
||||
match value {
|
||||
CellValue::Fixed(_) => None,
|
||||
CellValue::Unknown(x) => Some(x.clone())
|
||||
CellValue::Unknown(x) => Some(x.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +165,9 @@ impl Cell {
|
|||
let mut new_possibilities = possibilities.clone();
|
||||
|
||||
match new_possibilities.binary_search(&digit) {
|
||||
Ok(index_remove) => {new_possibilities.remove(index_remove);},
|
||||
Ok(index_remove) => {
|
||||
new_possibilities.remove(index_remove);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
|
@ -176,18 +181,17 @@ impl Cell {
|
|||
} else {
|
||||
Some(CellValue::UNKNOWN(new_possibilities))
|
||||
}*/
|
||||
},
|
||||
CellValue::Fixed(_) => {None}
|
||||
}
|
||||
CellValue::Fixed(_) => None,
|
||||
}
|
||||
};
|
||||
|
||||
match new_value_option {
|
||||
Some(new_value) => {
|
||||
cell.set_value(new_value);
|
||||
},
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,14 +204,14 @@ pub struct Section {
|
|||
pub vec: Vec<Rc<Cell>>,
|
||||
pub do_update: RefCell<bool>,
|
||||
pub index: usize,
|
||||
pub section_type: SectionType
|
||||
pub section_type: SectionType,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SectionType {
|
||||
Row,
|
||||
Column,
|
||||
Square
|
||||
Square,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
|
@ -225,7 +229,7 @@ impl Section {
|
|||
vec: Vec::new(),
|
||||
do_update: RefCell::new(false),
|
||||
index,
|
||||
section_type: line_type
|
||||
section_type: line_type,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +254,6 @@ pub struct Grid {
|
|||
impl Grid {
|
||||
/// Generate a new empty `Grid` with full empty possibilities for each `Cell`
|
||||
pub fn new() -> Grid {
|
||||
|
||||
let mut rows: Vec<MultiMut<Section>> = Vec::new();
|
||||
let mut columns: Vec<MultiMut<Section>> = Vec::new();
|
||||
let mut sections: Vec<MultiMut<Section>> = Vec::new();
|
||||
|
@ -262,17 +265,17 @@ impl Grid {
|
|||
}
|
||||
|
||||
for row_index in 0..9 {
|
||||
let row_rc = unsafe {
|
||||
rows.get_unchecked(row_index)
|
||||
};
|
||||
let row_rc = unsafe { rows.get_unchecked(row_index) };
|
||||
|
||||
let row_ref = &mut *row_rc.borrow_mut();
|
||||
|
||||
for column_index in 0..9 {
|
||||
let section_index = (row_index / 3) * 3 + column_index / 3;
|
||||
let (column_rc, section_rc) = unsafe {
|
||||
(columns.get_unchecked_mut(column_index),
|
||||
sections.get_unchecked_mut(section_index))
|
||||
(
|
||||
columns.get_unchecked_mut(column_index),
|
||||
sections.get_unchecked_mut(section_index),
|
||||
)
|
||||
};
|
||||
|
||||
let column_weak = Rc::downgrade(column_rc);
|
||||
|
@ -289,7 +292,7 @@ impl Grid {
|
|||
value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
|
||||
row: row_weak,
|
||||
column: column_weak,
|
||||
section: section_weak
|
||||
section: section_weak,
|
||||
};
|
||||
|
||||
let ref1 = Rc::new(cell);
|
||||
|
@ -302,7 +305,11 @@ impl Grid {
|
|||
}
|
||||
}
|
||||
|
||||
return Grid { rows, columns, sections };
|
||||
return Grid {
|
||||
rows,
|
||||
columns,
|
||||
sections,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the `Cell` (in an `Rc`) at the specified coordinates.
|
||||
|
@ -311,17 +318,16 @@ impl Grid {
|
|||
///
|
||||
/// Returns None if the coordinates are out of bounds.
|
||||
pub fn get(&self, r: usize, c: usize) -> Option<Rc<Cell>> {
|
||||
|
||||
let row = match self.rows.get(r) {
|
||||
Some(x) => x,
|
||||
None => return None
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let row = &*(&**row).borrow();
|
||||
|
||||
let cell = match row.get(c) {
|
||||
Some(x) => x,
|
||||
None => return None
|
||||
None => return None,
|
||||
};
|
||||
|
||||
return Some(Rc::clone(cell));
|
||||
|
@ -353,7 +359,7 @@ impl Grid {
|
|||
smallest_size = possibilities.len();
|
||||
smallest_cell = Some(cell_rc);
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -397,24 +403,23 @@ impl Clone for Grid {
|
|||
impl std::fmt::Display for Grid {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for r in 0..9 {
|
||||
|
||||
// Each row corresponds to 3 rows since we leave room for guesses
|
||||
let mut row1 = String::new();
|
||||
let mut row2 = String::new();
|
||||
let mut row3 = String::new();
|
||||
|
||||
for c in 0..9 {
|
||||
|
||||
let cell = &*self.get(r, c).unwrap();
|
||||
let value = &*cell.value.borrow();
|
||||
|
||||
|
||||
match value {
|
||||
CellValue::Fixed(x) => {
|
||||
row1.push_str(" ");
|
||||
row2.push(' '); row2.push_str(&x.to_string()); row2.push(' ');
|
||||
row2.push(' ');
|
||||
row2.push_str(&x.to_string());
|
||||
row2.push(' ');
|
||||
row3.push_str(" ");
|
||||
},
|
||||
}
|
||||
CellValue::Unknown(x) => {
|
||||
Grid::process_unknown(&x, 1, &mut row1);
|
||||
Grid::process_unknown(&x, 2, &mut row1);
|
||||
|
@ -439,8 +444,6 @@ impl std::fmt::Display for Grid {
|
|||
row2.push('┆');
|
||||
row3.push('┆');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
write!(f, "{}", row1)?;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub mod grid;
|
||||
pub mod solver;
|
||||
pub mod generator;
|
||||
pub mod grid;
|
||||
pub mod pdf;
|
||||
pub mod solver;
|
||||
|
|
12
src/pdf.rs
12
src/pdf.rs
|
@ -1,7 +1,7 @@
|
|||
use crate::grid::{CellValue, Grid};
|
||||
use printpdf::*;
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use crate::grid::{Grid, CellValue};
|
||||
|
||||
const BOTTOM_LEFT_X: f64 = 10.0;
|
||||
const BOTTOM_LEFT_Y: f64 = 279.0 - 200.0 - 10.0;
|
||||
|
@ -28,7 +28,6 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
|||
let x_offset = 6.1;
|
||||
let y_offset = -16.5;
|
||||
|
||||
|
||||
for r in 0..9 {
|
||||
let y = Mm(BOTTOM_LEFT_Y + (GRID_DIMENSION / 9.0) * (9.0 - r as f64) + y_offset);
|
||||
|
||||
|
@ -41,7 +40,7 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
|||
CellValue::Fixed(digit) => {
|
||||
let text = digit.to_string();
|
||||
layer.use_text(text, font_size, x, y, &font);
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +49,9 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
|||
doc.save(&mut BufWriter::new(File::create(filename)?))?;
|
||||
|
||||
return Ok(());
|
||||
|
||||
}
|
||||
|
||||
fn draw_empty_grid(layer: &PdfLayerReference) {
|
||||
|
||||
// x represents position on left-right scale
|
||||
// y represents position on up-down scale
|
||||
|
||||
|
@ -96,7 +93,6 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
|||
let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
|
||||
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,8 +110,6 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point) {
|
||||
let points = vec![(point1, false), (point2, false)];
|
||||
|
||||
|
@ -124,7 +118,7 @@ fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point){
|
|||
is_closed: false,
|
||||
has_fill: false,
|
||||
has_stroke: true,
|
||||
is_clipping_path: false
|
||||
is_clipping_path: false,
|
||||
};
|
||||
|
||||
layer.add_shape(line);
|
||||
|
|
434
src/solver.rs
434
src/solver.rs
|
@ -1,20 +1,19 @@
|
|||
|
||||
use crate::grid::{Cell, CellValue, Grid, Section};
|
||||
use std::rc::Rc;
|
||||
use crate::grid::{Cell, Section, Grid, CellValue};
|
||||
|
||||
pub static mut DEBUG: bool = false;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Uniqueness {
|
||||
Unique,
|
||||
NotUnique
|
||||
NotUnique,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum SolveStatus {
|
||||
Complete(Option<Uniqueness>),
|
||||
Unfinished,
|
||||
Invalid
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// See `SolveController` for a description of the solving strategies.
|
||||
|
@ -23,11 +22,10 @@ enum SolveAction{
|
|||
HiddenSingle,
|
||||
PossibilityGroup,
|
||||
UsefulConstraints,
|
||||
Guess
|
||||
Guess,
|
||||
}
|
||||
|
||||
impl SolveStatus {
|
||||
|
||||
fn increment(self, additional_status: SolveStatus) -> SolveStatus {
|
||||
match self {
|
||||
SolveStatus::Complete(uniqueness_option) => {
|
||||
|
@ -37,19 +35,24 @@ impl SolveStatus {
|
|||
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::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
|
||||
_ => additional_status,
|
||||
},
|
||||
SolveStatus::Invalid => panic!("increment() shouldn't be called on SolveStatus::Invalid")
|
||||
SolveStatus::Invalid => {
|
||||
panic!("increment() shouldn't be called on SolveStatus::Invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,11 +120,10 @@ pub struct SolveStatistics {
|
|||
pub hidden_singles: u32,
|
||||
pub possibility_groups: u32,
|
||||
pub useful_constraints: u32,
|
||||
pub guesses: u32
|
||||
pub guesses: u32,
|
||||
}
|
||||
|
||||
impl SolveStatistics {
|
||||
|
||||
/// Create a new SolveStatistics with `0` counts set for all the fields.
|
||||
pub fn new() -> SolveStatistics {
|
||||
SolveStatistics {
|
||||
|
@ -129,35 +131,31 @@ impl SolveStatistics {
|
|||
hidden_singles: 0,
|
||||
possibility_groups: 0,
|
||||
useful_constraints: 0,
|
||||
guesses: 0
|
||||
guesses: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&mut self, action: &SolveAction) {
|
||||
match action {
|
||||
SolveAction::Single => {self.singles = self.singles + 1}
|
||||
SolveAction::HiddenSingle => {self.hidden_singles = self.hidden_singles + 1}
|
||||
SolveAction::PossibilityGroup => {self.possibility_groups = self.possibility_groups + 1}
|
||||
SolveAction::UsefulConstraints => {self.useful_constraints = self.useful_constraints + 1}
|
||||
SolveAction::Guess => {self.guesses = self.guesses + 1}
|
||||
SolveAction::Single => self.singles = self.singles + 1,
|
||||
SolveAction::HiddenSingle => self.hidden_singles = self.hidden_singles + 1,
|
||||
SolveAction::PossibilityGroup => self.possibility_groups = self.possibility_groups + 1,
|
||||
SolveAction::UsefulConstraints => self.useful_constraints = self.useful_constraints + 1,
|
||||
SolveAction::Guess => self.guesses = self.guesses + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Code for identify_and_process_possibility_groups (it uses it's own structs)
|
||||
mod process_possibility_groups {
|
||||
use crate::grid::{Section, CellValue};
|
||||
use crate::grid::{CellValue, Section};
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct FauxCell {
|
||||
index: usize,
|
||||
possibilities: HashSet<u8>,
|
||||
in_group: bool
|
||||
in_group: bool,
|
||||
}
|
||||
|
||||
impl FauxCell {
|
||||
|
@ -166,14 +164,15 @@ mod process_possibility_groups {
|
|||
}
|
||||
|
||||
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>);
|
||||
|
||||
impl FauxLine {
|
||||
|
||||
fn num_in_group(&self) -> usize {
|
||||
self.0.iter().filter(|faux_cell| faux_cell.in_group).count()
|
||||
}
|
||||
|
@ -188,7 +187,10 @@ mod process_possibility_groups {
|
|||
pub fn identify_and_process_possibility_groups(line: &Section) -> bool {
|
||||
unsafe {
|
||||
if super::DEBUG {
|
||||
println!("Looking for possibility groups on line {:?} {}", line.section_type, line.index);
|
||||
println!(
|
||||
"Looking for possibility groups on line {:?} {}",
|
||||
line.section_type, line.index
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +198,6 @@ mod process_possibility_groups {
|
|||
}
|
||||
|
||||
fn bisect_possibility_groups(line: &Section, cells_of_interest: Vec<usize>) -> bool {
|
||||
|
||||
/*
|
||||
Algorithm -
|
||||
Setup - Let count = 0
|
||||
|
@ -235,15 +236,15 @@ mod process_possibility_groups {
|
|||
set.insert(digit.clone());
|
||||
}
|
||||
set
|
||||
},
|
||||
CellValue::Fixed(_) => { continue }
|
||||
}
|
||||
CellValue::Fixed(_) => continue,
|
||||
}
|
||||
};
|
||||
|
||||
let faux_cell = FauxCell {
|
||||
index: i,
|
||||
possibilities: faux_possibilities,
|
||||
in_group: false
|
||||
in_group: false,
|
||||
};
|
||||
|
||||
faux_line.0.push(faux_cell);
|
||||
|
@ -265,7 +266,12 @@ mod process_possibility_groups {
|
|||
let mut smallest_cell: Option<&mut FauxCell> = None;
|
||||
let mut smallest_size = usize::MAX;
|
||||
|
||||
for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() {
|
||||
for (_index, cell) in faux_line
|
||||
.0
|
||||
.iter_mut()
|
||||
.filter(|faux_cell| !faux_cell.in_group)
|
||||
.enumerate()
|
||||
{
|
||||
if cell.len() < smallest_size {
|
||||
smallest_size = cell.len();
|
||||
smallest_cell = Some(cell);
|
||||
|
@ -278,11 +284,15 @@ mod process_possibility_groups {
|
|||
// Step 2
|
||||
count = count + smallest_size;
|
||||
|
||||
|
||||
let possibilities_to_remove = smallest_cell.possibilities.clone(); // Necessary because of mutable borrow rules
|
||||
|
||||
// Step 3
|
||||
for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() {
|
||||
for (_index, cell) in faux_line
|
||||
.0
|
||||
.iter_mut()
|
||||
.filter(|faux_cell| !faux_cell.in_group)
|
||||
.enumerate()
|
||||
{
|
||||
cell.remove(&possibilities_to_remove);
|
||||
}
|
||||
|
||||
|
@ -293,7 +303,8 @@ mod process_possibility_groups {
|
|||
}
|
||||
|
||||
// Now we have to see if this was worth it
|
||||
if faux_line.num_out_group() > 0 { // Worth it
|
||||
if faux_line.num_out_group() > 0 {
|
||||
// Worth it
|
||||
// We now have two distinct groups and can separate their possibilities
|
||||
let mut in_group_possibilities = HashSet::new();
|
||||
let mut out_group_possibilities = HashSet::new();
|
||||
|
@ -301,9 +312,13 @@ mod process_possibility_groups {
|
|||
// Collect the possibilities for each group
|
||||
for (_index, cell) in faux_line.0.iter().enumerate() {
|
||||
if cell.in_group {
|
||||
cell.possibilities.iter().for_each(|digit| {in_group_possibilities.insert(digit.clone());});
|
||||
cell.possibilities.iter().for_each(|digit| {
|
||||
in_group_possibilities.insert(digit.clone());
|
||||
});
|
||||
} else {
|
||||
cell.possibilities.iter().for_each(|digit| {out_group_possibilities.insert(digit.clone());});
|
||||
cell.possibilities.iter().for_each(|digit| {
|
||||
out_group_possibilities.insert(digit.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,26 +329,29 @@ mod process_possibility_groups {
|
|||
let value = &*real_cell.value.borrow();
|
||||
match value {
|
||||
CellValue::Unknown(possibilities) => possibilities.clone(),
|
||||
CellValue::Fixed(_) => {panic!("Faux_cell shouldn't have linked to fixed cell")}
|
||||
CellValue::Fixed(_) => {
|
||||
panic!("Faux_cell shouldn't have linked to fixed cell")
|
||||
}
|
||||
}
|
||||
};
|
||||
let starting_possibility_size = possibilities.len();
|
||||
|
||||
let possibilities_to_remove = match faux_cell.in_group {
|
||||
true => &out_group_possibilities,
|
||||
false => &in_group_possibilities
|
||||
false => &in_group_possibilities,
|
||||
};
|
||||
|
||||
for (_i, possibility) in possibilities_to_remove.iter().enumerate() {
|
||||
match possibilities.binary_search(possibility) {
|
||||
Ok(x) => {
|
||||
possibilities.remove(x);
|
||||
},
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
}
|
||||
|
||||
if possibilities.len() < starting_possibility_size { // We have a change to make
|
||||
if possibilities.len() < starting_possibility_size {
|
||||
// We have a change to make
|
||||
made_change = true;
|
||||
let new_value = {
|
||||
if possibilities.len() == 1 {
|
||||
|
@ -374,7 +392,10 @@ mod process_possibility_groups {
|
|||
fn search_single_possibility(line: &Section) -> bool {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("search_single_possibility on line {:?} {}", line.section_type, line.index);
|
||||
println!(
|
||||
"search_single_possibility on line {:?} {}",
|
||||
line.section_type, line.index
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,7 +409,7 @@ fn search_single_possibility(line: &Section) -> bool{
|
|||
cell.set_value(new_value);
|
||||
made_change = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
@ -401,22 +422,32 @@ fn search_hidden_single(line: &Section) -> bool{
|
|||
enum Count {
|
||||
None,
|
||||
One(Rc<Cell>),
|
||||
Many
|
||||
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}
|
||||
Count::None => Count::One(cell),
|
||||
Count::One(_) => Count::Many,
|
||||
Count::Many => Count::Many,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut made_change = false;
|
||||
|
||||
let mut counts = [Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None];
|
||||
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();
|
||||
|
@ -427,7 +458,7 @@ fn search_hidden_single(line: &Section) -> bool{
|
|||
counts[digit - 1] = counts[digit - 1].increment(Rc::clone(cell));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
CellValue::Fixed(_) => {} // do nothing
|
||||
}
|
||||
}
|
||||
|
@ -437,7 +468,7 @@ fn search_hidden_single(line: &Section) -> bool{
|
|||
Count::One(cell) => {
|
||||
cell.set((digit + 1) as u8);
|
||||
made_change = true;
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -446,21 +477,21 @@ fn search_hidden_single(line: &Section) -> bool{
|
|||
}
|
||||
|
||||
mod search_useful_constraint {
|
||||
use crate::grid::{Grid, Section, SectionType, CellValue};
|
||||
use std::rc::{Rc, Weak};
|
||||
use crate::grid::{CellValue, Grid, Section, SectionType};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
enum PossibilityLines {
|
||||
Unique(usize),
|
||||
Invalid,
|
||||
None
|
||||
None,
|
||||
}
|
||||
|
||||
impl PossibilityLines {
|
||||
fn is_invalid(&self) -> bool {
|
||||
match &self {
|
||||
PossibilityLines::Invalid => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,22 +503,34 @@ mod search_useful_constraint{
|
|||
pub fn search_useful_constraint(grid: &Grid, line: &Section) -> bool {
|
||||
unsafe {
|
||||
if super::DEBUG {
|
||||
println!("Searching for a useful constraint on line {:?} {}", line.section_type, line.index);
|
||||
println!(
|
||||
"Searching for a useful constraint on line {:?} {}",
|
||||
line.section_type, line.index
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut made_change = false;
|
||||
|
||||
let (check_row, check_column, check_section) = match line.section_type {
|
||||
SectionType::Row => {(false, false, true)},
|
||||
SectionType::Column => {(false, false, true)},
|
||||
SectionType::Square => {(true, true, false)},
|
||||
SectionType::Row => (false, false, true),
|
||||
SectionType::Column => (false, false, true),
|
||||
SectionType::Square => (true, true, false),
|
||||
};
|
||||
|
||||
for possibility in 0..9 {
|
||||
let mut rows = match check_row {true => PossibilityLines::None, false => PossibilityLines::Invalid };
|
||||
let mut columns = match check_column {true => PossibilityLines::None, false => PossibilityLines::Invalid };
|
||||
let mut sections = match check_section {true => PossibilityLines::None, false => PossibilityLines::Invalid };
|
||||
let mut rows = match check_row {
|
||||
true => PossibilityLines::None,
|
||||
false => PossibilityLines::Invalid,
|
||||
};
|
||||
let mut columns = match check_column {
|
||||
true => PossibilityLines::None,
|
||||
false => PossibilityLines::Invalid,
|
||||
};
|
||||
let mut sections = match check_section {
|
||||
true => PossibilityLines::None,
|
||||
false => PossibilityLines::Invalid,
|
||||
};
|
||||
|
||||
for cell_id in 0..9 {
|
||||
let cell_ref = line.get(cell_id).unwrap();
|
||||
|
@ -496,7 +539,8 @@ mod search_useful_constraint{
|
|||
let value = &*cell_ref.value.borrow();
|
||||
|
||||
match value {
|
||||
CellValue::Fixed(x) => { // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
|
||||
CellValue::Fixed(x) => {
|
||||
// We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
|
||||
if possibility.eq(x) {
|
||||
rows = process_possibility_line(rows, &cell_ref.row);
|
||||
columns = process_possibility_line(columns, &cell_ref.column);
|
||||
|
@ -521,33 +565,52 @@ mod search_useful_constraint{
|
|||
// Check each line and see if we can determine anything
|
||||
match rows {
|
||||
PossibilityLines::Unique(index) => {
|
||||
made_change = made_change |
|
||||
remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.section_type, line.index);
|
||||
},
|
||||
made_change = made_change
|
||||
| remove_possibilities_line(
|
||||
grid.rows.get(index).unwrap(),
|
||||
possibility,
|
||||
&line.section_type,
|
||||
line.index,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match columns {
|
||||
PossibilityLines::Unique(index) => {
|
||||
made_change = made_change |
|
||||
remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.section_type, line.index);
|
||||
},
|
||||
made_change = made_change
|
||||
| remove_possibilities_line(
|
||||
grid.columns.get(index).unwrap(),
|
||||
possibility,
|
||||
&line.section_type,
|
||||
line.index,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match sections {
|
||||
PossibilityLines::Unique(index) => {
|
||||
made_change = made_change |
|
||||
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.section_type, line.index);
|
||||
},
|
||||
made_change = made_change
|
||||
| remove_possibilities_line(
|
||||
grid.sections.get(index).unwrap(),
|
||||
possibility,
|
||||
&line.section_type,
|
||||
line.index,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
return made_change;
|
||||
|
||||
}
|
||||
|
||||
// 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<Section>>, digit_to_remove: u8, initial_line_type: &SectionType, initial_line_index: usize) -> bool {
|
||||
fn remove_possibilities_line(
|
||||
line: &Rc<RefCell<Section>>,
|
||||
digit_to_remove: u8,
|
||||
initial_line_type: &SectionType,
|
||||
initial_line_index: usize,
|
||||
) -> bool {
|
||||
let line = &*(&**line).borrow();
|
||||
let mut made_change = false;
|
||||
|
||||
|
@ -559,7 +622,7 @@ mod search_useful_constraint{
|
|||
let parent_line = match initial_line_type {
|
||||
SectionType::Row => &cell.row,
|
||||
SectionType::Column => &cell.column,
|
||||
SectionType::Square => &cell.section
|
||||
SectionType::Square => &cell.section,
|
||||
};
|
||||
let parent_line = &*parent_line.upgrade().unwrap();
|
||||
let parent_line = &*parent_line.borrow();
|
||||
|
@ -568,44 +631,52 @@ mod search_useful_constraint{
|
|||
continue;
|
||||
}
|
||||
|
||||
let new_possibilities = match possibilities.binary_search(&digit_to_remove) {
|
||||
let new_possibilities = match possibilities.binary_search(&digit_to_remove)
|
||||
{
|
||||
Ok(x) => {
|
||||
let mut new_possibilities = possibilities.clone();
|
||||
new_possibilities.remove(x);
|
||||
new_possibilities
|
||||
},
|
||||
Err(_) => { continue; }
|
||||
}
|
||||
Err(_) => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let new_value;
|
||||
if new_possibilities.len() == 1 {
|
||||
new_value = CellValue::Fixed(new_possibilities.first().unwrap().clone());
|
||||
new_value =
|
||||
CellValue::Fixed(new_possibilities.first().unwrap().clone());
|
||||
} else {
|
||||
new_value = CellValue::Unknown(new_possibilities);
|
||||
}
|
||||
|
||||
new_value
|
||||
},
|
||||
_ => { continue; }
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cell.set_value(new_value);
|
||||
made_change = true;
|
||||
|
||||
}
|
||||
|
||||
return made_change;
|
||||
}
|
||||
|
||||
// We detected a useful constraint
|
||||
fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefCell<Section>>) -> PossibilityLines {
|
||||
fn process_possibility_line(
|
||||
possibility_line: PossibilityLines,
|
||||
line: &Weak<RefCell<Section>>,
|
||||
) -> PossibilityLines {
|
||||
let line = line.upgrade().unwrap();
|
||||
let line = &*(&*line).borrow();
|
||||
|
||||
match possibility_line {
|
||||
PossibilityLines::None => {PossibilityLines::Unique(line.index)},
|
||||
PossibilityLines::Invalid => {possibility_line},
|
||||
PossibilityLines::None => PossibilityLines::Unique(line.index),
|
||||
PossibilityLines::Invalid => possibility_line,
|
||||
PossibilityLines::Unique(x) => {
|
||||
if line.index.eq(&x) {
|
||||
possibility_line
|
||||
|
@ -615,11 +686,14 @@ mod search_useful_constraint{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){
|
||||
fn solve_line(
|
||||
grid: &Grid,
|
||||
line: &Section,
|
||||
solve_controller: &SolveController,
|
||||
solve_statistics: &mut SolveStatistics,
|
||||
) {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Solving {:?} {}", line.section_type, line.index);
|
||||
|
@ -631,7 +705,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
|
|||
if solve_controller.search_singles() {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Searching for singles on line {:?} of {}\n{}", line.section_type, line.index, grid);
|
||||
println!(
|
||||
"Searching for singles on line {:?} of {}\n{}",
|
||||
line.section_type, line.index, grid
|
||||
);
|
||||
}
|
||||
}
|
||||
if search_single_possibility(line) {
|
||||
|
@ -642,7 +719,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
|
|||
if solve_controller.search_hidden_singles() {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Searching for hidden singles on line {:?} of {}\n{}", line.section_type, line.index, grid);
|
||||
println!(
|
||||
"Searching for hidden singles on line {:?} of {}\n{}",
|
||||
line.section_type, line.index, grid
|
||||
);
|
||||
}
|
||||
}
|
||||
if search_hidden_single(line) {
|
||||
|
@ -653,7 +733,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
|
|||
if solve_controller.find_possibility_groups() {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Searching for possibility groups on line {:?} of {}\n{}", line.section_type, line.index, grid);
|
||||
println!(
|
||||
"Searching for possibility groups on line {:?} of {}\n{}",
|
||||
line.section_type, line.index, grid
|
||||
);
|
||||
}
|
||||
}
|
||||
if process_possibility_groups::identify_and_process_possibility_groups(line) {
|
||||
|
@ -664,14 +747,16 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
|
|||
if solve_controller.search_useful_constraint() {
|
||||
unsafe {
|
||||
if DEBUG {
|
||||
println!("Searching for useful constraints on line {:?} of {}\n{}", line.section_type, line.index, grid);
|
||||
println!(
|
||||
"Searching for useful constraints on line {:?} of {}\n{}",
|
||||
line.section_type, line.index, grid
|
||||
);
|
||||
}
|
||||
}
|
||||
if search_useful_constraint::search_useful_constraint(grid, line) {
|
||||
solve_statistics.increment(&SolveAction::UsefulConstraints);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Solves (and modifies) the input `Grid`. Returns a `SolveStatus` and `SolveStatistics`, and
|
||||
|
@ -685,17 +770,22 @@ pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) {
|
|||
search_hidden_singles: true,
|
||||
find_possibility_groups: true,
|
||||
search_useful_constraint: true,
|
||||
make_guesses: true
|
||||
make_guesses: true,
|
||||
};
|
||||
|
||||
let mut solve_statistics = SolveStatistics::new();
|
||||
let solve_status = solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics);
|
||||
let solve_status =
|
||||
solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics);
|
||||
|
||||
return (solve_status, solve_statistics);
|
||||
}
|
||||
|
||||
/// Solves (and modifies) the input `Grid` & `SolveStatistics`. Returns a `SolveStatus`.
|
||||
pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
|
||||
pub fn solve_grid_with_solve_controller(
|
||||
grid: &mut Grid,
|
||||
solve_controller: &SolveController,
|
||||
solve_statistics: &mut SolveStatistics,
|
||||
) -> SolveStatus {
|
||||
// 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
|
||||
// solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them
|
||||
|
@ -711,8 +801,8 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
|
|||
} 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,
|
||||
};
|
||||
|
||||
return status;
|
||||
|
@ -721,17 +811,24 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
|
|||
/// Similar to `solve_grid_with_solve_controller` except that we don't modify the input Grid; we
|
||||
/// only determine SolveStatus. This is useful when generating puzzles where we need to know *if*
|
||||
/// a puzzle can be solved but don't want to actually solve it.
|
||||
pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &SolveController) -> (SolveStatus, SolveStatistics){
|
||||
pub fn evaluate_grid_with_solve_controller(
|
||||
grid: &Grid,
|
||||
solve_controller: &SolveController,
|
||||
) -> (SolveStatus, SolveStatistics) {
|
||||
let mut mut_grid = grid.clone();
|
||||
let mut solve_statistics = SolveStatistics::new();
|
||||
|
||||
let solve_status = solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics);
|
||||
let solve_status =
|
||||
solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics);
|
||||
|
||||
return (solve_status, solve_statistics);
|
||||
}
|
||||
|
||||
fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
|
||||
|
||||
fn solve_grid_no_guess(
|
||||
grid: &mut Grid,
|
||||
solve_controller: &SolveController,
|
||||
solve_statistics: &mut SolveStatistics,
|
||||
) -> SolveStatus {
|
||||
loop {
|
||||
let mut ran_something = false;
|
||||
for (_index, line_ref) in grid.rows.iter().enumerate() {
|
||||
|
@ -759,7 +856,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
|
|||
}
|
||||
}
|
||||
|
||||
if !ran_something{ // No lines have changed since we last analyzed them
|
||||
if !ran_something {
|
||||
// No lines have changed since we last analyzed them
|
||||
return SolveStatus::Unfinished;
|
||||
}
|
||||
|
||||
|
@ -776,9 +874,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
|
|||
appears_complete = false;
|
||||
if possibilities.len() == 0 {
|
||||
return SolveStatus::Invalid;
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
CellValue::Fixed(_) => {}
|
||||
}
|
||||
}
|
||||
|
@ -790,16 +887,19 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
|
|||
return SolveStatus::Complete(Some(Uniqueness::Unique));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
|
||||
fn solve_grid_guess(
|
||||
grid: &mut Grid,
|
||||
solve_controller: &SolveController,
|
||||
solve_statistics: &mut SolveStatistics,
|
||||
) -> SolveStatus {
|
||||
solve_statistics.increment(&SolveAction::Guess);
|
||||
|
||||
let smallest_cell = grid.find_smallest_cell();
|
||||
let smallest_cell = match smallest_cell {
|
||||
Some(cell) => cell,
|
||||
None => return SolveStatus::Invalid
|
||||
None => return SolveStatus::Invalid,
|
||||
};
|
||||
|
||||
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
||||
|
@ -808,16 +908,19 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s
|
|||
let mut grid_solution = None;
|
||||
|
||||
for (_index, &digit) in 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_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics);
|
||||
grid_copy
|
||||
.get(smallest_cell.x, smallest_cell.y)
|
||||
.unwrap()
|
||||
.set(digit);
|
||||
let status =
|
||||
solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics);
|
||||
|
||||
// Keep a copy of grid_copy in case we later mutate grid with it
|
||||
match status {
|
||||
SolveStatus::Complete(_) => {
|
||||
grid_solution = Some(grid_copy);
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -830,34 +933,37 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s
|
|||
break; // no point in continuing
|
||||
}
|
||||
|
||||
let uniqueness = uniqueness.expect("We're looking for uniqueness and yet an earlier function didn't make a claim");
|
||||
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::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")
|
||||
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"));
|
||||
},
|
||||
grid.clone_from(
|
||||
&grid_solution.expect("grid_solution should have value if we found a solution"),
|
||||
);
|
||||
}
|
||||
SolveStatus::Unfinished => {
|
||||
current_status = SolveStatus::Invalid; // We can now say Invalid
|
||||
},
|
||||
}
|
||||
SolveStatus::Invalid => {}
|
||||
}
|
||||
|
||||
return current_status;
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -873,14 +979,20 @@ mod tests {
|
|||
grid.get(0, i).unwrap().set(i as u8 + 1);
|
||||
}
|
||||
|
||||
assert_eq!(CellValue::Unknown(vec![9]), grid.get(0, 8).unwrap().get_value_copy());
|
||||
assert_eq!(
|
||||
CellValue::Unknown(vec![9]),
|
||||
grid.get(0, 8).unwrap().get_value_copy()
|
||||
);
|
||||
|
||||
let line = grid.rows.first().unwrap();
|
||||
let line = &*(**line).borrow();
|
||||
|
||||
search_single_possibility(line);
|
||||
|
||||
assert_eq!(CellValue::Fixed(9), grid.get(0, 8).unwrap().get_value_copy());
|
||||
assert_eq!(
|
||||
CellValue::Fixed(9),
|
||||
grid.get(0, 8).unwrap().get_value_copy()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -895,14 +1007,20 @@ mod tests {
|
|||
grid.get(1, 7).unwrap().set(2);
|
||||
grid.get(1, 8).unwrap().set(3);
|
||||
|
||||
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]), grid.get(0, 0).unwrap().get_value_copy());
|
||||
assert_eq!(
|
||||
CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]),
|
||||
grid.get(0, 0).unwrap().get_value_copy()
|
||||
);
|
||||
|
||||
let line = grid.rows.first().unwrap();
|
||||
let line = &*(**line).borrow();
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -917,19 +1035,22 @@ mod tests {
|
|||
grid.get(1, 7).unwrap().set(2);
|
||||
grid.get(1, 8).unwrap().set(3);
|
||||
|
||||
|
||||
|
||||
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy());
|
||||
assert_eq!(
|
||||
CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]),
|
||||
grid.get(2, 0).unwrap().get_value_copy()
|
||||
);
|
||||
|
||||
let line = grid.rows.first().unwrap();
|
||||
let line = &*(**line).borrow();
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_search_useful_constraint_2() {
|
||||
let grid = Grid::new();
|
||||
|
@ -942,15 +1063,29 @@ mod tests {
|
|||
grid.get(6, 1).unwrap().set(8);
|
||||
grid.get(8, 2).unwrap().set(7);
|
||||
|
||||
grid.get(0, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9]));
|
||||
grid.get(1, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
|
||||
grid.get(2, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2]));
|
||||
grid.get(4, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7]));
|
||||
grid.get(7, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 9]));
|
||||
grid.get(8, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9]));
|
||||
grid.get(0, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9]));
|
||||
grid.get(1, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
|
||||
grid.get(2, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 2]));
|
||||
grid.get(4, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 3, 4, 7]));
|
||||
grid.get(7, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 2, 3, 9]));
|
||||
grid.get(8, 1)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9]));
|
||||
|
||||
// 5 is wrongly removed here
|
||||
grid.get(1, 0).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
|
||||
grid.get(1, 0)
|
||||
.unwrap()
|
||||
.set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
|
||||
|
||||
println!("{}", grid);
|
||||
|
||||
|
@ -959,8 +1094,10 @@ mod tests {
|
|||
|
||||
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]
|
||||
|
@ -985,10 +1122,13 @@ mod tests {
|
|||
|
||||
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());
|
||||
|
||||
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