Run cargo fmt

This commit is contained in:
Joel Therrien 2020-09-29 13:45:48 -07:00
parent 7d4e474731
commit 8dc7954f53
7 changed files with 642 additions and 413 deletions

View file

@ -1,13 +1,13 @@
use rand::prelude::*; use rand::prelude::*;
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, SolveStatistics};
use std::str::FromStr;
use std::sync::{mpsc, Arc};
use std::process::exit; use std::process::exit;
use std::thread; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering}; 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 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, Challenge,
Hard, Hard,
Medium, Medium,
Easy Easy,
} }
impl Difficulty { impl Difficulty {
@ -35,17 +35,17 @@ impl Difficulty {
search_hidden_singles: true, search_hidden_singles: true,
find_possibility_groups: true, find_possibility_groups: true,
search_useful_constraint: true, search_useful_constraint: true,
make_guesses: true make_guesses: true,
}; };
match self { match self {
Difficulty::Challenge => {}, // Do nothing, already hard Difficulty::Challenge => {} // Do nothing, already hard
Difficulty::Hard => { Difficulty::Hard => {
controller.make_guesses = false; controller.make_guesses = false;
} }
Difficulty::Medium => { Difficulty::Medium => {
controller.make_guesses = false; controller.make_guesses = false;
}, }
Difficulty::Easy => { Difficulty::Easy => {
controller.make_guesses = false; controller.make_guesses = false;
controller.search_useful_constraint = false; controller.search_useful_constraint = false;
@ -59,24 +59,28 @@ impl Difficulty {
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool { fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
match self { match self {
Difficulty::Challenge => { 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 => { Difficulty::Hard => {
(solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20) (solve_statistics.possibility_groups > 20)
&& (solve_statistics.useful_constraints > 20)
} }
Difficulty::Medium => { 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; type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("EASY") { if s.eq_ignore_ascii_case("EASY") {
return Ok(Difficulty::Easy); return Ok(Difficulty::Easy);
} else if s.eq_ignore_ascii_case("MEDIUM") { } else if s.eq_ignore_ascii_case("MEDIUM") {
@ -92,7 +96,6 @@ impl FromStr for Difficulty { // Needed for argparse
} }
fn main() { fn main() {
let mut debug = false; let mut debug = false;
let mut max_hints = 81; let mut max_hints = 81;
let mut max_attempts = 100; let mut max_attempts = 100;
@ -100,41 +103,67 @@ fn main() {
let mut difficulty = Difficulty::Challenge; let mut difficulty = Difficulty::Challenge;
let mut threads = 1; 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(); let mut ap = argparse::ArgumentParser::new();
ap.set_description("Generate Sudoku puzzles"); ap.set_description("Generate Sudoku puzzles");
ap.refer(&mut debug) ap.refer(&mut debug)
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
ap.refer(&mut max_hints) ap.refer(&mut max_hints).add_option(
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints"); &["--hints"],
argparse::Store,
"Only return a puzzle with less than or equal to this number of hints",
);
ap.refer(&mut max_attempts) 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"); .add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100");
ap.refer(&mut filename) ap.refer(&mut filename).add_argument(
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV"); "filename",
argparse::StoreOption,
"Optional filename to store puzzle in as a CSV",
);
ap.refer(&mut difficulty) ap.refer(&mut difficulty).add_option(
.add_option(&["-d", "--difficulty"], argparse::Store, "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE"); &["-d", "--difficulty"],
argparse::Store,
"Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE",
);
ap.refer(&mut threads) ap.refer(&mut threads).add_option(
.add_option(&["--threads"], argparse::Store, "Number of threads to use when generating possible puzzles"); &["--threads"],
argparse::Store,
"Number of threads to use when generating possible puzzles",
);
ap.parse_args_or_exit(); ap.parse_args_or_exit();
} }
let solve_controller = difficulty.map_to_solve_controller(); let solve_controller = difficulty.map_to_solve_controller();
let (result, num_attempts) = let (result, num_attempts) = if threads < 1 {
if threads < 1 {
eprintln!("--threads must be at least 1"); eprintln!("--threads must be at least 1");
exit(1); exit(1);
} else if threads == 1 { } else if threads == 1 {
let mut rng = SmallRng::from_entropy(); 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 { } 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 { let (grid, solve_statistics, num_hints) = match result {
@ -145,16 +174,27 @@ fn main() {
} }
}; };
println!("{}", grid); 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 { if debug {
println!("Solving this puzzle involves roughly:"); println!("Solving this puzzle involves roughly:");
println!("\t{} SINGLE actions", solve_statistics.singles); println!("\t{} SINGLE actions", solve_statistics.singles);
println!("\t{} HIDDEN_SINGLE actions", solve_statistics.hidden_singles); println!(
println!("\t{} USEFUL_CONSTRAINT actions", solve_statistics.useful_constraints); "\t{} HIDDEN_SINGLE actions",
println!("\t{} POSSIBILITY_GROUP actions", solve_statistics.possibility_groups); 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); println!("\t{} GUESS actions", solve_statistics.guesses);
} }
@ -168,13 +208,19 @@ fn main() {
save_grid_csv(&grid, &filename).unwrap(); save_grid_csv(&grid, &filename).unwrap();
println!("Grid saved as CSV to {}", filename); println!("Grid saved as CSV to {}", filename);
} }
}, }
None => {} 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 mut thread_rng = thread_rng();
let (transmitter, receiver) = mpsc::channel(); let (transmitter, receiver) = mpsc::channel();
let mut remaining_attempts = max_attempts; 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 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 mut result_was_some = false;
let result = match result { let result = match result {
None => {None} None => None,
Some((grid, solve_statistics, num_hints)) => { Some((grid, solve_statistics, num_hints)) => {
result_was_some = true; result_was_some = true;
Some((SafeGridWrapper(grid), solve_statistics, num_hints)) 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(); cloned_transmitter.send((result, num_attempts)).unwrap();
if debug { 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); 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; let mut num_attempts = 0;
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) { while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) {
let (grid, num_hints, solve_statistics) =
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller); sudoku_solver::generator::generate_grid(rng, &solve_controller);
num_attempts += 1; num_attempts += 1;
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints { 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 { for y in 0..9 {
let cell = grid.get(x, y).unwrap(); let cell = grid.get(x, y).unwrap();
let value = &*cell.value.borrow(); let value = &*cell.value.borrow();
let digit = let digit = match value {
match value { CellValue::Fixed(digit) => *digit,
CellValue::Fixed(digit) => {*digit} CellValue::Unknown(_) => 0,
CellValue::Unknown(_) => {0}
}; };
let mut text = digit.to_string(); let mut text = digit.to_string();
@ -272,7 +334,6 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
text.push(','); text.push(',');
} }
file.write(text.as_bytes())?; file.write(text.as_bytes())?;
} }
file.write(b"\n")?; file.write(b"\n")?;
} }

View file

@ -3,19 +3,21 @@ use std::str::FromStr;
use sudoku_solver::grid::Grid; use sudoku_solver::grid::Grid;
use sudoku_solver::solver::solve_grid; use sudoku_solver::solver::solve_grid;
fn main() { fn main() {
let mut debug = false; let mut debug = false;
let mut filename = String::new(); 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(); let mut ap = argparse::ArgumentParser::new();
ap.set_description("Solve Sudoku puzzles"); ap.set_description("Solve Sudoku puzzles");
ap.refer(&mut debug) ap.refer(&mut debug)
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
ap.refer(&mut filename) ap.refer(&mut filename).required().add_argument(
.required() "filename",
.add_argument("filename", argparse::Store, "Path to puzzle CSV file"); argparse::Store,
"Path to puzzle CSV file",
);
ap.parse_args_or_exit(); ap.parse_args_or_exit();
} }
@ -27,7 +29,6 @@ fn main() {
} }
} }
let mut grid = match read_grid(&filename) { let mut grid = match read_grid(&filename) {
Ok(grid) => grid, Ok(grid) => grid,
Err(e) => { Err(e) => {
@ -42,8 +43,6 @@ fn main() {
solve_grid(&mut grid); solve_grid(&mut grid);
println!("Solved grid:\n{}", grid); println!("Solved grid:\n{}", grid);
} }
fn read_grid(filename: &str) -> Result<Grid, String> { fn read_grid(filename: &str) -> Result<Grid, String> {
@ -78,18 +77,15 @@ fn read_grid(filename: &str) -> Result<Grid, String> {
if digit > 0 { if digit > 0 {
grid.get(row, column).unwrap().set(digit); 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 => {} None => {}
} }
} }
row = row + 1; row = row + 1;
} }
return Ok(grid); return Ok(grid);

View file

@ -1,7 +1,9 @@
use crate::grid::{Cell, Grid, CellValue, Section}; use crate::grid::{Cell, CellValue, Grid, Section};
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics}; use crate::solver::{
use std::rc::Rc; evaluate_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
};
use rand::prelude::*; use rand::prelude::*;
use std::rc::Rc;
pub static mut DEBUG: bool = false; pub static mut DEBUG: bool = false;
@ -16,11 +18,10 @@ impl Grid {
let cell = self.get(x, y).unwrap(); let cell = self.get(x, y).unwrap();
let add_cell = { let add_cell = {
let cell_value = &*cell.value.borrow(); let cell_value = &*cell.value.borrow();
match cell_value { // May cause issues with borrow rules match cell_value {
CellValue::Fixed(_) => {false} // May cause issues with borrow rules
CellValue::Unknown(_) => { CellValue::Fixed(_) => false,
true CellValue::Unknown(_) => true,
}
} }
}; };
if add_cell { if add_cell {
@ -31,7 +32,7 @@ impl Grid {
match empty_cells.iter().choose(rng) { match empty_cells.iter().choose(rng) {
Some(cell) => Ok(Rc::clone(cell)), 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 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 // 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.section
self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); .upgrade()
self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); .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(
eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self); &mut possibilities,
eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self); &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; return possibilities;
} }
@ -95,10 +119,10 @@ impl Section {
let new_possibilities = { let new_possibilities = {
let cell_value = &*cell.value.borrow(); let cell_value = &*cell.value.borrow();
match cell_value { match cell_value {
CellValue::Fixed(_) => { continue; } CellValue::Fixed(_) => {
CellValue::Unknown(_) => { continue;
cell.calculate_possibilities()
} }
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 grid = generate_completed_grid(rng);
let mut num_hints = 81; let mut num_hints = 81;
@ -132,8 +158,8 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
cell_clone.delete_value(); cell_clone.delete_value();
let (status, statistics) =
let (status, statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller); evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
match status { match status {
SolveStatus::Complete(uniqueness) => { SolveStatus::Complete(uniqueness) => {
let uniqueness = uniqueness.unwrap(); let uniqueness = uniqueness.unwrap();
@ -142,17 +168,20 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
num_hints = num_hints - 1; num_hints = num_hints - 1;
grid = grid_clone; 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::Unfinished => {
SolveStatus::Invalid => panic!("Removing constraints should not have set the # of solutions to zero") 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); statistics_option = Some(statistics);
} }
return (grid, num_hints, statistics_option.unwrap()); 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 // 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, search_hidden_singles: true,
find_possibility_groups: true, find_possibility_groups: true,
search_useful_constraint: true, search_useful_constraint: true,
make_guesses: true make_guesses: true,
}; };
let mut grid: Grid = loop { 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 // Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
grid = grid = 'outer: loop {
'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 = 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 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 // Let's scramble the order
cell_possibilities.shuffle(rng); cell_possibilities.shuffle(rng);
for (_index, digit) in cell_possibilities.iter().enumerate() { for (_index, digit) in cell_possibilities.iter().enumerate() {
let mut grid_clone = grid.clone(); let mut grid_clone = grid.clone();
let cell = &*grid_clone.get(cell.x, cell.y).unwrap(); let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
cell.set(*digit); 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 { match status {
SolveStatus::Complete(uniqueness) => { SolveStatus::Complete(uniqueness) => {
let uniqueness = uniqueness.unwrap(); 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::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
SolveStatus::Invalid => continue // Try another guess SolveStatus::Invalid => continue // Try another guess
} }
}
};
// If we reach this point in the loop, then none of the possibilities for cell provided any solution // 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 // 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"); panic!("Unable to continue as puzzle is invalid");
}; };
crate::solver::solve_grid(&mut grid); crate::solver::solve_grid(&mut grid);
@ -245,12 +276,13 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
return grid; return grid;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::grid::*;
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
use crate::generator::generate_grid; 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::prelude::SmallRng;
use rand::SeedableRng; use rand::SeedableRng;
@ -292,17 +324,20 @@ mod tests {
grid.get(8, 2).unwrap().set(6); 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, determine_uniqueness: true,
search_singles: true, search_singles: true,
search_hidden_singles: true, search_hidden_singles: true,
find_possibility_groups: true, find_possibility_groups: true,
search_useful_constraint: true, search_useful_constraint: true,
make_guesses: true make_guesses: true,
}, &mut SolveStatistics::new()); },
&mut SolveStatistics::new(),
);
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique))); 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 // 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, search_hidden_singles: true,
find_possibility_groups: true, find_possibility_groups: true,
search_useful_constraint: true, search_useful_constraint: true,
make_guesses: true make_guesses: true,
}; };
// Note that the puzzle itself doesn't matter // 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; let mut observed_empty_cell = false;
'outer: for x in 0..9 { 'outer: for x in 0..9 {
@ -337,6 +373,5 @@ mod tests {
} }
assert!(observed_empty_cell); assert!(observed_empty_cell);
} }
} }

View file

@ -1,13 +1,13 @@
use std::rc::{Rc, Weak}; use std::cell::RefCell;
use std::cell::{RefCell};
use std::fmt::Formatter; use std::fmt::Formatter;
use std::rc::{Rc, Weak};
pub static mut DEBUG: bool = false; pub static mut DEBUG: bool = false;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum CellValue { pub enum CellValue {
Fixed(u8), 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. /// 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) => { CellValue::Fixed(digit) => {
self.set(digit); self.set(digit);
return; return;
}, }
CellValue::Unknown(_) => { CellValue::Unknown(_) => {
self.set_value_exact(value); self.set_value_exact(value);
} // continue on } // continue on
@ -111,7 +111,10 @@ impl Cell {
pub fn set_value_exact(&self, value: CellValue) { pub fn set_value_exact(&self, value: CellValue) {
unsafe { unsafe {
if DEBUG { 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(); let value = &*self.value.borrow();
match value { match value {
CellValue::Fixed(_) => None, 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(); let mut new_possibilities = possibilities.clone();
match new_possibilities.binary_search(&digit) { 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 { } else {
Some(CellValue::UNKNOWN(new_possibilities)) Some(CellValue::UNKNOWN(new_possibilities))
}*/ }*/
}, }
CellValue::Fixed(_) => {None} CellValue::Fixed(_) => None,
} }
}; };
match new_value_option { match new_value_option {
Some(new_value) => { Some(new_value) => {
cell.set_value(new_value); cell.set_value(new_value);
}, }
None => {} None => {}
} }
} }
} }
} }
@ -200,14 +204,14 @@ pub struct Section {
pub vec: Vec<Rc<Cell>>, pub vec: Vec<Rc<Cell>>,
pub do_update: RefCell<bool>, pub do_update: RefCell<bool>,
pub index: usize, pub index: usize,
pub section_type: SectionType pub section_type: SectionType,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum SectionType { pub enum SectionType {
Row, Row,
Column, Column,
Square Square,
} }
impl Section { impl Section {
@ -225,7 +229,7 @@ impl Section {
vec: Vec::new(), vec: Vec::new(),
do_update: RefCell::new(false), do_update: RefCell::new(false),
index, index,
section_type: line_type section_type: line_type,
} }
} }
@ -250,7 +254,6 @@ pub struct Grid {
impl Grid { impl Grid {
/// Generate a new empty `Grid` with full empty possibilities for each `Cell` /// Generate a new empty `Grid` with full empty possibilities for each `Cell`
pub fn new() -> Grid { pub fn new() -> Grid {
let mut rows: Vec<MultiMut<Section>> = Vec::new(); let mut rows: Vec<MultiMut<Section>> = Vec::new();
let mut columns: Vec<MultiMut<Section>> = Vec::new(); let mut columns: Vec<MultiMut<Section>> = Vec::new();
let mut sections: 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 { for row_index in 0..9 {
let row_rc = unsafe { let row_rc = unsafe { rows.get_unchecked(row_index) };
rows.get_unchecked(row_index)
};
let row_ref = &mut *row_rc.borrow_mut(); let row_ref = &mut *row_rc.borrow_mut();
for column_index in 0..9 { for column_index in 0..9 {
let section_index = (row_index / 3) * 3 + column_index / 3; let section_index = (row_index / 3) * 3 + column_index / 3;
let (column_rc, section_rc) = unsafe { 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); 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])), value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
row: row_weak, row: row_weak,
column: column_weak, column: column_weak,
section: section_weak section: section_weak,
}; };
let ref1 = Rc::new(cell); 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. /// 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. /// Returns None if the coordinates are out of bounds.
pub fn get(&self, r: usize, c: usize) -> Option<Rc<Cell>> { pub fn get(&self, r: usize, c: usize) -> Option<Rc<Cell>> {
let row = match self.rows.get(r) { let row = match self.rows.get(r) {
Some(x) => x, Some(x) => x,
None => return None None => return None,
}; };
let row = &*(&**row).borrow(); let row = &*(&**row).borrow();
let cell = match row.get(c) { let cell = match row.get(c) {
Some(x) => x, Some(x) => x,
None => return None None => return None,
}; };
return Some(Rc::clone(cell)); return Some(Rc::clone(cell));
@ -353,7 +359,7 @@ impl Grid {
smallest_size = possibilities.len(); smallest_size = possibilities.len();
smallest_cell = Some(cell_rc); smallest_cell = Some(cell_rc);
} }
}, }
_ => {} _ => {}
} }
} }
@ -397,24 +403,23 @@ impl Clone for Grid {
impl std::fmt::Display for Grid { impl std::fmt::Display for Grid {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for r in 0..9 { for r in 0..9 {
// Each row corresponds to 3 rows since we leave room for guesses // Each row corresponds to 3 rows since we leave room for guesses
let mut row1 = String::new(); let mut row1 = String::new();
let mut row2 = String::new(); let mut row2 = String::new();
let mut row3 = String::new(); let mut row3 = String::new();
for c in 0..9 { for c in 0..9 {
let cell = &*self.get(r, c).unwrap(); let cell = &*self.get(r, c).unwrap();
let value = &*cell.value.borrow(); let value = &*cell.value.borrow();
match value { match value {
CellValue::Fixed(x) => { CellValue::Fixed(x) => {
row1.push_str(" "); 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(" "); row3.push_str(" ");
}, }
CellValue::Unknown(x) => { CellValue::Unknown(x) => {
Grid::process_unknown(&x, 1, &mut row1); Grid::process_unknown(&x, 1, &mut row1);
Grid::process_unknown(&x, 2, &mut row1); Grid::process_unknown(&x, 2, &mut row1);
@ -439,8 +444,6 @@ impl std::fmt::Display for Grid {
row2.push('┆'); row2.push('┆');
row3.push('┆'); row3.push('┆');
} }
} }
write!(f, "{}", row1)?; write!(f, "{}", row1)?;

View file

@ -1,4 +1,4 @@
pub mod grid;
pub mod solver;
pub mod generator; pub mod generator;
pub mod grid;
pub mod pdf; pub mod pdf;
pub mod solver;

View file

@ -1,7 +1,7 @@
use crate::grid::{CellValue, Grid};
use printpdf::*; use printpdf::*;
use std::fs::File; use std::fs::File;
use std::io::BufWriter; use std::io::BufWriter;
use crate::grid::{Grid, CellValue};
const BOTTOM_LEFT_X: f64 = 10.0; const BOTTOM_LEFT_X: f64 = 10.0;
const BOTTOM_LEFT_Y: f64 = 279.0 - 200.0 - 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 x_offset = 6.1;
let y_offset = -16.5; let y_offset = -16.5;
for r in 0..9 { for r in 0..9 {
let y = Mm(BOTTOM_LEFT_Y + (GRID_DIMENSION / 9.0) * (9.0 - r as f64) + y_offset); 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) => { CellValue::Fixed(digit) => {
let text = digit.to_string(); let text = digit.to_string();
layer.use_text(text, font_size, x, y, &font); 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)?))?; doc.save(&mut BufWriter::new(File::create(filename)?))?;
return Ok(()); return Ok(());
} }
fn draw_empty_grid(layer: &PdfLayerReference) { fn draw_empty_grid(layer: &PdfLayerReference) {
// x represents position on left-right scale // x represents position on left-right scale
// y represents position on up-down 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); let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y)); 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) { fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point) {
let points = vec![(point1, false), (point2, false)]; let points = vec![(point1, false), (point2, false)];
@ -124,7 +118,7 @@ fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point){
is_closed: false, is_closed: false,
has_fill: false, has_fill: false,
has_stroke: true, has_stroke: true,
is_clipping_path: false is_clipping_path: false,
}; };
layer.add_shape(line); layer.add_shape(line);

View file

@ -1,20 +1,19 @@
use crate::grid::{Cell, CellValue, Grid, Section};
use std::rc::Rc; use std::rc::Rc;
use crate::grid::{Cell, Section, Grid, CellValue};
pub static mut DEBUG: bool = false; pub static mut DEBUG: bool = false;
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Uniqueness { pub enum Uniqueness {
Unique, Unique,
NotUnique NotUnique,
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
pub enum SolveStatus { pub enum SolveStatus {
Complete(Option<Uniqueness>), Complete(Option<Uniqueness>),
Unfinished, Unfinished,
Invalid Invalid,
} }
/// See `SolveController` for a description of the solving strategies. /// See `SolveController` for a description of the solving strategies.
@ -23,11 +22,10 @@ enum SolveAction{
HiddenSingle, HiddenSingle,
PossibilityGroup, PossibilityGroup,
UsefulConstraints, UsefulConstraints,
Guess Guess,
} }
impl SolveStatus { impl SolveStatus {
fn increment(self, additional_status: SolveStatus) -> SolveStatus { fn increment(self, additional_status: SolveStatus) -> SolveStatus {
match self { match self {
SolveStatus::Complete(uniqueness_option) => { SolveStatus::Complete(uniqueness_option) => {
@ -37,19 +35,24 @@ impl SolveStatus {
match uniqueness_option.unwrap() { match uniqueness_option.unwrap() {
Uniqueness::NotUnique => SolveStatus::Complete(Some(Uniqueness::NotUnique)), Uniqueness::NotUnique => SolveStatus::Complete(Some(Uniqueness::NotUnique)),
Uniqueness::Unique => match additional_status { Uniqueness::Unique => match additional_status {
SolveStatus::Complete(_) => SolveStatus::Complete(Some(Uniqueness::NotUnique)), SolveStatus::Complete(_) => {
SolveStatus::Unfinished => SolveStatus::Complete(Some(Uniqueness::Unique)), SolveStatus::Complete(Some(Uniqueness::NotUnique))
SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique)) }
SolveStatus::Unfinished => {
SolveStatus::Complete(Some(Uniqueness::Unique))
}
SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique)),
},
} }
} }
} }
}
SolveStatus::Unfinished => match additional_status { SolveStatus::Unfinished => match additional_status {
SolveStatus::Invalid => SolveStatus::Unfinished, 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 hidden_singles: u32,
pub possibility_groups: u32, pub possibility_groups: u32,
pub useful_constraints: u32, pub useful_constraints: u32,
pub guesses: u32 pub guesses: u32,
} }
impl SolveStatistics { impl SolveStatistics {
/// Create a new SolveStatistics with `0` counts set for all the fields. /// Create a new SolveStatistics with `0` counts set for all the fields.
pub fn new() -> SolveStatistics { pub fn new() -> SolveStatistics {
SolveStatistics { SolveStatistics {
@ -129,35 +131,31 @@ impl SolveStatistics {
hidden_singles: 0, hidden_singles: 0,
possibility_groups: 0, possibility_groups: 0,
useful_constraints: 0, useful_constraints: 0,
guesses: 0 guesses: 0,
} }
} }
fn increment(&mut self, action: &SolveAction) { fn increment(&mut self, action: &SolveAction) {
match action { match action {
SolveAction::Single => {self.singles = self.singles + 1} SolveAction::Single => self.singles = self.singles + 1,
SolveAction::HiddenSingle => {self.hidden_singles = self.hidden_singles + 1} SolveAction::HiddenSingle => self.hidden_singles = self.hidden_singles + 1,
SolveAction::PossibilityGroup => {self.possibility_groups = self.possibility_groups + 1} SolveAction::PossibilityGroup => self.possibility_groups = self.possibility_groups + 1,
SolveAction::UsefulConstraints => {self.useful_constraints = self.useful_constraints + 1} SolveAction::UsefulConstraints => self.useful_constraints = self.useful_constraints + 1,
SolveAction::Guess => {self.guesses = self.guesses + 1} SolveAction::Guess => self.guesses = self.guesses + 1,
} }
} }
} }
// Code for identify_and_process_possibility_groups (it uses it's own structs) // Code for identify_and_process_possibility_groups (it uses it's own structs)
mod process_possibility_groups { mod process_possibility_groups {
use crate::grid::{Section, CellValue}; use crate::grid::{CellValue, Section};
use std::collections::HashSet; use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
struct FauxCell { struct FauxCell {
index: usize, index: usize,
possibilities: HashSet<u8>, possibilities: HashSet<u8>,
in_group: bool in_group: bool,
} }
impl FauxCell { impl FauxCell {
@ -166,14 +164,15 @@ mod process_possibility_groups {
} }
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()
} }
@ -188,7 +187,10 @@ mod process_possibility_groups {
pub fn identify_and_process_possibility_groups(line: &Section) -> bool { pub fn identify_and_process_possibility_groups(line: &Section) -> bool {
unsafe { unsafe {
if super::DEBUG { 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 { fn bisect_possibility_groups(line: &Section, cells_of_interest: Vec<usize>) -> bool {
/* /*
Algorithm - Algorithm -
Setup - Let count = 0 Setup - Let count = 0
@ -235,15 +236,15 @@ mod process_possibility_groups {
set.insert(digit.clone()); set.insert(digit.clone());
} }
set set
}, }
CellValue::Fixed(_) => { continue } CellValue::Fixed(_) => continue,
} }
}; };
let faux_cell = FauxCell { let faux_cell = FauxCell {
index: i, index: i,
possibilities: faux_possibilities, possibilities: faux_possibilities,
in_group: false in_group: false,
}; };
faux_line.0.push(faux_cell); 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_cell: Option<&mut FauxCell> = None;
let mut smallest_size = usize::MAX; 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 { if cell.len() < smallest_size {
smallest_size = cell.len(); smallest_size = cell.len();
smallest_cell = Some(cell); smallest_cell = Some(cell);
@ -278,11 +284,15 @@ mod process_possibility_groups {
// Step 2 // Step 2
count = count + smallest_size; count = count + smallest_size;
let possibilities_to_remove = smallest_cell.possibilities.clone(); // Necessary because of mutable borrow rules let possibilities_to_remove = smallest_cell.possibilities.clone(); // Necessary because of mutable borrow rules
// Step 3 // 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); cell.remove(&possibilities_to_remove);
} }
@ -293,7 +303,8 @@ mod process_possibility_groups {
} }
// Now we have to see if this was worth it // 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 // We now have two distinct groups and can separate their possibilities
let mut in_group_possibilities = HashSet::new(); let mut in_group_possibilities = HashSet::new();
let mut out_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 // Collect the possibilities for each group
for (_index, cell) in faux_line.0.iter().enumerate() { for (_index, cell) in faux_line.0.iter().enumerate() {
if cell.in_group { 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 { } 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(); let value = &*real_cell.value.borrow();
match value { match value {
CellValue::Unknown(possibilities) => possibilities.clone(), 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 starting_possibility_size = possibilities.len();
let possibilities_to_remove = match faux_cell.in_group { let possibilities_to_remove = match faux_cell.in_group {
true => &out_group_possibilities, true => &out_group_possibilities,
false => &in_group_possibilities false => &in_group_possibilities,
}; };
for (_i, possibility) in possibilities_to_remove.iter().enumerate() { for (_i, possibility) in possibilities_to_remove.iter().enumerate() {
match possibilities.binary_search(possibility) { match possibilities.binary_search(possibility) {
Ok(x) => { Ok(x) => {
possibilities.remove(x); possibilities.remove(x);
}, }
Err(_) => {} 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; made_change = true;
let new_value = { let new_value = {
if possibilities.len() == 1 { if possibilities.len() == 1 {
@ -374,7 +392,10 @@ mod process_possibility_groups {
fn search_single_possibility(line: &Section) -> bool { fn search_single_possibility(line: &Section) -> bool {
unsafe { unsafe {
if DEBUG { 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); cell.set_value(new_value);
made_change = true; made_change = true;
} }
}, }
None => {} None => {}
} }
} }
@ -401,22 +422,32 @@ fn search_hidden_single(line: &Section) -> bool{
enum Count { enum Count {
None, None,
One(Rc<Cell>), One(Rc<Cell>),
Many Many,
}; };
impl Count { impl Count {
fn increment(&self, cell: Rc<Cell>) -> Count { fn increment(&self, cell: Rc<Cell>) -> Count {
match self { match self {
Count::None => {Count::One(cell)} Count::None => Count::One(cell),
Count::One(_) => {Count::Many} Count::One(_) => Count::Many,
Count::Many => {Count::Many} Count::Many => Count::Many,
} }
} }
} }
let mut made_change = false; 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() { for (_index, cell) in line.vec.iter().enumerate() {
let value = &*cell.value.borrow(); 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)); counts[digit - 1] = counts[digit - 1].increment(Rc::clone(cell));
} }
} }
}, }
CellValue::Fixed(_) => {} // do nothing CellValue::Fixed(_) => {} // do nothing
} }
} }
@ -437,7 +468,7 @@ fn search_hidden_single(line: &Section) -> bool{
Count::One(cell) => { Count::One(cell) => {
cell.set((digit + 1) as u8); cell.set((digit + 1) as u8);
made_change = true; made_change = true;
}, }
_ => {} _ => {}
} }
} }
@ -446,21 +477,21 @@ fn search_hidden_single(line: &Section) -> bool{
} }
mod search_useful_constraint { mod search_useful_constraint {
use crate::grid::{Grid, Section, SectionType, CellValue}; use crate::grid::{CellValue, Grid, Section, SectionType};
use std::rc::{Rc, Weak};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::{Rc, Weak};
enum PossibilityLines { 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,
} }
} }
} }
@ -472,22 +503,34 @@ mod search_useful_constraint{
pub fn search_useful_constraint(grid: &Grid, line: &Section) -> bool { pub fn search_useful_constraint(grid: &Grid, line: &Section) -> bool {
unsafe { unsafe {
if super::DEBUG { 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 mut made_change = false;
let (check_row, check_column, check_section) = match line.section_type { let (check_row, check_column, check_section) = match line.section_type {
SectionType::Row => {(false, false, true)}, SectionType::Row => (false, false, true),
SectionType::Column => {(false, false, true)}, SectionType::Column => (false, false, true),
SectionType::Square => {(true, true, false)}, SectionType::Square => (true, true, false),
}; };
for possibility in 0..9 { for possibility in 0..9 {
let mut rows = match check_row {true => PossibilityLines::None, false => PossibilityLines::Invalid }; let mut rows = match check_row {
let mut columns = match check_column {true => PossibilityLines::None, false => PossibilityLines::Invalid }; true => PossibilityLines::None,
let mut sections = match check_section {true => PossibilityLines::None, false => PossibilityLines::Invalid }; 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 { for cell_id in 0..9 {
let cell_ref = line.get(cell_id).unwrap(); let cell_ref = line.get(cell_id).unwrap();
@ -496,7 +539,8 @@ mod search_useful_constraint{
let value = &*cell_ref.value.borrow(); let value = &*cell_ref.value.borrow();
match value { 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) { if possibility.eq(x) {
rows = process_possibility_line(rows, &cell_ref.row); rows = process_possibility_line(rows, &cell_ref.row);
columns = process_possibility_line(columns, &cell_ref.column); 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 // Check each line and see if we can determine anything
match rows { match rows {
PossibilityLines::Unique(index) => { PossibilityLines::Unique(index) => {
made_change = made_change | made_change = made_change
remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.section_type, line.index); | remove_possibilities_line(
}, grid.rows.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {} _ => {}
} }
match columns { match columns {
PossibilityLines::Unique(index) => { PossibilityLines::Unique(index) => {
made_change = made_change | made_change = made_change
remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.section_type, line.index); | remove_possibilities_line(
}, grid.columns.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {} _ => {}
} }
match sections { match sections {
PossibilityLines::Unique(index) => { PossibilityLines::Unique(index) => {
made_change = made_change | made_change = made_change
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.section_type, line.index); | remove_possibilities_line(
}, grid.sections.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {} _ => {}
} }
} }
return made_change; return made_change;
} }
// 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<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 line = &*(&**line).borrow();
let mut made_change = false; let mut made_change = false;
@ -559,7 +622,7 @@ mod search_useful_constraint{
let parent_line = match initial_line_type { let parent_line = match initial_line_type {
SectionType::Row => &cell.row, SectionType::Row => &cell.row,
SectionType::Column => &cell.column, SectionType::Column => &cell.column,
SectionType::Square => &cell.section SectionType::Square => &cell.section,
}; };
let parent_line = &*parent_line.upgrade().unwrap(); let parent_line = &*parent_line.upgrade().unwrap();
let parent_line = &*parent_line.borrow(); let parent_line = &*parent_line.borrow();
@ -568,44 +631,52 @@ mod search_useful_constraint{
continue; continue;
} }
let new_possibilities = match possibilities.binary_search(&digit_to_remove) { let new_possibilities = match possibilities.binary_search(&digit_to_remove)
{
Ok(x) => { Ok(x) => {
let mut new_possibilities = possibilities.clone(); let mut new_possibilities = possibilities.clone();
new_possibilities.remove(x); new_possibilities.remove(x);
new_possibilities new_possibilities
}, }
Err(_) => { continue; } Err(_) => {
continue;
}
}; };
let new_value; let new_value;
if new_possibilities.len() == 1 { if new_possibilities.len() == 1 {
new_value = CellValue::Fixed(new_possibilities.first().unwrap().clone()); new_value =
CellValue::Fixed(new_possibilities.first().unwrap().clone());
} else { } else {
new_value = CellValue::Unknown(new_possibilities); new_value = CellValue::Unknown(new_possibilities);
} }
new_value new_value
}, }
_ => { continue; } _ => {
continue;
}
} }
}; };
cell.set_value(new_value); cell.set_value(new_value);
made_change = true; made_change = true;
} }
return made_change; return made_change;
} }
// We detected a useful constraint // 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.upgrade().unwrap();
let line = &*(&*line).borrow(); let line = &*(&*line).borrow();
match possibility_line { match possibility_line {
PossibilityLines::None => {PossibilityLines::Unique(line.index)}, PossibilityLines::None => PossibilityLines::Unique(line.index),
PossibilityLines::Invalid => {possibility_line}, PossibilityLines::Invalid => possibility_line,
PossibilityLines::Unique(x) => { PossibilityLines::Unique(x) => {
if line.index.eq(&x) { if line.index.eq(&x) {
possibility_line possibility_line
@ -615,11 +686,14 @@ mod search_useful_constraint{
} }
} }
} }
} }
fn solve_line(
fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){ grid: &Grid,
line: &Section,
solve_controller: &SolveController,
solve_statistics: &mut SolveStatistics,
) {
unsafe { unsafe {
if DEBUG { if DEBUG {
println!("Solving {:?} {}", line.section_type, line.index); 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() { if solve_controller.search_singles() {
unsafe { unsafe {
if DEBUG { 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) { 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() { if solve_controller.search_hidden_singles() {
unsafe { unsafe {
if DEBUG { 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) { 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() { if solve_controller.find_possibility_groups() {
unsafe { unsafe {
if DEBUG { 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) { 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() { if solve_controller.search_useful_constraint() {
unsafe { unsafe {
if DEBUG { 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) { if search_useful_constraint::search_useful_constraint(grid, line) {
solve_statistics.increment(&SolveAction::UsefulConstraints); solve_statistics.increment(&SolveAction::UsefulConstraints);
} }
} }
} }
/// Solves (and modifies) the input `Grid`. Returns a `SolveStatus` and `SolveStatistics`, and /// 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, search_hidden_singles: true,
find_possibility_groups: true, find_possibility_groups: true,
search_useful_constraint: true, search_useful_constraint: true,
make_guesses: true make_guesses: true,
}; };
let mut solve_statistics = SolveStatistics::new(); 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); return (solve_status, solve_statistics);
} }
/// Solves (and modifies) the input `Grid` & `SolveStatistics`. Returns a `SolveStatus`. /// 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 // 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
@ -711,8 +801,8 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
} else { } 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 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; 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 /// 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* /// 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. /// 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 mut_grid = grid.clone();
let mut solve_statistics = SolveStatistics::new(); 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); 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 { loop {
let mut ran_something = false; let mut ran_something = false;
for (_index, line_ref) in grid.rows.iter().enumerate() { 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; return SolveStatus::Unfinished;
} }
@ -776,9 +874,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
appears_complete = false; appears_complete = false;
if possibilities.len() == 0 { if possibilities.len() == 0 {
return SolveStatus::Invalid; return SolveStatus::Invalid;
} }
}, }
CellValue::Fixed(_) => {} CellValue::Fixed(_) => {}
} }
} }
@ -790,16 +887,19 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
return SolveStatus::Complete(Some(Uniqueness::Unique)); 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); solve_statistics.increment(&SolveAction::Guess);
let smallest_cell = grid.find_smallest_cell(); let smallest_cell = grid.find_smallest_cell();
let smallest_cell = match smallest_cell { let smallest_cell = match smallest_cell {
Some(cell) => cell, Some(cell) => cell,
None => return SolveStatus::Invalid None => return SolveStatus::Invalid,
}; };
let possibilities = smallest_cell.get_value_possibilities().unwrap(); 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; 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
let status = solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics); .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 // Keep a copy of grid_copy in case we later mutate grid with it
match status { match status {
SolveStatus::Complete(_) => { SolveStatus::Complete(_) => {
grid_solution = Some(grid_copy); 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 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 { match uniqueness {
Uniqueness::Unique => {continue;} // gotta keep on checking Uniqueness::Unique => {
continue;
} // gotta keep on checking
Uniqueness::NotUnique => { Uniqueness::NotUnique => {
break; // We can stop looking as we already found at least two solutions break; // We can stop looking as we already found at least two solutions
} }
} }
} }
SolveStatus::Unfinished => {continue} // Keep looking for a solution SolveStatus::Unfinished => continue, // Keep looking for a solution
SolveStatus::Invalid => panic!("current_status should not be INVALID at this point") SolveStatus::Invalid => panic!("current_status should not be INVALID at this point"),
} }
} }
// We've finished the for-loop // We've finished the for-loop
match current_status { match current_status {
SolveStatus::Complete(_) => { 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 => { SolveStatus::Unfinished => {
current_status = SolveStatus::Invalid; // We can now say Invalid current_status = SolveStatus::Invalid; // We can now say Invalid
}, }
SolveStatus::Invalid => {} SolveStatus::Invalid => {}
} }
return current_status; return current_status;
} }
#[cfg(test)] #[cfg(test)]
@ -873,14 +979,20 @@ mod tests {
grid.get(0, i).unwrap().set(i as u8 + 1); 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 = grid.rows.first().unwrap();
let line = &*(**line).borrow(); let line = &*(**line).borrow();
search_single_possibility(line); 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] #[test]
@ -895,14 +1007,20 @@ mod tests {
grid.get(1, 7).unwrap().set(2); grid.get(1, 7).unwrap().set(2);
grid.get(1, 8).unwrap().set(3); 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 = grid.rows.first().unwrap();
let line = &*(**line).borrow(); let line = &*(**line).borrow();
process_possibility_groups::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()
);
} }
#[test] #[test]
@ -917,19 +1035,22 @@ mod tests {
grid.get(1, 7).unwrap().set(2); grid.get(1, 7).unwrap().set(2);
grid.get(1, 8).unwrap().set(3); grid.get(1, 8).unwrap().set(3);
assert_eq!(
CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]),
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy()); grid.get(2, 0).unwrap().get_value_copy()
);
let line = grid.rows.first().unwrap(); let line = grid.rows.first().unwrap();
let line = &*(**line).borrow(); let line = &*(**line).borrow();
search_useful_constraint::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()
);
} }
#[test] #[test]
fn test_search_useful_constraint_2() { fn test_search_useful_constraint_2() {
let grid = Grid::new(); let grid = Grid::new();
@ -942,15 +1063,29 @@ mod tests {
grid.get(6, 1).unwrap().set(8); grid.get(6, 1).unwrap().set(8);
grid.get(8, 2).unwrap().set(7); grid.get(8, 2).unwrap().set(7);
grid.get(0, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9])); grid.get(0, 1)
grid.get(1, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9])); .unwrap()
grid.get(2, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2])); .set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9]));
grid.get(4, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7])); grid.get(1, 1)
grid.get(7, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 9])); .unwrap()
grid.get(8, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9])); .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 // 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); println!("{}", grid);
@ -959,8 +1094,10 @@ mod tests {
search_useful_constraint::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] #[test]
@ -985,10 +1122,13 @@ mod tests {
search_hidden_single(&first_row); search_hidden_single(&first_row);
assert_eq!(CellValue::Fixed(1), grid.get(0, 0).unwrap().get_value_copy()); assert_eq!(
assert_eq!(CellValue::Fixed(2), grid.get(0, 2).unwrap().get_value_copy()); CellValue::Fixed(1),
grid.get(0, 0).unwrap().get_value_copy()
);
assert_eq!(
CellValue::Fixed(2),
grid.get(0, 2).unwrap().get_value_copy()
);
} }
} }