Difficulty settings now look for a minimum number of complicated Solve Actions
This commit is contained in:
parent
fa370ed9a9
commit
b933f4c281
3 changed files with 156 additions and 47 deletions
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
148
src/solver.rs
148
src/solver.rs
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue