|
@@ -4,10 +4,11 @@ use crate::group::*;
|
|
|
// use std::collections::HashSet;
|
|
|
use std::string::String;
|
|
|
|
|
|
+const SIZE: u8 = 3;
|
|
|
/// Width of the sudoku board.
|
|
|
-const WIDTH: u8 = 9;
|
|
|
+const WIDTH: u8 = SIZE*SIZE;
|
|
|
/// Size (width * height) of the board.
|
|
|
-const MAX_SIZE: u8 = 81;
|
|
|
+const MAX_SIZE: u8 = WIDTH * WIDTH; // 81;
|
|
|
|
|
|
// Use bitfields instead of HashSets.
|
|
|
use bit_field::BitField;
|
|
@@ -140,6 +141,12 @@ mod tests {
|
|
|
pub type SudokuBoard = [u8; MAX_SIZE as usize];
|
|
|
pub type SudokuPossible = [Possible; MAX_SIZE as usize];
|
|
|
|
|
|
+/*
|
|
|
+I probably should keep board and possible separate from one another.
|
|
|
+Possible is only used when solving the puzzles, and only used by the
|
|
|
+logic solver. Not needed by brute-force.
|
|
|
+ */
|
|
|
+
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
pub struct Sudoku {
|
|
|
pub board: [u8; MAX_SIZE as usize],
|
|
@@ -201,7 +208,7 @@ impl Sudoku {
|
|
|
}
|
|
|
|
|
|
/// Load puzzle from a string.
|
|
|
- /// Note, we load from (top,left), to (bottom,left) by columns.
|
|
|
+ /// Note, we load from (top,left) going down, to (bottom,left) by columns.
|
|
|
pub fn load_from_tld(&mut self, start_ch: char, blank: char, s: &str) {
|
|
|
self.clear();
|
|
|
let mut x: u8 = 0;
|
|
@@ -220,7 +227,7 @@ impl Sudoku {
|
|
|
}
|
|
|
|
|
|
/// Load puzzle from a string.
|
|
|
- /// This loads from (top,left) to (top,right), by rows.
|
|
|
+ /// This loads from (top,left) going right, to (top,right), by rows.
|
|
|
pub fn load_from_tlr(&mut self, start_ch: char, blank: char, s: &str) {
|
|
|
self.clear();
|
|
|
let mut i: u8 = 0;
|
|
@@ -240,7 +247,7 @@ impl Sudoku {
|
|
|
let mut x: u8 = 0;
|
|
|
let mut y: u8 = 0;
|
|
|
|
|
|
- for i in 0..MAX_SIZE {
|
|
|
+ for _i in 0..MAX_SIZE {
|
|
|
if self.board[pos(x, y) as usize] == 0 {
|
|
|
result.push(blank);
|
|
|
} else {
|
|
@@ -273,6 +280,12 @@ impl Sudoku {
|
|
|
self.board[pos(x,y) as usize]
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ This set does more then it needs to.
|
|
|
+ When setting a location, it also updates the possible.
|
|
|
+ This needs to be moved into something else. Putting it into Possible?
|
|
|
+ */
|
|
|
+
|
|
|
pub fn set(&mut self, x: u8, y: u8, value: u8) {
|
|
|
self.board[pos(x, y) as usize] = value;
|
|
|
// Ok, update the possible
|
|
@@ -300,6 +313,9 @@ impl Sudoku {
|
|
|
self.possible[pos(x, y) as usize].clear();
|
|
|
}
|
|
|
|
|
|
+ /// Reset the Possible
|
|
|
+ /// - For when a new puzzle has been loaded.
|
|
|
+ /// - When something has changed, and the possibles are out of sync.
|
|
|
pub fn reset_possible(&mut self) {
|
|
|
// Reset the possible.
|
|
|
self.clear_possible();
|
|
@@ -325,6 +341,7 @@ impl Sudoku {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Display the sudoku puzzle.
|
|
|
pub fn display(&self) {
|
|
|
println!("╔═══╦═══╦═══╗");
|
|
|
for y in 0..WIDTH {
|
|
@@ -351,6 +368,8 @@ impl Sudoku {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Display the possibles.
|
|
|
+ /// This should be in the Possible struct, not here.
|
|
|
pub fn display_possible(&self) {
|
|
|
for y in 0..WIDTH {
|
|
|
for x in 0..WIDTH {
|
|
@@ -381,6 +400,10 @@ impl Sudoku {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Is the puzzle complete?
|
|
|
+ /// Have all of the locations been filled with a value?
|
|
|
+ /// - This does not validate that it is a correct puzzle,
|
|
|
+ /// - It doesn't check for duplicate digits (for example).
|
|
|
pub fn puzzle_complete(&self) -> bool {
|
|
|
for i in 0..MAX_SIZE {
|
|
|
if self.board[i as usize] == 0 {
|
|
@@ -390,6 +413,9 @@ impl Sudoku {
|
|
|
true
|
|
|
}
|
|
|
|
|
|
+ /// Recursive brute-force solver
|
|
|
+ /// - As possibilities are tried, it recursively calls itself to see
|
|
|
+ /// if there are any solutions with the given possibility.
|
|
|
fn calculate_possible(&mut self, total_solutions: &mut u16, solutions: &mut Vec<SudokuBoard>) -> bool {
|
|
|
for idx in 0..MAX_SIZE {
|
|
|
if self.board[idx as usize] == 0 {
|
|
@@ -454,6 +480,9 @@ impl Sudoku {
|
|
|
false
|
|
|
}
|
|
|
|
|
|
+ /// Brute-force solver
|
|
|
+ /// - Prints out a (one) solution.
|
|
|
+ /// - Returns the number of solutions found.
|
|
|
pub fn bruteforce_solver(&self) -> u16 {
|
|
|
let mut workset = Sudoku {
|
|
|
board: self.board,
|
|
@@ -475,6 +504,8 @@ impl Sudoku {
|
|
|
total_solutions
|
|
|
}
|
|
|
|
|
|
+ /// Make a sudoku puzzle.
|
|
|
+ /// This gives us a fully solved puzzle.
|
|
|
pub fn make(&mut self) {
|
|
|
let mut rng = ChaCha20Rng::from_entropy();
|
|
|
|
|
@@ -482,6 +513,9 @@ impl Sudoku {
|
|
|
// Ok, this gives us a random (but fully solved) puzzle.
|
|
|
}
|
|
|
|
|
|
+ /// Fill puzzle with random
|
|
|
+ /// - This is like the brute-force solver, it calls itself recursively
|
|
|
+ /// and backtraces if a solution can't be found.
|
|
|
fn fill_board(&mut self, rng: &mut ChaCha20Rng) -> bool {
|
|
|
let backup = Sudoku {
|
|
|
board: self.board,
|
|
@@ -530,6 +564,10 @@ impl Sudoku {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ /// Puzzle generation
|
|
|
+ /// - Removes a number, tests to see if the puzzle can still be solved by logic.
|
|
|
+ /// - Returns true when still a valid, solvable puzzle.
|
|
|
+ /// - Otherwise, restores number and returns false.
|
|
|
pub fn remove(&mut self) -> bool {
|
|
|
// Find a number, remove it. Save position.
|
|
|
let mut rng = ChaCha20Rng::from_entropy();
|
|
@@ -611,6 +649,9 @@ impl Sudoku {
|
|
|
}
|
|
|
*/
|
|
|
|
|
|
+ /// Solve, using logic alone
|
|
|
+ /// - Returns true when something was added to the board.
|
|
|
+ /// - Call solve until it returns false.
|
|
|
pub fn solve(&mut self, debug: bool) -> bool {
|
|
|
// Pass 1: Look for singles in the possible sets.
|
|
|
let mut found_something = false;
|