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 sudoku_solver::grid::{Grid, CellValue};
use std::error::Error;
use std::io::Write;
use sudoku_solver::solver::{SolveController, SolveStatistics};
use std::str::FromStr;
use std::sync::{mpsc, Arc};
use std::process::exit;
use std::thread;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc};
use std::thread;
use sudoku_solver::grid::{CellValue, Grid};
use sudoku_solver::solver::{SolveController, SolveStatistics};
/*
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
@ -24,7 +24,7 @@ enum Difficulty {
Challenge,
Hard,
Medium,
Easy
Easy,
}
impl Difficulty {
@ -35,17 +35,17 @@ impl Difficulty {
search_hidden_singles: true,
find_possibility_groups: true,
search_useful_constraint: true,
make_guesses: true
make_guesses: true,
};
match self {
Difficulty::Challenge => {}, // Do nothing, already hard
Difficulty::Challenge => {} // Do nothing, already hard
Difficulty::Hard => {
controller.make_guesses = false;
}
Difficulty::Medium => {
controller.make_guesses = false;
},
}
Difficulty::Easy => {
controller.make_guesses = false;
controller.search_useful_constraint = false;
@ -59,24 +59,28 @@ impl Difficulty {
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
match self {
Difficulty::Challenge => {
(solve_statistics.guesses > 0) && (solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
(solve_statistics.guesses > 0)
&& (solve_statistics.possibility_groups > 20)
&& (solve_statistics.useful_constraints > 20)
}
Difficulty::Hard => {
(solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
(solve_statistics.possibility_groups > 20)
&& (solve_statistics.useful_constraints > 20)
}
Difficulty::Medium => {
(solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10)
(solve_statistics.possibility_groups > 10)
&& (solve_statistics.useful_constraints > 10)
}
Difficulty::Easy => {true} // easy has no minimum
Difficulty::Easy => true, // easy has no minimum
}
}
}
impl FromStr for Difficulty { // Needed for argparse
impl FromStr for Difficulty {
// Needed for argparse
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("EASY") {
return Ok(Difficulty::Easy);
} else if s.eq_ignore_ascii_case("MEDIUM") {
@ -92,7 +96,6 @@ impl FromStr for Difficulty { // Needed for argparse
}
fn main() {
let mut debug = false;
let mut max_hints = 81;
let mut max_attempts = 100;
@ -100,41 +103,67 @@ fn main() {
let mut difficulty = Difficulty::Challenge;
let mut threads = 1;
{ // this block limits scope of borrows by ap.refer() method
{
// this block limits scope of borrows by ap.refer() method
let mut ap = argparse::ArgumentParser::new();
ap.set_description("Generate Sudoku puzzles");
ap.refer(&mut debug)
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
ap.refer(&mut max_hints)
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints");
ap.refer(&mut max_hints).add_option(
&["--hints"],
argparse::Store,
"Only return a puzzle with less than or equal to this number of hints",
);
ap.refer(&mut max_attempts)
.add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100");
ap.refer(&mut filename)
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV");
ap.refer(&mut filename).add_argument(
"filename",
argparse::StoreOption,
"Optional filename to store puzzle in as a CSV",
);
ap.refer(&mut difficulty)
.add_option(&["-d", "--difficulty"], argparse::Store, "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE");
ap.refer(&mut difficulty).add_option(
&["-d", "--difficulty"],
argparse::Store,
"Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE",
);
ap.refer(&mut threads)
.add_option(&["--threads"], argparse::Store, "Number of threads to use when generating possible puzzles");
ap.refer(&mut threads).add_option(
&["--threads"],
argparse::Store,
"Number of threads to use when generating possible puzzles",
);
ap.parse_args_or_exit();
}
let solve_controller = difficulty.map_to_solve_controller();
let (result, num_attempts) =
if threads < 1 {
let (result, num_attempts) = if threads < 1 {
eprintln!("--threads must be at least 1");
exit(1);
} else if threads == 1 {
let mut rng = SmallRng::from_entropy();
get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints, &AtomicBool::new(false))
get_puzzle_matching_conditions(
&mut rng,
&difficulty,
&solve_controller,
max_attempts,
max_hints,
&AtomicBool::new(false),
)
} else {
run_multi_threaded(max_attempts, max_hints, threads, debug, solve_controller, difficulty)
run_multi_threaded(
max_attempts,
max_hints,
threads,
debug,
solve_controller,
difficulty,
)
};
let (grid, solve_statistics, num_hints) = match result {
@ -145,16 +174,27 @@ fn main() {
}
};
println!("{}", grid);
println!("Puzzle has {} hints and was found in {} attempts.", num_hints, num_attempts);
println!(
"Puzzle has {} hints and was found in {} attempts.",
num_hints, num_attempts
);
if debug {
println!("Solving this puzzle involves roughly:");
println!("\t{} SINGLE actions", solve_statistics.singles);
println!("\t{} HIDDEN_SINGLE actions", solve_statistics.hidden_singles);
println!("\t{} USEFUL_CONSTRAINT actions", solve_statistics.useful_constraints);
println!("\t{} POSSIBILITY_GROUP actions", solve_statistics.possibility_groups);
println!(
"\t{} HIDDEN_SINGLE actions",
solve_statistics.hidden_singles
);
println!(
"\t{} USEFUL_CONSTRAINT actions",
solve_statistics.useful_constraints
);
println!(
"\t{} POSSIBILITY_GROUP actions",
solve_statistics.possibility_groups
);
println!("\t{} GUESS actions", solve_statistics.guesses);
}
@ -168,13 +208,19 @@ fn main() {
save_grid_csv(&grid, &filename).unwrap();
println!("Grid saved as CSV to {}", filename);
}
},
}
None => {}
}
}
fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bool, solve_controller: SolveController, difficulty: Difficulty) -> (Option<(Grid, SolveStatistics, i32)>, i32){
fn run_multi_threaded(
max_attempts: i32,
max_hints: i32,
threads: i32,
debug: bool,
solve_controller: SolveController,
difficulty: Difficulty,
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
let mut thread_rng = thread_rng();
let (transmitter, receiver) = mpsc::channel();
let mut remaining_attempts = max_attempts;
@ -195,11 +241,18 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
}
let should_stop = &*should_stop;
let (result, num_attempts) = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, thread_attempts, max_hints, should_stop);
let (result, num_attempts) = get_puzzle_matching_conditions(
&mut rng,
&difficulty,
&solve_controller,
thread_attempts,
max_hints,
should_stop,
);
let mut result_was_some = false;
let result = match result {
None => {None}
None => None,
Some((grid, solve_statistics, num_hints)) => {
result_was_some = true;
Some((SafeGridWrapper(grid), solve_statistics, num_hints))
@ -209,7 +262,10 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
cloned_transmitter.send((result, num_attempts)).unwrap();
if debug {
println!("Thread {}, terminated having run {} attempts; did send result: {}", i, num_attempts, result_was_some);
println!(
"Thread {}, terminated having run {} attempts; did send result: {}",
i, num_attempts, result_was_some
);
}
});
}
@ -237,12 +293,19 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
return (result_to_return, attempt_count);
}
fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32, should_stop: &AtomicBool) -> (Option<(Grid, SolveStatistics, i32)>, i32){
fn get_puzzle_matching_conditions(
rng: &mut SmallRng,
difficulty: &Difficulty,
solve_controller: &SolveController,
max_attempts: i32,
max_hints: i32,
should_stop: &AtomicBool,
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
let mut num_attempts = 0;
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) {
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller);
let (grid, num_hints, solve_statistics) =
sudoku_solver::generator::generate_grid(rng, &solve_controller);
num_attempts += 1;
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
@ -261,10 +324,9 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
for y in 0..9 {
let cell = grid.get(x, y).unwrap();
let value = &*cell.value.borrow();
let digit =
match value {
CellValue::Fixed(digit) => {*digit}
CellValue::Unknown(_) => {0}
let digit = match value {
CellValue::Fixed(digit) => *digit,
CellValue::Unknown(_) => 0,
};
let mut text = digit.to_string();
@ -272,7 +334,6 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
text.push(',');
}
file.write(text.as_bytes())?;
}
file.write(b"\n")?;
}

View file

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

View file

@ -1,7 +1,9 @@
use crate::grid::{Cell, Grid, CellValue, Section};
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics};
use std::rc::Rc;
use crate::grid::{Cell, CellValue, Grid, Section};
use crate::solver::{
evaluate_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
};
use rand::prelude::*;
use std::rc::Rc;
pub static mut DEBUG: bool = false;
@ -16,11 +18,10 @@ impl Grid {
let cell = self.get(x, y).unwrap();
let add_cell = {
let cell_value = &*cell.value.borrow();
match cell_value { // May cause issues with borrow rules
CellValue::Fixed(_) => {false}
CellValue::Unknown(_) => {
true
}
match cell_value {
// May cause issues with borrow rules
CellValue::Fixed(_) => false,
CellValue::Unknown(_) => true,
}
};
if add_cell {
@ -31,7 +32,7 @@ impl Grid {
match empty_cells.iter().choose(rng) {
Some(cell) => Ok(Rc::clone(cell)),
None => Err("Unable to find an empty cell")
None => Err("Unable to find an empty cell"),
}
}
}
@ -47,10 +48,21 @@ impl Cell {
self.set_value_exact(CellValue::Unknown(vec![])); // placeholder
// This will reset all the possibilities for this cell and the ones that might have been limited by this cell
self.section.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
self.section
.upgrade()
.unwrap()
.borrow()
.recalculate_and_set_possibilities();
self.row
.upgrade()
.unwrap()
.borrow()
.recalculate_and_set_possibilities();
self.column
.upgrade()
.unwrap()
.borrow()
.recalculate_and_set_possibilities();
}
/**
@ -80,9 +92,21 @@ impl Cell {
}
}
eliminate_possibilities(&mut possibilities, &self.section.upgrade().unwrap().borrow(), self);
eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self);
eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self);
eliminate_possibilities(
&mut possibilities,
&self.section.upgrade().unwrap().borrow(),
self,
);
eliminate_possibilities(
&mut possibilities,
&self.row.upgrade().unwrap().borrow(),
self,
);
eliminate_possibilities(
&mut possibilities,
&self.column.upgrade().unwrap().borrow(),
self,
);
return possibilities;
}
@ -95,10 +119,10 @@ impl Section {
let new_possibilities = {
let cell_value = &*cell.value.borrow();
match cell_value {
CellValue::Fixed(_) => { continue; }
CellValue::Unknown(_) => {
cell.calculate_possibilities()
CellValue::Fixed(_) => {
continue;
}
CellValue::Unknown(_) => cell.calculate_possibilities(),
}
};
@ -107,8 +131,10 @@ impl Section {
}
}
pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> (Grid, i32, SolveStatistics) {
pub fn generate_grid(
rng: &mut SmallRng,
solve_controller: &SolveController,
) -> (Grid, i32, SolveStatistics) {
let mut grid = generate_completed_grid(rng);
let mut num_hints = 81;
@ -132,8 +158,8 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
cell_clone.delete_value();
let (status, statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
let (status, statistics) =
evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
match status {
SolveStatus::Complete(uniqueness) => {
let uniqueness = uniqueness.unwrap();
@ -142,17 +168,20 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
num_hints = num_hints - 1;
grid = grid_clone;
}
Uniqueness::NotUnique => continue // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller)
Uniqueness::NotUnique => continue, // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller)
}
}
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED"),
SolveStatus::Invalid => panic!("Removing constraints should not have set the # of solutions to zero")
SolveStatus::Unfinished => {
panic!("evaluate_grid_with_solve_controller should never return UNFINISHED")
}
SolveStatus::Invalid => {
panic!("Removing constraints should not have set the # of solutions to zero")
}
}
statistics_option = Some(statistics);
}
return (grid, num_hints, statistics_option.unwrap());
}
// We generate a completed grid with no mind for difficulty; afterward generate_puzzle will take out as many fields as it can with regards to the difficulty
@ -163,7 +192,7 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
search_hidden_singles: true,
find_possibility_groups: true,
search_useful_constraint: true,
make_guesses: true
make_guesses: true,
};
let mut grid: Grid = loop {
@ -199,23 +228,24 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
};
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
grid =
'outer: loop {
grid = 'outer: loop {
let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
let cell = &*cell;
let mut cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities");
let mut cell_possibilities = cell
.get_value_possibilities()
.expect("An empty cell has no possibilities");
// Let's scramble the order
cell_possibilities.shuffle(rng);
for (_index, digit) in cell_possibilities.iter().enumerate() {
let mut grid_clone = grid.clone();
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
cell.set(*digit);
let (status, _statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
let (status, _statistics) =
evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
match status {
SolveStatus::Complete(uniqueness) => {
let uniqueness = uniqueness.unwrap();
@ -230,14 +260,15 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
SolveStatus::Invalid => continue // Try another guess
}
};
}
// If we reach this point in the loop, then none of the possibilities for cell provided any solution
// Which means something serious happened before in the solving process - reason for panic
eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y);
eprint!(
"No valid hints were found for puzzle\n{} at cell ({}, {})",
grid, cell.x, cell.y
);
panic!("Unable to continue as puzzle is invalid");
};
crate::solver::solve_grid(&mut grid);
@ -245,12 +276,13 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
return grid;
}
#[cfg(test)]
mod tests {
use crate::grid::*;
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
use crate::generator::generate_grid;
use crate::grid::*;
use crate::solver::{
solve_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
};
use rand::prelude::SmallRng;
use rand::SeedableRng;
@ -292,17 +324,20 @@ mod tests {
grid.get(8, 2).unwrap().set(6);
let status = solve_grid_with_solve_controller(&mut grid, &SolveController{
let status = solve_grid_with_solve_controller(
&mut grid,
&SolveController {
determine_uniqueness: true,
search_singles: true,
search_hidden_singles: true,
find_possibility_groups: true,
search_useful_constraint: true,
make_guesses: true
}, &mut SolveStatistics::new());
make_guesses: true,
},
&mut SolveStatistics::new(),
);
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique)));
}
// There was a bug where even though mutate_grid was set to false, the end result was still solved
@ -314,11 +349,12 @@ mod tests {
search_hidden_singles: true,
find_possibility_groups: true,
search_useful_constraint: true,
make_guesses: true
make_guesses: true,
};
// Note that the puzzle itself doesn't matter
let (grid, _num_hints, _statistics) = generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
let (grid, _num_hints, _statistics) =
generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
let mut observed_empty_cell = false;
'outer: for x in 0..9 {
@ -337,6 +373,5 @@ mod tests {
}
assert!(observed_empty_cell);
}
}

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

View file

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

View file

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

View file

@ -1,20 +1,19 @@
use crate::grid::{Cell, CellValue, Grid, Section};
use std::rc::Rc;
use crate::grid::{Cell, Section, Grid, CellValue};
pub static mut DEBUG: bool = false;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Uniqueness {
Unique,
NotUnique
NotUnique,
}
#[derive(Eq, PartialEq, Debug)]
pub enum SolveStatus {
Complete(Option<Uniqueness>),
Unfinished,
Invalid
Invalid,
}
/// See `SolveController` for a description of the solving strategies.
@ -23,11 +22,10 @@ enum SolveAction{
HiddenSingle,
PossibilityGroup,
UsefulConstraints,
Guess
Guess,
}
impl SolveStatus {
fn increment(self, additional_status: SolveStatus) -> SolveStatus {
match self {
SolveStatus::Complete(uniqueness_option) => {
@ -37,19 +35,24 @@ impl SolveStatus {
match uniqueness_option.unwrap() {
Uniqueness::NotUnique => SolveStatus::Complete(Some(Uniqueness::NotUnique)),
Uniqueness::Unique => match additional_status {
SolveStatus::Complete(_) => SolveStatus::Complete(Some(Uniqueness::NotUnique)),
SolveStatus::Unfinished => SolveStatus::Complete(Some(Uniqueness::Unique)),
SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique))
SolveStatus::Complete(_) => {
SolveStatus::Complete(Some(Uniqueness::NotUnique))
}
SolveStatus::Unfinished => {
SolveStatus::Complete(Some(Uniqueness::Unique))
}
SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique)),
},
}
}
}
}
SolveStatus::Unfinished => match additional_status {
SolveStatus::Invalid => SolveStatus::Unfinished,
_ => additional_status
_ => additional_status,
},
SolveStatus::Invalid => panic!("increment() shouldn't be called on SolveStatus::Invalid")
SolveStatus::Invalid => {
panic!("increment() shouldn't be called on SolveStatus::Invalid")
}
}
}
}
@ -117,11 +120,10 @@ pub struct SolveStatistics {
pub hidden_singles: u32,
pub possibility_groups: u32,
pub useful_constraints: u32,
pub guesses: u32
pub guesses: u32,
}
impl SolveStatistics {
/// Create a new SolveStatistics with `0` counts set for all the fields.
pub fn new() -> SolveStatistics {
SolveStatistics {
@ -129,35 +131,31 @@ impl SolveStatistics {
hidden_singles: 0,
possibility_groups: 0,
useful_constraints: 0,
guesses: 0
guesses: 0,
}
}
fn increment(&mut self, action: &SolveAction) {
match action {
SolveAction::Single => {self.singles = self.singles + 1}
SolveAction::HiddenSingle => {self.hidden_singles = self.hidden_singles + 1}
SolveAction::PossibilityGroup => {self.possibility_groups = self.possibility_groups + 1}
SolveAction::UsefulConstraints => {self.useful_constraints = self.useful_constraints + 1}
SolveAction::Guess => {self.guesses = self.guesses + 1}
SolveAction::Single => self.singles = self.singles + 1,
SolveAction::HiddenSingle => self.hidden_singles = self.hidden_singles + 1,
SolveAction::PossibilityGroup => self.possibility_groups = self.possibility_groups + 1,
SolveAction::UsefulConstraints => self.useful_constraints = self.useful_constraints + 1,
SolveAction::Guess => self.guesses = self.guesses + 1,
}
}
}
// Code for identify_and_process_possibility_groups (it uses it's own structs)
mod process_possibility_groups {
use crate::grid::{Section, CellValue};
use crate::grid::{CellValue, Section};
use std::collections::HashSet;
use std::rc::Rc;
struct FauxCell {
index: usize,
possibilities: HashSet<u8>,
in_group: bool
in_group: bool,
}
impl FauxCell {
@ -166,14 +164,15 @@ mod process_possibility_groups {
}
fn remove(&mut self, to_remove: &HashSet<u8>) {
to_remove.iter().for_each(|digit| {self.possibilities.remove(digit);});
to_remove.iter().for_each(|digit| {
self.possibilities.remove(digit);
});
}
}
struct FauxLine(Vec<FauxCell>);
impl FauxLine {
fn num_in_group(&self) -> usize {
self.0.iter().filter(|faux_cell| faux_cell.in_group).count()
}
@ -188,7 +187,10 @@ mod process_possibility_groups {
pub fn identify_and_process_possibility_groups(line: &Section) -> bool {
unsafe {
if super::DEBUG {
println!("Looking for possibility groups on line {:?} {}", line.section_type, line.index);
println!(
"Looking for possibility groups on line {:?} {}",
line.section_type, line.index
);
}
}
@ -196,7 +198,6 @@ mod process_possibility_groups {
}
fn bisect_possibility_groups(line: &Section, cells_of_interest: Vec<usize>) -> bool {
/*
Algorithm -
Setup - Let count = 0
@ -235,15 +236,15 @@ mod process_possibility_groups {
set.insert(digit.clone());
}
set
},
CellValue::Fixed(_) => { continue }
}
CellValue::Fixed(_) => continue,
}
};
let faux_cell = FauxCell {
index: i,
possibilities: faux_possibilities,
in_group: false
in_group: false,
};
faux_line.0.push(faux_cell);
@ -265,7 +266,12 @@ mod process_possibility_groups {
let mut smallest_cell: Option<&mut FauxCell> = None;
let mut smallest_size = usize::MAX;
for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() {
for (_index, cell) in faux_line
.0
.iter_mut()
.filter(|faux_cell| !faux_cell.in_group)
.enumerate()
{
if cell.len() < smallest_size {
smallest_size = cell.len();
smallest_cell = Some(cell);
@ -278,11 +284,15 @@ mod process_possibility_groups {
// Step 2
count = count + smallest_size;
let possibilities_to_remove = smallest_cell.possibilities.clone(); // Necessary because of mutable borrow rules
// Step 3
for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() {
for (_index, cell) in faux_line
.0
.iter_mut()
.filter(|faux_cell| !faux_cell.in_group)
.enumerate()
{
cell.remove(&possibilities_to_remove);
}
@ -293,7 +303,8 @@ mod process_possibility_groups {
}
// Now we have to see if this was worth it
if faux_line.num_out_group() > 0 { // Worth it
if faux_line.num_out_group() > 0 {
// Worth it
// We now have two distinct groups and can separate their possibilities
let mut in_group_possibilities = HashSet::new();
let mut out_group_possibilities = HashSet::new();
@ -301,9 +312,13 @@ mod process_possibility_groups {
// Collect the possibilities for each group
for (_index, cell) in faux_line.0.iter().enumerate() {
if cell.in_group {
cell.possibilities.iter().for_each(|digit| {in_group_possibilities.insert(digit.clone());});
cell.possibilities.iter().for_each(|digit| {
in_group_possibilities.insert(digit.clone());
});
} else {
cell.possibilities.iter().for_each(|digit| {out_group_possibilities.insert(digit.clone());});
cell.possibilities.iter().for_each(|digit| {
out_group_possibilities.insert(digit.clone());
});
}
}
@ -314,26 +329,29 @@ mod process_possibility_groups {
let value = &*real_cell.value.borrow();
match value {
CellValue::Unknown(possibilities) => possibilities.clone(),
CellValue::Fixed(_) => {panic!("Faux_cell shouldn't have linked to fixed cell")}
CellValue::Fixed(_) => {
panic!("Faux_cell shouldn't have linked to fixed cell")
}
}
};
let starting_possibility_size = possibilities.len();
let possibilities_to_remove = match faux_cell.in_group {
true => &out_group_possibilities,
false => &in_group_possibilities
false => &in_group_possibilities,
};
for (_i, possibility) in possibilities_to_remove.iter().enumerate() {
match possibilities.binary_search(possibility) {
Ok(x) => {
possibilities.remove(x);
},
}
Err(_) => {}
};
}
if possibilities.len() < starting_possibility_size { // We have a change to make
if possibilities.len() < starting_possibility_size {
// We have a change to make
made_change = true;
let new_value = {
if possibilities.len() == 1 {
@ -374,7 +392,10 @@ mod process_possibility_groups {
fn search_single_possibility(line: &Section) -> bool {
unsafe {
if DEBUG {
println!("search_single_possibility on line {:?} {}", line.section_type, line.index);
println!(
"search_single_possibility on line {:?} {}",
line.section_type, line.index
);
}
}
@ -388,7 +409,7 @@ fn search_single_possibility(line: &Section) -> bool{
cell.set_value(new_value);
made_change = true;
}
},
}
None => {}
}
}
@ -401,22 +422,32 @@ fn search_hidden_single(line: &Section) -> bool{
enum Count {
None,
One(Rc<Cell>),
Many
Many,
};
impl Count {
fn increment(&self, cell: Rc<Cell>) -> Count {
match self {
Count::None => {Count::One(cell)}
Count::One(_) => {Count::Many}
Count::Many => {Count::Many}
Count::None => Count::One(cell),
Count::One(_) => Count::Many,
Count::Many => Count::Many,
}
}
}
let mut made_change = false;
let mut counts = [Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None];
let mut counts = [
Count::None,
Count::None,
Count::None,
Count::None,
Count::None,
Count::None,
Count::None,
Count::None,
Count::None,
];
for (_index, cell) in line.vec.iter().enumerate() {
let value = &*cell.value.borrow();
@ -427,7 +458,7 @@ fn search_hidden_single(line: &Section) -> bool{
counts[digit - 1] = counts[digit - 1].increment(Rc::clone(cell));
}
}
},
}
CellValue::Fixed(_) => {} // do nothing
}
}
@ -437,7 +468,7 @@ fn search_hidden_single(line: &Section) -> bool{
Count::One(cell) => {
cell.set((digit + 1) as u8);
made_change = true;
},
}
_ => {}
}
}
@ -446,21 +477,21 @@ fn search_hidden_single(line: &Section) -> bool{
}
mod search_useful_constraint {
use crate::grid::{Grid, Section, SectionType, CellValue};
use std::rc::{Rc, Weak};
use crate::grid::{CellValue, Grid, Section, SectionType};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
enum PossibilityLines {
Unique(usize),
Invalid,
None
None,
}
impl PossibilityLines {
fn is_invalid(&self) -> bool {
match &self {
PossibilityLines::Invalid => true,
_ => false
_ => false,
}
}
}
@ -472,22 +503,34 @@ mod search_useful_constraint{
pub fn search_useful_constraint(grid: &Grid, line: &Section) -> bool {
unsafe {
if super::DEBUG {
println!("Searching for a useful constraint on line {:?} {}", line.section_type, line.index);
println!(
"Searching for a useful constraint on line {:?} {}",
line.section_type, line.index
);
}
}
let mut made_change = false;
let (check_row, check_column, check_section) = match line.section_type {
SectionType::Row => {(false, false, true)},
SectionType::Column => {(false, false, true)},
SectionType::Square => {(true, true, false)},
SectionType::Row => (false, false, true),
SectionType::Column => (false, false, true),
SectionType::Square => (true, true, false),
};
for possibility in 0..9 {
let mut rows = match check_row {true => PossibilityLines::None, false => PossibilityLines::Invalid };
let mut columns = match check_column {true => PossibilityLines::None, false => PossibilityLines::Invalid };
let mut sections = match check_section {true => PossibilityLines::None, false => PossibilityLines::Invalid };
let mut rows = match check_row {
true => PossibilityLines::None,
false => PossibilityLines::Invalid,
};
let mut columns = match check_column {
true => PossibilityLines::None,
false => PossibilityLines::Invalid,
};
let mut sections = match check_section {
true => PossibilityLines::None,
false => PossibilityLines::Invalid,
};
for cell_id in 0..9 {
let cell_ref = line.get(cell_id).unwrap();
@ -496,7 +539,8 @@ mod search_useful_constraint{
let value = &*cell_ref.value.borrow();
match value {
CellValue::Fixed(x) => { // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
CellValue::Fixed(x) => {
// We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
if possibility.eq(x) {
rows = process_possibility_line(rows, &cell_ref.row);
columns = process_possibility_line(columns, &cell_ref.column);
@ -521,33 +565,52 @@ mod search_useful_constraint{
// Check each line and see if we can determine anything
match rows {
PossibilityLines::Unique(index) => {
made_change = made_change |
remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.section_type, line.index);
},
made_change = made_change
| remove_possibilities_line(
grid.rows.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {}
}
match columns {
PossibilityLines::Unique(index) => {
made_change = made_change |
remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.section_type, line.index);
},
made_change = made_change
| remove_possibilities_line(
grid.columns.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {}
}
match sections {
PossibilityLines::Unique(index) => {
made_change = made_change |
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.section_type, line.index);
},
made_change = made_change
| remove_possibilities_line(
grid.sections.get(index).unwrap(),
possibility,
&line.section_type,
line.index,
);
}
_ => {}
}
}
return made_change;
}
// initial_line_type and initial_line_index are to identify the cells that should NOT have their possibilities removed
fn remove_possibilities_line(line: &Rc<RefCell<Section>>, digit_to_remove: u8, initial_line_type: &SectionType, initial_line_index: usize) -> bool {
fn remove_possibilities_line(
line: &Rc<RefCell<Section>>,
digit_to_remove: u8,
initial_line_type: &SectionType,
initial_line_index: usize,
) -> bool {
let line = &*(&**line).borrow();
let mut made_change = false;
@ -559,7 +622,7 @@ mod search_useful_constraint{
let parent_line = match initial_line_type {
SectionType::Row => &cell.row,
SectionType::Column => &cell.column,
SectionType::Square => &cell.section
SectionType::Square => &cell.section,
};
let parent_line = &*parent_line.upgrade().unwrap();
let parent_line = &*parent_line.borrow();
@ -568,44 +631,52 @@ mod search_useful_constraint{
continue;
}
let new_possibilities = match possibilities.binary_search(&digit_to_remove) {
let new_possibilities = match possibilities.binary_search(&digit_to_remove)
{
Ok(x) => {
let mut new_possibilities = possibilities.clone();
new_possibilities.remove(x);
new_possibilities
},
Err(_) => { continue; }
}
Err(_) => {
continue;
}
};
let new_value;
if new_possibilities.len() == 1 {
new_value = CellValue::Fixed(new_possibilities.first().unwrap().clone());
new_value =
CellValue::Fixed(new_possibilities.first().unwrap().clone());
} else {
new_value = CellValue::Unknown(new_possibilities);
}
new_value
},
_ => { continue; }
}
_ => {
continue;
}
}
};
cell.set_value(new_value);
made_change = true;
}
return made_change;
}
// We detected a useful constraint
fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefCell<Section>>) -> PossibilityLines {
fn process_possibility_line(
possibility_line: PossibilityLines,
line: &Weak<RefCell<Section>>,
) -> PossibilityLines {
let line = line.upgrade().unwrap();
let line = &*(&*line).borrow();
match possibility_line {
PossibilityLines::None => {PossibilityLines::Unique(line.index)},
PossibilityLines::Invalid => {possibility_line},
PossibilityLines::None => PossibilityLines::Unique(line.index),
PossibilityLines::Invalid => possibility_line,
PossibilityLines::Unique(x) => {
if line.index.eq(&x) {
possibility_line
@ -615,11 +686,14 @@ mod search_useful_constraint{
}
}
}
}
fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){
fn solve_line(
grid: &Grid,
line: &Section,
solve_controller: &SolveController,
solve_statistics: &mut SolveStatistics,
) {
unsafe {
if DEBUG {
println!("Solving {:?} {}", line.section_type, line.index);
@ -631,7 +705,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
if solve_controller.search_singles() {
unsafe {
if DEBUG {
println!("Searching for singles on line {:?} of {}\n{}", line.section_type, line.index, grid);
println!(
"Searching for singles on line {:?} of {}\n{}",
line.section_type, line.index, grid
);
}
}
if search_single_possibility(line) {
@ -642,7 +719,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
if solve_controller.search_hidden_singles() {
unsafe {
if DEBUG {
println!("Searching for hidden singles on line {:?} of {}\n{}", line.section_type, line.index, grid);
println!(
"Searching for hidden singles on line {:?} of {}\n{}",
line.section_type, line.index, grid
);
}
}
if search_hidden_single(line) {
@ -653,7 +733,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
if solve_controller.find_possibility_groups() {
unsafe {
if DEBUG {
println!("Searching for possibility groups on line {:?} of {}\n{}", line.section_type, line.index, grid);
println!(
"Searching for possibility groups on line {:?} of {}\n{}",
line.section_type, line.index, grid
);
}
}
if process_possibility_groups::identify_and_process_possibility_groups(line) {
@ -664,14 +747,16 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s
if solve_controller.search_useful_constraint() {
unsafe {
if DEBUG {
println!("Searching for useful constraints on line {:?} of {}\n{}", line.section_type, line.index, grid);
println!(
"Searching for useful constraints on line {:?} of {}\n{}",
line.section_type, line.index, grid
);
}
}
if search_useful_constraint::search_useful_constraint(grid, line) {
solve_statistics.increment(&SolveAction::UsefulConstraints);
}
}
}
/// Solves (and modifies) the input `Grid`. Returns a `SolveStatus` and `SolveStatistics`, and
@ -685,17 +770,22 @@ pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) {
search_hidden_singles: true,
find_possibility_groups: true,
search_useful_constraint: true,
make_guesses: true
make_guesses: true,
};
let mut solve_statistics = SolveStatistics::new();
let solve_status = solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics);
let solve_status =
solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics);
return (solve_status, solve_statistics);
}
/// Solves (and modifies) the input `Grid` & `SolveStatistics`. Returns a `SolveStatus`.
pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
pub fn solve_grid_with_solve_controller(
grid: &mut Grid,
solve_controller: &SolveController,
solve_statistics: &mut SolveStatistics,
) -> SolveStatus {
// Code is kind of messy so here it goes - solve_grid first tries to solve without any guesses
// If that's not enough and a guess is required, then solve_grid_guess is called
// solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them
@ -711,8 +801,8 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
} else {
SolveStatus::Complete(Some(Uniqueness::NotUnique)) // solve_grid_no_guess couldn't finish and we can't make guesses, so it's 'not unique' in the sense that we need more guesses
}
},
_ => {status}
}
_ => status,
};
return status;
@ -721,17 +811,24 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
/// Similar to `solve_grid_with_solve_controller` except that we don't modify the input Grid; we
/// only determine SolveStatus. This is useful when generating puzzles where we need to know *if*
/// a puzzle can be solved but don't want to actually solve it.
pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &SolveController) -> (SolveStatus, SolveStatistics){
pub fn evaluate_grid_with_solve_controller(
grid: &Grid,
solve_controller: &SolveController,
) -> (SolveStatus, SolveStatistics) {
let mut mut_grid = grid.clone();
let mut solve_statistics = SolveStatistics::new();
let solve_status = solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics);
let solve_status =
solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics);
return (solve_status, solve_statistics);
}
fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
fn solve_grid_no_guess(
grid: &mut Grid,
solve_controller: &SolveController,
solve_statistics: &mut SolveStatistics,
) -> SolveStatus {
loop {
let mut ran_something = false;
for (_index, line_ref) in grid.rows.iter().enumerate() {
@ -759,7 +856,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
}
}
if !ran_something{ // No lines have changed since we last analyzed them
if !ran_something {
// No lines have changed since we last analyzed them
return SolveStatus::Unfinished;
}
@ -776,9 +874,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
appears_complete = false;
if possibilities.len() == 0 {
return SolveStatus::Invalid;
}
},
}
CellValue::Fixed(_) => {}
}
}
@ -790,16 +887,19 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv
return SolveStatus::Complete(Some(Uniqueness::Unique));
}
}
}
fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
fn solve_grid_guess(
grid: &mut Grid,
solve_controller: &SolveController,
solve_statistics: &mut SolveStatistics,
) -> SolveStatus {
solve_statistics.increment(&SolveAction::Guess);
let smallest_cell = grid.find_smallest_cell();
let smallest_cell = match smallest_cell {
Some(cell) => cell,
None => return SolveStatus::Invalid
None => return SolveStatus::Invalid,
};
let possibilities = smallest_cell.get_value_possibilities().unwrap();
@ -808,16 +908,19 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s
let mut grid_solution = None;
for (_index, &digit) in possibilities.iter().enumerate() {
let mut grid_copy = grid.clone();
grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit);
let status = solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics);
grid_copy
.get(smallest_cell.x, smallest_cell.y)
.unwrap()
.set(digit);
let status =
solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics);
// Keep a copy of grid_copy in case we later mutate grid with it
match status {
SolveStatus::Complete(_) => {
grid_solution = Some(grid_copy);
},
}
_ => {}
}
@ -830,34 +933,37 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s
break; // no point in continuing
}
let uniqueness = uniqueness.expect("We're looking for uniqueness and yet an earlier function didn't make a claim");
let uniqueness = uniqueness.expect(
"We're looking for uniqueness and yet an earlier function didn't make a claim",
);
match uniqueness {
Uniqueness::Unique => {continue;} // gotta keep on checking
Uniqueness::Unique => {
continue;
} // gotta keep on checking
Uniqueness::NotUnique => {
break; // We can stop looking as we already found at least two solutions
}
}
}
SolveStatus::Unfinished => {continue} // Keep looking for a solution
SolveStatus::Invalid => panic!("current_status should not be INVALID at this point")
SolveStatus::Unfinished => continue, // Keep looking for a solution
SolveStatus::Invalid => panic!("current_status should not be INVALID at this point"),
}
}
// We've finished the for-loop
match current_status {
SolveStatus::Complete(_) => {
grid.clone_from(&grid_solution.expect("grid_solution should have value if we found a solution"));
},
grid.clone_from(
&grid_solution.expect("grid_solution should have value if we found a solution"),
);
}
SolveStatus::Unfinished => {
current_status = SolveStatus::Invalid; // We can now say Invalid
},
}
SolveStatus::Invalid => {}
}
return current_status;
}
#[cfg(test)]
@ -873,14 +979,20 @@ mod tests {
grid.get(0, i).unwrap().set(i as u8 + 1);
}
assert_eq!(CellValue::Unknown(vec![9]), grid.get(0, 8).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![9]),
grid.get(0, 8).unwrap().get_value_copy()
);
let line = grid.rows.first().unwrap();
let line = &*(**line).borrow();
search_single_possibility(line);
assert_eq!(CellValue::Fixed(9), grid.get(0, 8).unwrap().get_value_copy());
assert_eq!(
CellValue::Fixed(9),
grid.get(0, 8).unwrap().get_value_copy()
);
}
#[test]
@ -895,14 +1007,20 @@ mod tests {
grid.get(1, 7).unwrap().set(2);
grid.get(1, 8).unwrap().set(3);
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]), grid.get(0, 0).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]),
grid.get(0, 0).unwrap().get_value_copy()
);
let line = grid.rows.first().unwrap();
let line = &*(**line).borrow();
process_possibility_groups::identify_and_process_possibility_groups(line);
assert_eq!(CellValue::Unknown(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![1, 2, 3]),
grid.get(0, 0).unwrap().get_value_copy()
);
}
#[test]
@ -917,19 +1035,22 @@ mod tests {
grid.get(1, 7).unwrap().set(2);
grid.get(1, 8).unwrap().set(3);
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]),
grid.get(2, 0).unwrap().get_value_copy()
);
let line = grid.rows.first().unwrap();
let line = &*(**line).borrow();
search_useful_constraint::search_useful_constraint(&grid, line);
assert_eq!(CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]),
grid.get(2, 0).unwrap().get_value_copy()
);
}
#[test]
fn test_search_useful_constraint_2() {
let grid = Grid::new();
@ -942,15 +1063,29 @@ mod tests {
grid.get(6, 1).unwrap().set(8);
grid.get(8, 2).unwrap().set(7);
grid.get(0, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9]));
grid.get(1, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
grid.get(2, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2]));
grid.get(4, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7]));
grid.get(7, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 9]));
grid.get(8, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9]));
grid.get(0, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9]));
grid.get(1, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
grid.get(2, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 2]));
grid.get(4, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 3, 4, 7]));
grid.get(7, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 2, 3, 9]));
grid.get(8, 1)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9]));
// 5 is wrongly removed here
grid.get(1, 0).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
grid.get(1, 0)
.unwrap()
.set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
println!("{}", grid);
@ -959,8 +1094,10 @@ mod tests {
search_useful_constraint::search_useful_constraint(&grid, line);
assert_eq!(CellValue::Unknown(vec![1, 3, 4, 5, 9]), grid.get(1, 0).unwrap().get_value_copy());
assert_eq!(
CellValue::Unknown(vec![1, 3, 4, 5, 9]),
grid.get(1, 0).unwrap().get_value_copy()
);
}
#[test]
@ -985,10 +1122,13 @@ mod tests {
search_hidden_single(&first_row);
assert_eq!(CellValue::Fixed(1), grid.get(0, 0).unwrap().get_value_copy());
assert_eq!(CellValue::Fixed(2), grid.get(0, 2).unwrap().get_value_copy());
assert_eq!(
CellValue::Fixed(1),
grid.get(0, 0).unwrap().get_value_copy()
);
assert_eq!(
CellValue::Fixed(2),
grid.get(0, 2).unwrap().get_value_copy()
);
}
}