Difficulty settings now look for a minimum number of complicated Solve Actions

This commit is contained in:
Joel Therrien 2020-09-24 15:03:38 -07:00
parent fa370ed9a9
commit b933f4c281
3 changed files with 156 additions and 47 deletions

View file

@ -3,7 +3,7 @@ use rand::prelude::*;
use sudoku_solver::grid::{Grid, CellValue}; use sudoku_solver::grid::{Grid, CellValue};
use std::error::Error; use std::error::Error;
use std::io::Write; use std::io::Write;
use sudoku_solver::solver::SolveController; use sudoku_solver::solver::{SolveController, SolveStatistics};
use std::str::FromStr; use std::str::FromStr;
#[derive(Clone)] // Needed for argparse #[derive(Clone)] // Needed for argparse
@ -38,6 +38,18 @@ impl Difficulty {
controller controller
} }
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
match self {
Difficulty::Hard => {
(solve_statistics.guesses > 0) && (solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10)
}
Difficulty::Medium => {
(solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10)
}
Difficulty::Easy => {true} // easy has no minimum
}
}
} }
impl FromStr for Difficulty { // Needed for argparse impl FromStr for Difficulty { // Needed for argparse
@ -92,6 +104,7 @@ fn main() {
ap.parse_args_or_exit(); ap.parse_args_or_exit();
} }
/*
if debug { if debug {
unsafe { unsafe {
sudoku_solver::grid::DEBUG = true; sudoku_solver::grid::DEBUG = true;
@ -99,6 +112,8 @@ fn main() {
sudoku_solver::generator::DEBUG = true; sudoku_solver::generator::DEBUG = true;
} }
} }
*/
if debug { if debug {
println!("Using seed {}", seed); println!("Using seed {}", seed);
@ -111,25 +126,34 @@ fn main() {
let mut num_attempts = 0; let mut num_attempts = 0;
let grid = loop { let (grid, solve_statistics) = loop {
if num_attempts >= max_attempts{ if num_attempts >= max_attempts{
println!("Unable to find a puzzle with only {} hints in {} attempts", max_hints, max_attempts); println!("Unable to find a puzzle with only {} hints in {} attempts", max_hints, max_attempts);
return; return;
} }
let (grid, num_hints) = sudoku_solver::generator::generate_grid(&mut rng, &solve_controller); let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(&mut rng, &solve_controller);
num_attempts = num_attempts + 1; num_attempts = num_attempts + 1;
if num_hints <= max_hints { if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
println!("{}", grid); println!("{}", grid);
println!("Puzzle has {} hints", num_hints); println!("Puzzle has {} hints", num_hints);
if num_attempts > 1 { if num_attempts > 1 {
println!("It took {} attempts to find this puzzle.", num_attempts); println!("It took {} attempts to find this puzzle.", num_attempts);
} }
break grid; break (grid, solve_statistics);
} }
}; };
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{} GUESS actions", solve_statistics.guesses);
}
match filename { match filename {
Some(filename) => { Some(filename) => {
// check if we save to a csv or a pdf // check if we save to a csv or a pdf

View file

@ -1,5 +1,5 @@
use crate::grid::{Cell, Grid, CellValue, Line}; use crate::grid::{Cell, Grid, CellValue, Line};
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller}; use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics};
use std::rc::Rc; use std::rc::Rc;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
@ -108,7 +108,7 @@ impl Line {
} }
} }
pub fn generate_grid(rng: &mut ChaCha8Rng, solve_controller: &SolveController) -> (Grid, i32) { pub fn generate_grid(rng: &mut ChaCha8Rng, 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;
@ -124,6 +124,8 @@ pub fn generate_grid(rng: &mut ChaCha8Rng, solve_controller: &SolveController) -
// Need to randomly reorder non_empty_cells // Need to randomly reorder non_empty_cells
non_empty_cells.shuffle(rng); non_empty_cells.shuffle(rng);
let mut statistics_option = None;
for (_index, cell) in non_empty_cells.iter().enumerate() { for (_index, cell) in non_empty_cells.iter().enumerate() {
let mut grid_clone = grid.clone(); let mut grid_clone = grid.clone();
let cell_clone = grid_clone.get(cell.x, cell.y).unwrap(); let cell_clone = grid_clone.get(cell.x, cell.y).unwrap();
@ -132,7 +134,7 @@ pub fn generate_grid(rng: &mut ChaCha8Rng, solve_controller: &SolveController) -
cell_clone.delete_value(); cell_clone.delete_value();
let status = 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();
@ -147,9 +149,10 @@ pub fn generate_grid(rng: &mut ChaCha8Rng, solve_controller: &SolveController) -
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED"), 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::Invalid => panic!("Removing constraints should not have set the # of solutions to zero")
} }
statistics_option = Some(statistics);
} }
return (grid, num_hints); return (grid, num_hints, statistics_option.unwrap());
} }
@ -179,7 +182,7 @@ fn generate_completed_grid(rng: &mut ChaCha8Rng) -> Grid {
} }
} }
let status = evaluate_grid_with_solve_controller(&grid, &solve_controller); let (status, _statistics) = evaluate_grid_with_solve_controller(&grid, &solve_controller);
match status { match status {
SolveStatus::Complete(uniqueness) => { SolveStatus::Complete(uniqueness) => {
let uniqueness = uniqueness.unwrap(); let uniqueness = uniqueness.unwrap();
@ -213,7 +216,7 @@ fn generate_completed_grid(rng: &mut ChaCha8Rng) -> Grid {
cell.set(*digit); cell.set(*digit);
let status = 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();
@ -247,7 +250,7 @@ fn generate_completed_grid(rng: &mut ChaCha8Rng) -> Grid {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::grid::*; use crate::grid::*;
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus}; use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
use crate::generator::generate_grid; use crate::generator::generate_grid;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use rand_chacha::rand_core::SeedableRng; use rand_chacha::rand_core::SeedableRng;
@ -297,7 +300,7 @@ mod tests {
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());
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique))); assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique)));
@ -316,7 +319,7 @@ mod tests {
}; };
// Note that the puzzle itself doesn't matter // Note that the puzzle itself doesn't matter
let (grid, _num_hints) = generate_grid(&mut ChaCha8Rng::seed_from_u64(123), &solve_controller); let (grid, _num_hints, _statistics) = generate_grid(&mut ChaCha8Rng::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 {

View file

@ -17,6 +17,15 @@ pub enum SolveStatus {
Invalid Invalid
} }
enum SolveAction{
Single,
HiddenSingle,
PossibilityGroup,
UsefulConstraints,
Guess
}
impl SolveStatus { impl SolveStatus {
fn increment(self, additional_status : SolveStatus) -> SolveStatus { fn increment(self, additional_status : SolveStatus) -> SolveStatus {
@ -64,9 +73,7 @@ impl SolveController {
} }
fn search_hidden_singles(&self) -> bool { fn search_hidden_singles(&self) -> bool {
// search_hidden_singles is a special case of find_possibility_groups, so if find_possibility_groups self.search_hidden_singles
// is enabled then it's a waste of resources to keep this on
self.search_hidden_singles && !self.find_possibility_groups
} }
fn find_possibility_groups(&self) -> bool { fn find_possibility_groups(&self) -> bool {
@ -82,6 +89,40 @@ impl SolveController {
} }
} }
/**
Tracks when we relied on a method to make progress. We'll consider 'relied on' to mean that the method make at least
one change to the line it was originally called on, whether that be setting a value or adjusting the possibilities in a cell.
*/
#[derive(Copy, Clone)]
pub struct SolveStatistics {
pub singles: u32,
pub hidden_singles: u32,
pub possibility_groups: u32,
pub useful_constraints: u32,
pub guesses: u32
}
impl SolveStatistics {
pub(crate) fn new() -> SolveStatistics {
SolveStatistics{
singles: 0,
hidden_singles: 0,
possibility_groups: 0,
useful_constraints: 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}
}
}
}
pub fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{ pub fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{
// Find a cell of smallest size (in terms of possibilities) and make a guess // Find a cell of smallest size (in terms of possibilities) and make a guess
@ -154,17 +195,17 @@ mod process_possibility_groups {
// See if there's a set of cells with possibilities that exclude those possibilities from other cells. // See if there's a set of cells with possibilities that exclude those possibilities from other cells.
// Runs recursively on each group to identify all groups in case there's more than 2. // Runs recursively on each group to identify all groups in case there's more than 2.
pub fn identify_and_process_possibility_groups(line: &Line){ pub fn identify_and_process_possibility_groups(line: &Line) -> bool{
unsafe { unsafe {
if super::DEBUG { if super::DEBUG {
println!("Looking for possibility groups on line {:?} {}", line.line_type, line.index); println!("Looking for possibility groups on line {:?} {}", line.line_type, line.index);
} }
} }
bisect_possibility_groups(line, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); bisect_possibility_groups(line, vec![0, 1, 2, 3, 4, 5, 6, 7, 8])
} }
fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){ fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>) -> bool{
/* /*
Algorithm - Algorithm -
@ -180,6 +221,8 @@ mod process_possibility_groups {
let mut out_group_indices = Vec::new(); let mut out_group_indices = Vec::new();
let mut run_recursion = false; let mut run_recursion = false;
let mut made_change = false;
{ {
// <Setup> // <Setup>
let mut count = 0; let mut count = 0;
@ -219,7 +262,7 @@ mod process_possibility_groups {
// No point in continuing. // No point in continuing.
if faux_line.num_out_group() <= 2 { if faux_line.num_out_group() <= 2 {
return; return made_change;
} }
// A kind of do-while loop // A kind of do-while loop
@ -301,6 +344,7 @@ mod process_possibility_groups {
} }
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 = { let new_value = {
if possibilities.len() == 1 { if possibilities.len() == 1 {
CellValue::Fixed(possibilities.pop().unwrap()) CellValue::Fixed(possibilities.pop().unwrap())
@ -331,32 +375,39 @@ mod process_possibility_groups {
bisect_possibility_groups(line, in_group_indices); bisect_possibility_groups(line, in_group_indices);
bisect_possibility_groups(line, out_group_indices); bisect_possibility_groups(line, out_group_indices);
} }
return made_change;
} }
} }
// Search for a cell with only one possibility so that we can set it to FIXED // Search for a cell with only one possibility so that we can set it to FIXED
fn search_single_possibility(line: &Line){ fn search_single_possibility(line: &Line) -> bool{
unsafe { unsafe {
if DEBUG { if DEBUG {
println!("search_single_possibility on line {:?} {}", line.line_type, line.index); println!("search_single_possibility on line {:?} {}", line.line_type, line.index);
} }
} }
let mut made_change = false;
for (_index, cell) in line.vec.iter().enumerate(){ for (_index, cell) in line.vec.iter().enumerate(){
match cell.get_value_possibilities(){ match cell.get_value_possibilities(){
Some(x) => { Some(x) => {
if x.len() == 1 { if x.len() == 1 {
let new_value = CellValue::Fixed(x.first().unwrap().clone()); let new_value = CellValue::Fixed(x.first().unwrap().clone());
cell.set_value(new_value); cell.set_value(new_value);
made_change = true;
} }
}, },
None => {} None => {}
} }
} }
return made_change;
} }
// Count up how many times each possibility occurs in the Line. If it only occurs once, that's a hidden single that we can set // Count up how many times each possibility occurs in the Line. If it only occurs once, that's a hidden single that we can set
fn search_hidden_single(line: &Line){ fn search_hidden_single(line: &Line) -> bool{
enum Count { enum Count {
None, None,
One(Rc<Cell>), One(Rc<Cell>),
@ -373,6 +424,8 @@ fn search_hidden_single(line: &Line){
} }
} }
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() {
@ -393,11 +446,13 @@ fn search_hidden_single(line: &Line){
match count { match count {
Count::One(cell) => { Count::One(cell) => {
cell.set((digit + 1) as u8); cell.set((digit + 1) as u8);
made_change = true;
}, },
_ => {} _ => {}
} }
} }
return made_change;
} }
mod search_useful_constraint{ mod search_useful_constraint{
@ -424,13 +479,15 @@ mod search_useful_constraint{
// I.e. If possibility '1' only occurs in the first row for section 0, then you can remove that possibility // I.e. If possibility '1' only occurs in the first row for section 0, then you can remove that possibility
// from row 0 across the other sections. Conversely, if the possibility only occurs in the first section // from row 0 across the other sections. Conversely, if the possibility only occurs in the first section
// for row 0, then you can remove the possibility from the rest of section 0. // for row 0, then you can remove the possibility from the rest of section 0.
pub fn search_useful_constraint(grid: &Grid, line: &Line){ pub fn search_useful_constraint(grid: &Grid, line: &Line) -> bool{
unsafe { unsafe {
if super::DEBUG { if super::DEBUG {
println!("Searching for a useful constraint on line {:?} {}", line.line_type, line.index); println!("Searching for a useful constraint on line {:?} {}", line.line_type, line.index);
} }
} }
let mut made_change = false;
let (check_row, check_column, check_section) = match line.line_type { let (check_row, check_column, check_section) = match line.line_type {
LineType::Row => {(false, false, true)}, LineType::Row => {(false, false, true)},
LineType::Column => {(false, false, true)}, LineType::Column => {(false, false, true)},
@ -474,29 +531,35 @@ 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) => {
remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.line_type, line.index); made_change = made_change |
remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.line_type, line.index);
}, },
_ => {} _ => {}
} }
match columns { match columns {
PossibilityLines::Unique(index) => { PossibilityLines::Unique(index) => {
remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.line_type, line.index); made_change = made_change |
remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.line_type, line.index);
}, },
_ => {} _ => {}
} }
match sections { match sections {
PossibilityLines::Unique(index) => { PossibilityLines::Unique(index) => {
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.line_type, line.index); made_change = made_change |
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.line_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 // initial_line_type and initial_line_index are to identify the cells that should NOT have their possibilities removed
fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, initial_line_type: &LineType, initial_line_index: usize) { fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, initial_line_type: &LineType, initial_line_index: usize) -> bool {
let line = &*(&**line).borrow(); let line = &*(&**line).borrow();
let mut made_change = false;
for (_index, cell) in line.vec.iter().enumerate() { for (_index, cell) in line.vec.iter().enumerate() {
let new_value = { let new_value = {
@ -538,8 +601,11 @@ mod search_useful_constraint{
}; };
cell.set_value(new_value); cell.set_value(new_value);
made_change = true;
} }
return made_change;
} }
// We detected a useful constraint // We detected a useful constraint
@ -563,7 +629,7 @@ mod search_useful_constraint{
} }
fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){
unsafe { unsafe {
if DEBUG { if DEBUG {
println!("Solving {:?} {}", line.line_type, line.index); println!("Solving {:?} {}", line.line_type, line.index);
@ -578,7 +644,9 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){
println!("Searching for singles on line {:?} of {}\n{}", line.line_type, line.index, grid); println!("Searching for singles on line {:?} of {}\n{}", line.line_type, line.index, grid);
} }
} }
search_single_possibility(line); if search_single_possibility(line) {
solve_statistics.increment(&SolveAction::Single);
}
} }
if solve_controller.search_hidden_singles() { if solve_controller.search_hidden_singles() {
@ -587,7 +655,9 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){
println!("Searching for hidden singles on line {:?} of {}\n{}", line.line_type, line.index, grid); println!("Searching for hidden singles on line {:?} of {}\n{}", line.line_type, line.index, grid);
} }
} }
search_hidden_single(line); if search_hidden_single(line) {
solve_statistics.increment(&SolveAction::HiddenSingle);
}
} }
if solve_controller.find_possibility_groups() { if solve_controller.find_possibility_groups() {
@ -596,7 +666,9 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){
println!("Searching for possibility groups on line {:?} of {}\n{}", line.line_type, line.index, grid); println!("Searching for possibility groups on line {:?} of {}\n{}", line.line_type, line.index, grid);
} }
} }
process_possibility_groups::identify_and_process_possibility_groups(line); if process_possibility_groups::identify_and_process_possibility_groups(line) {
solve_statistics.increment(&SolveAction::PossibilityGroup);
}
} }
if solve_controller.search_useful_constraint() { if solve_controller.search_useful_constraint() {
@ -605,12 +677,14 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController){
println!("Searching for useful constraints on line {:?} of {}\n{}", line.line_type, line.index, grid); println!("Searching for useful constraints on line {:?} of {}\n{}", line.line_type, line.index, grid);
} }
} }
search_useful_constraint::search_useful_constraint(grid, line); if search_useful_constraint::search_useful_constraint(grid, line) {
solve_statistics.increment(&SolveAction::UsefulConstraints);
}
} }
} }
pub fn solve_grid(grid: &mut Grid) -> SolveStatus { pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) {
// By default we enable everything // By default we enable everything
let solve_controller = SolveController { let solve_controller = SolveController {
determine_uniqueness: true, determine_uniqueness: true,
@ -621,11 +695,13 @@ pub fn solve_grid(grid: &mut Grid) -> SolveStatus {
make_guesses: true make_guesses: true
}; };
solve_grid_with_solve_controller(grid, &solve_controller) let mut solve_statistics = SolveStatistics::new();
let solve_status = solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics);
return (solve_status, solve_statistics);
} }
pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &SolveController) -> 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
@ -633,11 +709,11 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv
// solve_grid_no_guess tries to solve without any guesses. // solve_grid_no_guess tries to solve without any guesses.
// Of course this is if the solve_controller lets everything be used for solving it // Of course this is if the solve_controller lets everything be used for solving it
let mut status = solve_grid_no_guess(grid, solve_controller); let mut status = solve_grid_no_guess(grid, solve_controller, solve_statistics);
status = match status { status = match status {
SolveStatus::Unfinished => { SolveStatus::Unfinished => {
if solve_controller.make_guesses() { if solve_controller.make_guesses() {
solve_grid_guess(grid, solve_controller) solve_grid_guess(grid, solve_controller, solve_statistics)
} 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
} }
@ -649,12 +725,16 @@ 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 // Similar to solve_grid_with_solve_controller except that we don't modify the input Grid; we only determine SolveStatus
pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &SolveController) -> SolveStatus{ 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();
return solve_grid_with_solve_controller(&mut mut_grid, solve_controller); let mut solve_statistics = SolveStatistics::new();
let solve_status = solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics);
return (solve_status, solve_statistics);
} }
pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController) -> SolveStatus{ pub 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;
@ -662,7 +742,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController)
//println!("Processing row {}", _index); //println!("Processing row {}", _index);
let line_ref = &*(&**line_ref).borrow(); let line_ref = &*(&**line_ref).borrow();
if line_ref.do_update() { if line_ref.do_update() {
solve_line(&grid, line_ref, solve_controller); solve_line(&grid, line_ref, solve_controller, solve_statistics);
ran_something = true; ran_something = true;
} }
} }
@ -670,7 +750,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController)
//println!("Processing column {}", _index); //println!("Processing column {}", _index);
let line_ref = &*(&**line_ref).borrow(); let line_ref = &*(&**line_ref).borrow();
if line_ref.do_update() { if line_ref.do_update() {
solve_line(&grid, line_ref, solve_controller); solve_line(&grid, line_ref, solve_controller, solve_statistics);
ran_something = true; ran_something = true;
} }
} }
@ -678,7 +758,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController)
//println!("Processing section {}", _index); //println!("Processing section {}", _index);
let line_ref = &*(&**line_ref).borrow(); let line_ref = &*(&**line_ref).borrow();
if line_ref.do_update() { if line_ref.do_update() {
solve_line(&grid, line_ref, solve_controller); solve_line(&grid, line_ref, solve_controller, solve_statistics);
ran_something = true; ran_something = true;
} }
} }
@ -717,7 +797,9 @@ pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController)
} }
fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController) -> SolveStatus{ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{
solve_statistics.increment(&SolveAction::Guess);
let smallest_cell = find_smallest_cell(grid); let smallest_cell = find_smallest_cell(grid);
let smallest_cell = match smallest_cell { let smallest_cell = match smallest_cell {
Some(cell) => cell, Some(cell) => cell,
@ -733,7 +815,7 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController) -> Solv
let mut grid_copy = grid.clone(); let mut grid_copy = grid.clone();
grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit); grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit);
let status = solve_grid_with_solve_controller(&mut grid_copy, solve_controller); 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 {