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 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")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
129
src/generator.rs
129
src/generator.rs
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
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::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)?;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
12
src/pdf.rs
12
src/pdf.rs
|
@ -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);
|
||||||
|
|
434
src/solver.rs
434
src/solver.rs
|
@ -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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue