Add Generator #1
7 changed files with 342 additions and 86 deletions
|
@ -9,3 +9,5 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
csv = "1.1.3"
|
csv = "1.1.3"
|
||||||
argparse = "0.2.2"
|
argparse = "0.2.2"
|
||||||
|
rand = "0.7"
|
||||||
|
rand_chacha = "0.2.2"
|
35
src/bin/generator.rs
Normal file
35
src/bin/generator.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
|
||||||
|
let mut debug = false;
|
||||||
|
// Starting default seed will just be based on time
|
||||||
|
let mut seed = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("Time went backwards").as_secs();
|
||||||
|
|
||||||
|
{ // 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 seed)
|
||||||
|
.add_option(&["--seed"], argparse::Store, "Provide seed for puzzle generation");
|
||||||
|
|
||||||
|
ap.parse_args_or_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
unsafe {
|
||||||
|
sudoku_solver::grid::DEBUG = true;
|
||||||
|
sudoku_solver::solver::DEBUG = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println!("Using seed {}", seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (grid, num_hints) = sudoku_solver::generator::generate_grid(seed);
|
||||||
|
|
||||||
|
println!("{}", grid);
|
||||||
|
println!("Puzzle has {} hints", num_hints);
|
||||||
|
}
|
219
src/generator.rs
Normal file
219
src/generator.rs
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
use crate::grid::{Cell, Grid, CellValue};
|
||||||
|
use crate::solver::{solve_grid_no_guess, SolveStatus, find_smallest_cell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
|
// Extension of SolveStatus
|
||||||
|
pub enum GenerateStatus {
|
||||||
|
UniqueSolution,
|
||||||
|
Unfinished,
|
||||||
|
NoSolution,
|
||||||
|
NotUniqueSolution
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateStatus {
|
||||||
|
fn increment(self, new_status : GenerateStatus) -> GenerateStatus {
|
||||||
|
match self {
|
||||||
|
GenerateStatus::UniqueSolution => {
|
||||||
|
match new_status {
|
||||||
|
GenerateStatus::UniqueSolution => GenerateStatus::NotUniqueSolution, // We now have two completes, so the solutions are not unique
|
||||||
|
GenerateStatus::NoSolution => GenerateStatus::UniqueSolution, // We already have a complete, so no issue with another guess being invalid
|
||||||
|
GenerateStatus::Unfinished => {panic!("Should not have encountered an UNFINISHED status")},
|
||||||
|
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution // That solver found multiple solutions so no need to keep checking
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GenerateStatus::Unfinished => {
|
||||||
|
match new_status {
|
||||||
|
GenerateStatus::UniqueSolution => GenerateStatus::UniqueSolution,
|
||||||
|
GenerateStatus::NoSolution => GenerateStatus::Unfinished,
|
||||||
|
GenerateStatus::Unfinished => {panic!("Should not have encountered an UNFINISHED status")},
|
||||||
|
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution // That solver found multiple solutions so no need to keep checking
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GenerateStatus::NotUniqueSolution => GenerateStatus::NotUniqueSolution,
|
||||||
|
GenerateStatus::NoSolution => GenerateStatus::NoSolution // This guess didn't pan out
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolveStatus {
|
||||||
|
fn map_to_generate_status(self) -> GenerateStatus {
|
||||||
|
match self {
|
||||||
|
SolveStatus::Complete => {GenerateStatus::UniqueSolution }
|
||||||
|
SolveStatus::Unfinished => {GenerateStatus::Unfinished }
|
||||||
|
SolveStatus::Invalid => {GenerateStatus::NoSolution }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
fn get_random_empty_cell(&self, rng : &mut ChaCha8Rng) -> Result<Rc<Cell>, &str> {
|
||||||
|
// Idea - put all empty cells into a vector and choose one at random
|
||||||
|
// If vector is empty we return an error
|
||||||
|
|
||||||
|
let mut empty_cells = Vec::new();
|
||||||
|
for x in 0..9 {
|
||||||
|
for y in 0..9 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if add_cell {
|
||||||
|
empty_cells.push(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match empty_cells.iter().choose(rng) {
|
||||||
|
Some(cell) => Ok(Rc::clone(cell)),
|
||||||
|
None => Err("Unable to find an empty cell")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_grid(seed: u64) -> (Grid, i32) {
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
|
|
||||||
|
let digit_excluded = rng.gen_range(1, 10);
|
||||||
|
|
||||||
|
let mut num_hints = 0;
|
||||||
|
let mut grid : Grid = loop {
|
||||||
|
// First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution
|
||||||
|
// We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle
|
||||||
|
// We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it
|
||||||
|
let mut grid = Grid::new();
|
||||||
|
|
||||||
|
for digit in 1..10 {
|
||||||
|
if digit != digit_excluded {
|
||||||
|
let cell = grid.get_random_empty_cell(&mut rng);
|
||||||
|
cell.unwrap().set(digit);
|
||||||
|
num_hints = num_hints + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = solve_grid(&mut grid);
|
||||||
|
match status {
|
||||||
|
GenerateStatus::UniqueSolution => { // very surprising result, given that the smallest puzzles found have 14 guesses
|
||||||
|
eprintln!("Wow! A puzzle with only 8 guesses have been found");
|
||||||
|
return (grid, num_hints);
|
||||||
|
}
|
||||||
|
GenerateStatus::Unfinished => {panic!("solve_grid should never return UNFINISHED")}
|
||||||
|
GenerateStatus::NoSolution => {continue;}
|
||||||
|
GenerateStatus::NotUniqueSolution => {break grid;}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
||||||
|
'outer: loop {
|
||||||
|
num_hints = num_hints + 1;
|
||||||
|
let cell = grid.get_random_empty_cell(&mut rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
|
||||||
|
let cell = &*cell;
|
||||||
|
let cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities");
|
||||||
|
|
||||||
|
// Let's scramble the order
|
||||||
|
let cell_possibilities = cell_possibilities.iter().choose_multiple(&mut rng, cell_possibilities.len());
|
||||||
|
|
||||||
|
for (_index, digit) in cell_possibilities.iter().enumerate() {
|
||||||
|
if **digit == digit_excluded {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let grid_clone = grid.clone();
|
||||||
|
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
||||||
|
|
||||||
|
cell.set(**digit);
|
||||||
|
|
||||||
|
let status = solve_grid(&grid_clone);
|
||||||
|
match status {
|
||||||
|
GenerateStatus::UniqueSolution => { // We're done!
|
||||||
|
return (grid_clone, num_hints);
|
||||||
|
}
|
||||||
|
GenerateStatus::Unfinished => {
|
||||||
|
panic!("solve_grid should never return UNFINISHED")
|
||||||
|
}
|
||||||
|
GenerateStatus::NoSolution => { // Try another guess
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GenerateStatus::NotUniqueSolution => { // We need more guesses
|
||||||
|
grid = grid_clone;
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
//panic!("Unable to continue as puzzle is invalid");
|
||||||
|
num_hints = num_hints - 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_grid(grid: &Grid) -> GenerateStatus{
|
||||||
|
// 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
|
||||||
|
// through calling this function.
|
||||||
|
// solve_grid_no_guess tries to solve without any guesses.
|
||||||
|
|
||||||
|
let mut grid = grid.clone(); // We're generating a result and don't want to make changes to our input
|
||||||
|
|
||||||
|
let mut status = solve_grid_no_guess(&mut grid).map_to_generate_status();
|
||||||
|
status = match status {
|
||||||
|
GenerateStatus::Unfinished => {
|
||||||
|
solve_grid_guess(&mut grid)
|
||||||
|
},
|
||||||
|
_ => {status}
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
GenerateStatus::Unfinished => panic!("solve_grid_guess should never return UNFINISHED"),
|
||||||
|
_ => return status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_grid_guess(grid: &Grid) -> GenerateStatus{
|
||||||
|
let smallest_cell = find_smallest_cell(grid);
|
||||||
|
let smallest_cell = match smallest_cell {
|
||||||
|
Some(cell) => cell,
|
||||||
|
None => return GenerateStatus::NoSolution
|
||||||
|
};
|
||||||
|
|
||||||
|
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
||||||
|
|
||||||
|
let mut current_status = GenerateStatus::Unfinished;
|
||||||
|
|
||||||
|
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(&mut grid_copy);
|
||||||
|
current_status = current_status.increment(status);
|
||||||
|
|
||||||
|
match current_status {
|
||||||
|
GenerateStatus::NotUniqueSolution => return GenerateStatus::NotUniqueSolution, // We have our answer; return it
|
||||||
|
GenerateStatus::UniqueSolution => {continue}, // Still looking to see if solution is unique
|
||||||
|
GenerateStatus::NoSolution => {panic!("current_status should not be NO_SOLUTION at this point")},
|
||||||
|
GenerateStatus::Unfinished => {continue} // Still looking for a solution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've tried all the possibilities for this guess
|
||||||
|
match current_status {
|
||||||
|
GenerateStatus::NotUniqueSolution => return current_status,
|
||||||
|
GenerateStatus::Unfinished => return GenerateStatus::NoSolution, // nothing panned out; last guess is a bust
|
||||||
|
GenerateStatus::UniqueSolution => return current_status, // Hey! Looks good!
|
||||||
|
GenerateStatus::NoSolution => {panic!("current_status should not be NO_SOLUTION at this point")}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
src/grid.rs
38
src/grid.rs
|
@ -6,8 +6,8 @@ pub static mut DEBUG: bool = false;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum CellValue {
|
pub enum CellValue {
|
||||||
FIXED(u8),
|
Fixed(u8),
|
||||||
UNKNOWN(Vec<u8>)
|
Unknown(Vec<u8>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
|
@ -27,7 +27,7 @@ impl Cell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.value.replace(CellValue::FIXED(digit));
|
self.value.replace(CellValue::Fixed(digit));
|
||||||
|
|
||||||
// We fully expect our row, column, and section to still be here even though the Rust compiler won't guarantee it
|
// We fully expect our row, column, and section to still be here even though the Rust compiler won't guarantee it
|
||||||
// Panic-ing if they're not present is perfectly reasonable
|
// Panic-ing if they're not present is perfectly reasonable
|
||||||
|
@ -53,11 +53,11 @@ impl Cell {
|
||||||
|
|
||||||
pub fn set_value(&self, value: CellValue){
|
pub fn set_value(&self, value: CellValue){
|
||||||
match value {
|
match value {
|
||||||
CellValue::FIXED(digit) => {
|
CellValue::Fixed(digit) => {
|
||||||
self.set(digit);
|
self.set(digit);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
CellValue::UNKNOWN(_) => {
|
CellValue::Unknown(_) => {
|
||||||
self.set_value_exact(value);
|
self.set_value_exact(value);
|
||||||
} // continue on
|
} // continue on
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ impl Cell {
|
||||||
pub fn get_value_possibilities(&self) -> Option<Vec<u8>> {
|
pub fn get_value_possibilities(&self) -> Option<Vec<u8>> {
|
||||||
let value = &*self.value.borrow();
|
let value = &*self.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
CellValue::FIXED(_) => None,
|
CellValue::Fixed(_) => None,
|
||||||
CellValue::UNKNOWN(x) => Some(x.clone())
|
CellValue::Unknown(x) => Some(x.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ impl Cell {
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
CellValue::UNKNOWN(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
let mut new_possibilities = possibilities.clone();
|
let mut new_possibilities = possibilities.clone();
|
||||||
|
|
||||||
match new_possibilities.binary_search(&digit) {
|
match new_possibilities.binary_search(&digit) {
|
||||||
|
@ -117,7 +117,7 @@ impl Cell {
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(CellValue::UNKNOWN(new_possibilities))
|
Some(CellValue::Unknown(new_possibilities))
|
||||||
/*
|
/*
|
||||||
if new_possibilities.len() == 1 {
|
if new_possibilities.len() == 1 {
|
||||||
let remaining_digit = new_possibilities.first().unwrap().clone();
|
let remaining_digit = new_possibilities.first().unwrap().clone();
|
||||||
|
@ -128,7 +128,7 @@ impl Cell {
|
||||||
Some(CellValue::UNKNOWN(new_possibilities))
|
Some(CellValue::UNKNOWN(new_possibilities))
|
||||||
}*/
|
}*/
|
||||||
},
|
},
|
||||||
CellValue::FIXED(_) => {None}
|
CellValue::Fixed(_) => {None}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,9 +152,9 @@ pub struct Line {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LineType {
|
pub enum LineType {
|
||||||
ROW,
|
Row,
|
||||||
COLUMN,
|
Column,
|
||||||
SECTION
|
Section
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
|
@ -199,9 +199,9 @@ impl Grid {
|
||||||
let mut sections: Vec<MultiMut<Line>> = Vec::new();
|
let mut sections: Vec<MultiMut<Line>> = Vec::new();
|
||||||
|
|
||||||
for i in 0..9 {
|
for i in 0..9 {
|
||||||
rows.push(Rc::new(RefCell::new(Line::new(i, LineType::ROW))));
|
rows.push(Rc::new(RefCell::new(Line::new(i, LineType::Row))));
|
||||||
columns.push(Rc::new(RefCell::new(Line::new(i, LineType::COLUMN))));
|
columns.push(Rc::new(RefCell::new(Line::new(i, LineType::Column))));
|
||||||
sections.push(Rc::new(RefCell::new(Line::new(i, LineType::SECTION))));
|
sections.push(Rc::new(RefCell::new(Line::new(i, LineType::Section))));
|
||||||
}
|
}
|
||||||
|
|
||||||
for row_index in 0..9 {
|
for row_index in 0..9 {
|
||||||
|
@ -229,7 +229,7 @@ impl Grid {
|
||||||
let cell = Cell {
|
let cell = Cell {
|
||||||
x: row_index,
|
x: row_index,
|
||||||
y: column_index,
|
y: column_index,
|
||||||
value: RefCell::new(CellValue::UNKNOWN(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
|
value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
|
||||||
row: row_weak,
|
row: row_weak,
|
||||||
column: column_weak,
|
column: column_weak,
|
||||||
section: section_weak
|
section: section_weak
|
||||||
|
@ -325,12 +325,12 @@ impl std::fmt::Display for Grid {
|
||||||
|
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
CellValue::FIXED(x) => {
|
CellValue::Fixed(x) => {
|
||||||
row1.push_str(" ");
|
row1.push_str(" ");
|
||||||
row2.push(' '); row2.push_str(&x.to_string()); row2.push(' ');
|
row2.push(' '); row2.push_str(&x.to_string()); row2.push(' ');
|
||||||
row3.push_str(" ");
|
row3.push_str(" ");
|
||||||
},
|
},
|
||||||
CellValue::UNKNOWN(x) => {
|
CellValue::Unknown(x) => {
|
||||||
Grid::process_unknown(&x, 1, &mut row1);
|
Grid::process_unknown(&x, 1, &mut row1);
|
||||||
Grid::process_unknown(&x, 2, &mut row1);
|
Grid::process_unknown(&x, 2, &mut row1);
|
||||||
Grid::process_unknown(&x, 3, &mut row1);
|
Grid::process_unknown(&x, 3, &mut row1);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
|
||||||
pub mod solver;
|
pub mod solver;
|
||||||
|
pub mod generator;
|
128
src/solver.rs
128
src/solver.rs
|
@ -80,14 +80,14 @@ fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
||||||
let faux_possibilities = {
|
let faux_possibilities = {
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
CellValue::UNKNOWN(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for (_index, digit) in possibilities.iter().enumerate() {
|
for (_index, digit) in possibilities.iter().enumerate() {
|
||||||
set.insert(digit.clone());
|
set.insert(digit.clone());
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
},
|
},
|
||||||
CellValue::FIXED(_) => { continue }
|
CellValue::Fixed(_) => { continue }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,8 +164,8 @@ fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
||||||
let mut possibilities = {
|
let mut possibilities = {
|
||||||
let value = &*real_cell.value.borrow();
|
let value = &*real_cell.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
CellValue::UNKNOWN(possibilities) => possibilities.clone(),
|
CellValue::Unknown(possibilities) => possibilities.clone(),
|
||||||
CellValue::FIXED(_) => {panic!("Faux_cell shouldn't have linked to fixed cell")}
|
CellValue::Fixed(_) => {panic!("Faux_cell shouldn't have linked to fixed cell")}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let starting_possibility_size = possibilities.len();
|
let starting_possibility_size = possibilities.len();
|
||||||
|
@ -187,9 +187,9 @@ fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec<usize>){
|
||||||
if possibilities.len() < starting_possibility_size { // We have a change to make
|
if possibilities.len() < starting_possibility_size { // We have a change to make
|
||||||
let new_value = {
|
let new_value = {
|
||||||
if possibilities.len() == 1 {
|
if possibilities.len() == 1 {
|
||||||
CellValue::FIXED(possibilities.pop().unwrap())
|
CellValue::Fixed(possibilities.pop().unwrap())
|
||||||
} else {
|
} else {
|
||||||
CellValue::UNKNOWN(possibilities)
|
CellValue::Unknown(possibilities)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ fn search_single_possibility(line: &Line){
|
||||||
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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -239,15 +239,15 @@ fn search_single_possibility(line: &Line){
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PossibilityLines {
|
enum PossibilityLines {
|
||||||
UNIQUE(usize),
|
Unique(usize),
|
||||||
INVALID,
|
Invalid,
|
||||||
NONE
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PossibilityLines {
|
impl PossibilityLines {
|
||||||
fn is_invalid(&self) -> bool {
|
fn is_invalid(&self) -> bool {
|
||||||
match &self {
|
match &self {
|
||||||
PossibilityLines::INVALID => true,
|
PossibilityLines::Invalid => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,15 +265,15 @@ fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
}
|
}
|
||||||
|
|
||||||
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)},
|
||||||
LineType::SECTION => {(true, true, false)},
|
LineType::Section => {(true, true, false)},
|
||||||
};
|
};
|
||||||
|
|
||||||
for possibility in 0..9 {
|
for possibility in 0..9 {
|
||||||
let mut rows = match check_row {true => PossibilityLines::NONE, false => PossibilityLines::INVALID};
|
let mut rows = match check_row {true => PossibilityLines::None, false => PossibilityLines::Invalid };
|
||||||
let mut columns = match check_column {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 sections = match check_section {true => PossibilityLines::None, false => PossibilityLines::Invalid };
|
||||||
|
|
||||||
for cell_id in 0..9 {
|
for cell_id in 0..9 {
|
||||||
let cell_ref = line.get(cell_id).unwrap();
|
let cell_ref = line.get(cell_id).unwrap();
|
||||||
|
@ -282,7 +282,7 @@ fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
let value = &*cell_ref.value.borrow();
|
let value = &*cell_ref.value.borrow();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
CellValue::FIXED(x) => { // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
|
CellValue::Fixed(x) => { // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop
|
||||||
if possibility.eq(x) {
|
if possibility.eq(x) {
|
||||||
rows = process_possibility_line(rows, &cell_ref.row);
|
rows = process_possibility_line(rows, &cell_ref.row);
|
||||||
columns = process_possibility_line(columns, &cell_ref.column);
|
columns = process_possibility_line(columns, &cell_ref.column);
|
||||||
|
@ -290,7 +290,7 @@ fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CellValue::UNKNOWN(digits) => {
|
CellValue::Unknown(digits) => {
|
||||||
if digits.contains(&possibility) {
|
if digits.contains(&possibility) {
|
||||||
rows = process_possibility_line(rows, &cell_ref.row);
|
rows = process_possibility_line(rows, &cell_ref.row);
|
||||||
columns = process_possibility_line(columns, &cell_ref.column);
|
columns = process_possibility_line(columns, &cell_ref.column);
|
||||||
|
@ -306,19 +306,19 @@ fn search_useful_constraint(grid: &Grid, line: &Line){
|
||||||
|
|
||||||
// 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);
|
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);
|
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);
|
remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.line_type, line.index);
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -335,11 +335,11 @@ fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, init
|
||||||
let new_value = {
|
let new_value = {
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
CellValue::UNKNOWN(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
let parent_line = match initial_line_type {
|
let parent_line = match initial_line_type {
|
||||||
LineType::ROW => &cell.row,
|
LineType::Row => &cell.row,
|
||||||
LineType::COLUMN => &cell.column,
|
LineType::Column => &cell.column,
|
||||||
LineType::SECTION => &cell.section
|
LineType::Section => &cell.section
|
||||||
};
|
};
|
||||||
let parent_line = &*parent_line.upgrade().unwrap();
|
let parent_line = &*parent_line.upgrade().unwrap();
|
||||||
let parent_line = &*parent_line.borrow();
|
let parent_line = &*parent_line.borrow();
|
||||||
|
@ -359,9 +359,9 @@ fn remove_possibilities_line(line: &Rc<RefCell<Line>>, digit_to_remove: u8, init
|
||||||
|
|
||||||
let new_value;
|
let new_value;
|
||||||
if new_possibilities.len() == 1 {
|
if new_possibilities.len() == 1 {
|
||||||
new_value = CellValue::FIXED(new_possibilities.first().unwrap().clone());
|
new_value = CellValue::Fixed(new_possibilities.first().unwrap().clone());
|
||||||
} else {
|
} else {
|
||||||
new_value = CellValue::UNKNOWN(new_possibilities);
|
new_value = CellValue::Unknown(new_possibilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_value
|
new_value
|
||||||
|
@ -381,13 +381,13 @@ fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak<RefC
|
||||||
let line = &*(&*line).borrow();
|
let line = &*(&*line).borrow();
|
||||||
|
|
||||||
match possibility_line {
|
match possibility_line {
|
||||||
PossibilityLines::NONE => {PossibilityLines::UNIQUE(line.index)},
|
PossibilityLines::None => {PossibilityLines::Unique(line.index)},
|
||||||
PossibilityLines::INVALID => {possibility_line},
|
PossibilityLines::Invalid => {possibility_line},
|
||||||
PossibilityLines::UNIQUE(x) => {
|
PossibilityLines::Unique(x) => {
|
||||||
if line.index.eq(&x) {
|
if line.index.eq(&x) {
|
||||||
possibility_line
|
possibility_line
|
||||||
} else {
|
} else {
|
||||||
PossibilityLines::INVALID
|
PossibilityLines::Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,7 +426,7 @@ fn solve_line(grid: &Grid, line: &Line){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
// Can assume that no cells of only possibility 1 exist
|
// Can assume that no cells of only possibility 1 exist
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{
|
||||||
let cell_value = &*cell.value.borrow();
|
let cell_value = &*cell.value.borrow();
|
||||||
|
|
||||||
match cell_value {
|
match cell_value {
|
||||||
CellValue::UNKNOWN(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
if (possibilities.len() < smallest_size) && (possibilities.len() > 0){
|
if (possibilities.len() < smallest_size) && (possibilities.len() > 0){
|
||||||
smallest_size = possibilities.len();
|
smallest_size = possibilities.len();
|
||||||
smallest_cell = Some(cell_rc);
|
smallest_cell = Some(cell_rc);
|
||||||
|
@ -461,9 +461,9 @@ fn find_smallest_cell(grid: &Grid) -> Option<Rc<Cell>>{
|
||||||
|
|
||||||
|
|
||||||
pub enum SolveStatus {
|
pub enum SolveStatus {
|
||||||
COMPLETE,
|
Complete,
|
||||||
UNFINISHED,
|
Unfinished,
|
||||||
INVALID
|
Invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn solve_grid(grid: &mut Grid) -> SolveStatus{
|
pub fn solve_grid(grid: &mut Grid) -> SolveStatus{
|
||||||
|
@ -475,14 +475,14 @@ pub fn solve_grid(grid: &mut Grid) -> SolveStatus{
|
||||||
|
|
||||||
let mut status = solve_grid_no_guess(grid);
|
let mut status = solve_grid_no_guess(grid);
|
||||||
status = match status {
|
status = match status {
|
||||||
SolveStatus::UNFINISHED => {
|
SolveStatus::Unfinished => {
|
||||||
solve_grid_guess(grid)
|
solve_grid_guess(grid)
|
||||||
},
|
},
|
||||||
_ => {status}
|
_ => {status}
|
||||||
};
|
};
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
SolveStatus::UNFINISHED => panic!("solve_grid_guess should never return UNFINISHED"),
|
SolveStatus::Unfinished => panic!("solve_grid_guess should never return UNFINISHED"),
|
||||||
_ => return status
|
_ => return status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -517,7 +517,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ran_something{ // No lines have changed since we last analyzed them
|
if !ran_something{ // No lines have changed since we last analyzed them
|
||||||
return SolveStatus::UNFINISHED;
|
return SolveStatus::Unfinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if complete or invalid
|
// Check if complete or invalid
|
||||||
|
@ -529,20 +529,20 @@ pub fn solve_grid_no_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
let value = &**(&cell.value.borrow());
|
let value = &**(&cell.value.borrow());
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
CellValue::UNKNOWN(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
appears_complete = false;
|
appears_complete = false;
|
||||||
if possibilities.len() == 0 {
|
if possibilities.len() == 0 {
|
||||||
return SolveStatus::INVALID;
|
return SolveStatus::Invalid;
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CellValue::FIXED(_) => {}
|
CellValue::Fixed(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if appears_complete {
|
if appears_complete {
|
||||||
return SolveStatus::COMPLETE;
|
return SolveStatus::Complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,7 +552,7 @@ fn solve_grid_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
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,
|
||||||
None => return SolveStatus::INVALID
|
None => return SolveStatus::Invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
let possibilities = smallest_cell.get_value_possibilities().unwrap();
|
||||||
|
@ -562,20 +562,20 @@ fn solve_grid_guess(grid: &mut Grid) -> SolveStatus{
|
||||||
let status = solve_grid(&mut grid_copy);
|
let status = solve_grid(&mut grid_copy);
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
SolveStatus::COMPLETE => {
|
SolveStatus::Complete => {
|
||||||
grid.clone_from(&grid_copy);
|
grid.clone_from(&grid_copy);
|
||||||
return SolveStatus::COMPLETE;
|
return SolveStatus::Complete;
|
||||||
},
|
},
|
||||||
SolveStatus::UNFINISHED => {
|
SolveStatus::Unfinished => {
|
||||||
panic!("solve_grid should never return UNFINISHED")
|
panic!("solve_grid should never return UNFINISHED")
|
||||||
},
|
},
|
||||||
SolveStatus::INVALID => {
|
SolveStatus::Invalid => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SolveStatus::INVALID;
|
return SolveStatus::Invalid;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,14 +592,14 @@ mod tests {
|
||||||
grid.get(0, i).unwrap().set(i as u8 +1);
|
grid.get(0, i).unwrap().set(i as u8 +1);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(CellValue::UNKNOWN(vec![9]), grid.get(0, 8).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![9]), grid.get(0, 8).unwrap().get_value_copy());
|
||||||
|
|
||||||
let line = grid.rows.first().unwrap();
|
let line = grid.rows.first().unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
search_single_possibility(line);
|
search_single_possibility(line);
|
||||||
|
|
||||||
assert_eq!(CellValue::FIXED(9), grid.get(0, 8).unwrap().get_value_copy());
|
assert_eq!(CellValue::Fixed(9), grid.get(0, 8).unwrap().get_value_copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -614,14 +614,14 @@ mod tests {
|
||||||
grid.get(1, 7).unwrap().set(2);
|
grid.get(1, 7).unwrap().set(2);
|
||||||
grid.get(1, 8).unwrap().set(3);
|
grid.get(1, 8).unwrap().set(3);
|
||||||
|
|
||||||
assert_eq!(CellValue::UNKNOWN(vec![1, 2, 3, 7, 8, 9]), grid.get(0, 0).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]), grid.get(0, 0).unwrap().get_value_copy());
|
||||||
|
|
||||||
let line = grid.rows.first().unwrap();
|
let line = grid.rows.first().unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
identify_and_process_possibility_groups(line);
|
identify_and_process_possibility_groups(line);
|
||||||
|
|
||||||
assert_eq!(CellValue::UNKNOWN(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy());
|
assert_eq!(CellValue::Unknown(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -638,14 +638,14 @@ mod tests {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 = grid.rows.first().unwrap();
|
||||||
let line = &*(**line).borrow();
|
let line = &*(**line).borrow();
|
||||||
|
|
||||||
search_useful_constraint(&grid, line);
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -661,15 +661,15 @@ mod tests {
|
||||||
grid.get(6, 1).unwrap().set(8);
|
grid.get(6, 1).unwrap().set(8);
|
||||||
grid.get(8, 2).unwrap().set(7);
|
grid.get(8, 2).unwrap().set(7);
|
||||||
|
|
||||||
grid.get(0, 1).unwrap().set_value(CellValue::UNKNOWN(vec![1, 3, 4, 7, 9]));
|
grid.get(0, 1).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(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(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(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(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(8, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9]));
|
||||||
|
|
||||||
// 5 is wrongly removed here
|
// 5 is wrongly removed here
|
||||||
grid.get(1, 0).unwrap().set_value(CellValue::UNKNOWN(vec![1, 3, 4, 5, 9]));
|
grid.get(1, 0).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9]));
|
||||||
|
|
||||||
println!("{}", grid);
|
println!("{}", grid);
|
||||||
|
|
||||||
|
@ -678,7 +678,7 @@ mod tests {
|
||||||
|
|
||||||
search_useful_constraint(&grid, line);
|
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());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue