From 3c845aec3f46b4512665f1b52d81dacf78d7a93d Mon Sep 17 00:00:00 2001 From: Joel Therrien Date: Tue, 22 Sep 2020 16:38:38 -0700 Subject: [PATCH] Slightly improve Sudoku puzzle generator --- src/bin/generator.rs | 1 + src/generator.rs | 140 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/bin/generator.rs b/src/bin/generator.rs index 0d988b5..7a92715 100644 --- a/src/bin/generator.rs +++ b/src/bin/generator.rs @@ -21,6 +21,7 @@ fn main() { unsafe { sudoku_solver::grid::DEBUG = true; sudoku_solver::solver::DEBUG = true; + sudoku_solver::generator::DEBUG = true; } } diff --git a/src/generator.rs b/src/generator.rs index 2b0e10e..bbe307f 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,9 +1,11 @@ -use crate::grid::{Cell, Grid, CellValue}; +use crate::grid::{Cell, Grid, CellValue, Line}; use crate::solver::{solve_grid_no_guess, SolveStatus, find_smallest_cell}; use std::rc::Rc; use rand::prelude::*; use rand_chacha::ChaCha8Rng; +pub static mut DEBUG : bool = false; + // Extension of SolveStatus pub enum GenerateStatus { UniqueSolution, @@ -79,17 +81,89 @@ impl Grid { } } +impl Cell { + fn delete_value(&self){ + unsafe { + if DEBUG { + println!("Cell {}, {} had its value deleted.", self.x, self.y); + } + } + + self.set_value_exact(CellValue::Unknown(vec![])); // placeholder + + // This will reset all the possibilities for this cell and the ones that might have been limited by this cell + self.section.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); + self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); + self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); + + } + + /** + As part of delete_value, we need to manually recalculate possibilities for not just the cell whose value we deleted, + but also the other empty cells in the same row, column, and section. + */ + fn calculate_possibilities(&self) -> Vec { + // Need to calculate possibilities for this cell + let mut possibilities = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + fn eliminate_possibilities(possibilities: &mut Vec, line: &Line, cell: &Cell){ + for (_index, other) in line.vec.iter().enumerate(){ + if other.x != cell.x || other.y != cell.y { + let value = &*other.value.borrow(); + match value { + CellValue::Fixed(digit) => { + let location = possibilities.binary_search(digit); + match location { + Ok(location) => { + possibilities.remove(location); + } + Err(_) => {} + } + } + CellValue::Unknown(_) => {} + } + } + } + } + + eliminate_possibilities(&mut possibilities, &self.section.upgrade().unwrap().borrow(), self); + eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self); + eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self); + + return possibilities; + } +} + +impl Line { + fn recalculate_and_set_possibilities(&self) { + for (_index, cell) in self.vec.iter().enumerate() { + let cell = &**cell; + let new_possibilities = { + let cell_value = &*cell.value.borrow(); + match cell_value { + CellValue::Fixed(_) => { continue; } + CellValue::Unknown(_) => { + cell.calculate_possibilities() + } + } + }; + + cell.set_value_exact(CellValue::Unknown(new_possibilities)); + } + } +} + 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 num_hints; 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(); + num_hints = 0; + + let digit_excluded = rng.gen_range(1, 10); for digit in 1..10 { if digit != digit_excluded { @@ -112,29 +186,27 @@ pub fn generate_grid(seed: u64) -> (Grid, i32) { }; // Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution + grid = '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 mut 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()); + cell_possibilities.shuffle(&mut rng); 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); + cell.set(*digit); let status = solve_grid(&grid_clone); match status { GenerateStatus::UniqueSolution => { // We're done! - return (grid_clone, num_hints); + break 'outer grid_clone; } GenerateStatus::Unfinished => { panic!("solve_grid should never return UNFINISHED") @@ -148,15 +220,53 @@ pub fn generate_grid(seed: u64) -> (Grid, i32) { } } - } + }; // 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; + eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y); + panic!("Unable to continue as puzzle is invalid"); + + }; + + // At this point we have a valid puzzle, but from experience it has way too many guesses, and many of them + // are likely not needed. Let's now try removing a bunch. + let mut non_empty_cells = Vec::new(); + for x in 0..9 { + for y in 0..9 { + let cell = grid.get(x, y).unwrap(); + let value = &*cell.value.borrow(); + match value { + CellValue::Fixed(_) => {non_empty_cells.push(Rc::clone(&cell))} + CellValue::Unknown(_) => {} + } + } } + // Need to randomly reorder non_empty_cells + non_empty_cells.shuffle(&mut rng); + + for (_index, cell) in non_empty_cells.iter().enumerate() { + let grid_clone = grid.clone(); + let cell_clone = grid_clone.get(cell.x, cell.y).unwrap(); + let cell_clone = &*cell_clone; + + cell_clone.delete_value(); + + let status = solve_grid(&mut grid); + match status { + GenerateStatus::UniqueSolution => { // great; that cell value was not needed + num_hints = num_hints - 1; + grid = grid_clone; + + } + GenerateStatus::Unfinished => {panic!("solve_grid should never return UNFINISHED")} + GenerateStatus::NoSolution => {panic!("Removing constraints should not have set the # of solutions to zero")} + GenerateStatus::NotUniqueSolution => {continue;} + }; + } + + return (grid, num_hints); }