|
@@ -5,15 +5,9 @@ use crate::group::*;
|
|
|
// use std::collections::HashSet;
|
|
|
use std::string::String;
|
|
|
|
|
|
-const SIZE: u8 = 3;
|
|
|
-/// Width of the sudoku board.
|
|
|
-const WIDTH: u8 = SIZE * SIZE;
|
|
|
-/// Size (width * height) of the board.
|
|
|
-const MAX_SIZE: u8 = WIDTH * WIDTH; // 81;
|
|
|
-
|
|
|
extern crate rand_chacha;
|
|
|
// use rand::prelude::*;
|
|
|
-use rand::distributions::{Distribution, Uniform};
|
|
|
+// use rand::distributions::{Distribution, Uniform};
|
|
|
use rand::seq::SliceRandom;
|
|
|
use rand_chacha::rand_core::SeedableRng;
|
|
|
use rand_chacha::ChaCha20Rng;
|
|
@@ -22,81 +16,6 @@ use rand_chacha::ChaCha20Rng;
|
|
|
use std::error;
|
|
|
use std::fmt;
|
|
|
|
|
|
-// Used bo calculate_possible to return the solutions.
|
|
|
-pub type SudokuBoard = [u8; MAX_SIZE as usize];
|
|
|
-
|
|
|
-/*
|
|
|
-pub type Possible = [Bits; MAX_SIZE as usize];
|
|
|
-pub type SudokuPossible = [Bits; MAX_SIZE as usize];
|
|
|
-*/
|
|
|
-
|
|
|
-#[derive(Debug, Clone, Copy)]
|
|
|
-pub struct Board([u8; MAX_SIZE as usize]);
|
|
|
-
|
|
|
-impl Board {
|
|
|
- pub fn new() -> Self {
|
|
|
- let s = Self {
|
|
|
- 0: [0; MAX_SIZE as usize],
|
|
|
- };
|
|
|
- s
|
|
|
- }
|
|
|
-
|
|
|
- pub fn clear(&mut self) {
|
|
|
- self.0 = [0; MAX_SIZE as usize];
|
|
|
- }
|
|
|
-
|
|
|
- pub fn set(&mut self, x: u8, y: u8, value: u8) {
|
|
|
- self.0[pos(x, y) as usize] = value;
|
|
|
- }
|
|
|
-
|
|
|
- pub fn get(&mut self, x: u8, y: u8) -> u8 {
|
|
|
- self.0[pos(x, y) as usize]
|
|
|
- }
|
|
|
-
|
|
|
- /// Load puzzle from a string.
|
|
|
- /// 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;
|
|
|
- let mut y: u8 = 0;
|
|
|
-
|
|
|
- for ch in s.chars() {
|
|
|
- if ch != blank {
|
|
|
- self.set(x, y, (ch as u8 - start_ch as u8) + 1);
|
|
|
- }
|
|
|
- y += 1;
|
|
|
- if y >= WIDTH {
|
|
|
- y = 0;
|
|
|
- x += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Save puzzle to a string.
|
|
|
- /// Note, we load from (top,left) going down, to (bottom,left) by columns.
|
|
|
-
|
|
|
- pub fn save_to_tld(&mut self, start_ch: char, blank: char) -> String {
|
|
|
- let mut result = String::new();
|
|
|
- result.reserve(MAX_SIZE as usize);
|
|
|
- let start_ch = (start_ch as u8 - 1) as char;
|
|
|
- let mut x: u8 = 0;
|
|
|
- let mut y: u8 = 0;
|
|
|
- for _i in 0..MAX_SIZE {
|
|
|
- if self.0[pos(x, y) as usize] == 0 {
|
|
|
- result.push(blank);
|
|
|
- } else {
|
|
|
- result.push((start_ch as u8 + self.0[pos(x, y) as usize]) as char);
|
|
|
- }
|
|
|
- y += 1;
|
|
|
- if y >= WIDTH {
|
|
|
- y = 0;
|
|
|
- x += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- result
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
#[derive(Debug, Clone)]
|
|
|
struct GameLoadError {
|
|
|
message: String,
|
|
@@ -916,7 +835,6 @@ impl AnySolver {
|
|
|
// - Record that we did something.
|
|
|
pass1 = true;
|
|
|
pass1_again = true;
|
|
|
- found_something = false;
|
|
|
}
|
|
|
}
|
|
|
pass1
|
|
@@ -999,7 +917,7 @@ impl AnySolver {
|
|
|
|
|
|
if found_something == true {
|
|
|
// Ok, pass 2 found something.
|
|
|
- pass2 = true;
|
|
|
+ pass2 = true;
|
|
|
found_something = false;
|
|
|
pass2_again = true;
|
|
|
// continue;
|
|
@@ -1009,344 +927,354 @@ impl AnySolver {
|
|
|
}
|
|
|
|
|
|
/// Solve by logic alone.
|
|
|
- /// - Returns true when something was added to board.
|
|
|
- /// - Call solve until it returns false.
|
|
|
+ /// - Returns true if solved.
|
|
|
/// - It might not be solved (if guessing is required).
|
|
|
pub fn solve_logic(&mut self) -> bool {
|
|
|
// self.reset_possible(); // destroys anything pass3 accomplishes ...
|
|
|
|
|
|
// self.board.display();
|
|
|
// self.possible.display();
|
|
|
- let mut result = true;
|
|
|
- // Pass 1: Look for singles in the possible sets.
|
|
|
- while result {
|
|
|
- result = self.logic_pass1();
|
|
|
- println!("Pass1");
|
|
|
- }
|
|
|
|
|
|
- let mut result2 = true;
|
|
|
- while result2 {
|
|
|
- result2 = self.logic_pass2();
|
|
|
- println!("Pass2");
|
|
|
- }
|
|
|
+ let mut found_more = true;
|
|
|
|
|
|
- result || result2
|
|
|
- /*
|
|
|
- // Pass 2: Is the same as Pass 1!
|
|
|
- // We just look in groups, instead of all the indexes.
|
|
|
+ while found_more {
|
|
|
+ found_more = false;
|
|
|
|
|
|
- let mut found_something = false;
|
|
|
- let mut pass1 = false;
|
|
|
+ let mut result1 = true;
|
|
|
+ // Pass 1: Look for singles in the possible sets.
|
|
|
+ while result1 {
|
|
|
+ result1 = self.logic_pass1();
|
|
|
+ if result1 {
|
|
|
+ println!("Pass1");
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- // Repeat pass 1 until all have been found.
|
|
|
- let mut pass1_again = true;
|
|
|
+ let mut result2 = true;
|
|
|
+ while result2 {
|
|
|
+ result2 = self.logic_pass2();
|
|
|
+ if result2 {
|
|
|
+ println!("Pass2");
|
|
|
+ found_more = true;
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
- let width = self.group.width;
|
|
|
- let grp = self.group.clone();
|
|
|
- let mut pass2 = false;
|
|
|
+ /*
|
|
|
+ // Pass 2: Is the same as Pass 1!
|
|
|
+ // We just look in groups, instead of all the indexes.
|
|
|
|
|
|
- while pass1_again {
|
|
|
- // Pass 1 - look for singles.
|
|
|
+ let mut found_something = false;
|
|
|
+ let mut pass1 = false;
|
|
|
|
|
|
- for i in 0..self.possible.max_index {
|
|
|
- if self.board.board[i] != 0 {
|
|
|
- // Skip, if position already filled.
|
|
|
- continue;
|
|
|
- }
|
|
|
+ // Repeat pass 1 until all have been found.
|
|
|
+ let mut pass1_again = true;
|
|
|
|
|
|
- if self.possible.possible[i].count_set() == 1 {
|
|
|
- // Get value
|
|
|
- let value = self.possible.possible[i].iter().next().unwrap() + 1;
|
|
|
- let pos = self.board.xy(i);
|
|
|
- // println!("SET {}({},{})={}", i, pos.0 + 1, pos.1 + 1, value);
|
|
|
- self.set(pos.0, pos.1, value);
|
|
|
- found_something = true;
|
|
|
- }
|
|
|
- }
|
|
|
+ let width = self.group.width;
|
|
|
+ let grp = self.group.clone();
|
|
|
+ let mut pass2 = false;
|
|
|
|
|
|
- if found_something {
|
|
|
- // We found something this pass.
|
|
|
- // - set pass1 to true (pass 1 found something)
|
|
|
- // - reset found_something so we can try again.
|
|
|
- pass1 = true;
|
|
|
- found_something = false;
|
|
|
- continue;
|
|
|
- } else {
|
|
|
- // Nothing found with this run of pass 1.
|
|
|
+ while pass1_again {
|
|
|
+ // Pass 1 - look for singles.
|
|
|
|
|
|
- // Pass 2 - Look for singles within the groups.
|
|
|
+ for i in 0..self.possible.max_index {
|
|
|
+ if self.board.board[i] != 0 {
|
|
|
+ // Skip, if position already filled.
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // Are we done?
|
|
|
- /*
|
|
|
- if self.board.complete() {
|
|
|
- return false;
|
|
|
+ if self.possible.possible[i].count_set() == 1 {
|
|
|
+ // Get value
|
|
|
+ let value = self.possible.possible[i].iter().next().unwrap() + 1;
|
|
|
+ let pos = self.board.xy(i);
|
|
|
+ // println!("SET {}({},{})={}", i, pos.0 + 1, pos.1 + 1, value);
|
|
|
+ self.set(pos.0, pos.1, value);
|
|
|
+ found_something = true;
|
|
|
+ }
|
|
|
}
|
|
|
- // We're getting stuck in pass 2 ...
|
|
|
- // not anymore. ;)
|
|
|
- println!("Pass 2:");
|
|
|
- self.board.display();
|
|
|
|
|
|
- */
|
|
|
-
|
|
|
- found_something = false;
|
|
|
+ if found_something {
|
|
|
+ // We found something this pass.
|
|
|
+ // - set pass1 to true (pass 1 found something)
|
|
|
+ // - reset found_something so we can try again.
|
|
|
+ pass1 = true;
|
|
|
+ found_something = false;
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ // Nothing found with this run of pass 1.
|
|
|
|
|
|
- // let mut values = Bits(0); // HashSet<u8> = HashSet::new();
|
|
|
- let mut values = GenBits::<u32>(0);
|
|
|
+ // Pass 2 - Look for singles within the groups.
|
|
|
|
|
|
- let mut group_process = |this: &mut Self, grp: &[usize]| {
|
|
|
- // Collect all the possible values within the group.
|
|
|
- values.clear();
|
|
|
- for gidx in 0..width {
|
|
|
- // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
- for v in this.possible.possible[grp[gidx as usize]].iter() {
|
|
|
- values.set(v as usize, true);
|
|
|
- }
|
|
|
- // values.extend(this.possible[grp.0[gidx as usize] as usize]);
|
|
|
- // println!("now : {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
+ // Are we done?
|
|
|
+ /*
|
|
|
+ if self.board.complete() {
|
|
|
+ return false;
|
|
|
}
|
|
|
+ // We're getting stuck in pass 2 ...
|
|
|
+ // not anymore. ;)
|
|
|
+ println!("Pass 2:");
|
|
|
+ self.board.display();
|
|
|
|
|
|
- // println!("values {:?}", values);
|
|
|
+ */
|
|
|
|
|
|
- // Now, check for singles.
|
|
|
- for v in values.iter() {
|
|
|
- // println!("Check Value: {}", v);
|
|
|
+ found_something = false;
|
|
|
|
|
|
- let mut count = 0;
|
|
|
- let mut pos = 0;
|
|
|
+ // let mut values = Bits(0); // HashSet<u8> = HashSet::new();
|
|
|
+ let mut values = GenBits::<u32>(0);
|
|
|
|
|
|
+ let mut group_process = |this: &mut Self, grp: &[usize]| {
|
|
|
+ // Collect all the possible values within the group.
|
|
|
+ values.clear();
|
|
|
for gidx in 0..width {
|
|
|
- if this.possible.possible[grp[gidx as usize]].get(v as usize) {
|
|
|
- // if this.possible[grp.0[gidx as usize] as usize].contains(&v) {
|
|
|
- count += 1;
|
|
|
- pos = grp[gidx as usize];
|
|
|
- if count > 1 {
|
|
|
- break;
|
|
|
- } else {
|
|
|
- // print!(" IDX {} POS {} ", gidx, pos);
|
|
|
- }
|
|
|
+ // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
+ for v in this.possible.possible[grp[gidx as usize]].iter() {
|
|
|
+ values.set(v as usize, true);
|
|
|
}
|
|
|
+ // values.extend(this.possible[grp.0[gidx as usize] as usize]);
|
|
|
+ // println!("now : {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
}
|
|
|
- if count == 1 {
|
|
|
- // don't need this, it was v!
|
|
|
- // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
|
|
|
- let xy = this.board.xy(pos);
|
|
|
- // this.possible.display();
|
|
|
- // this.board.display();
|
|
|
- this.set(xy.0, xy.1, v + 1);
|
|
|
- // println!("SET {} ({},{}) = {}", pos, xy.0 + 1, xy.1 + 1, v+1);
|
|
|
-
|
|
|
- found_something = true;
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // Change to 0..WIDTH ... Keep it simple.
|
|
|
- for i in 0..width {
|
|
|
- // println!("Column {i}:");
|
|
|
- let mut g = grp.column(i);
|
|
|
- group_process(self, g);
|
|
|
- // println!("Row {i}:");
|
|
|
- g = grp.row(i);
|
|
|
- group_process(self, g);
|
|
|
- // println!("Cell {i}:");
|
|
|
- g = grp.cell(i);
|
|
|
- group_process(self, g);
|
|
|
- }
|
|
|
-
|
|
|
- if found_something == true {
|
|
|
- pass2 = true;
|
|
|
- // Ok, pass 2 found something.
|
|
|
- found_something = false;
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- //
|
|
|
- // - If pass1 set, reset the found_something (because it
|
|
|
- // did find something)
|
|
|
- // - Clear the pass1_again flag, we're done with pass 1.
|
|
|
- if pass1 {
|
|
|
- pass1_again = false;
|
|
|
- found_something = true;
|
|
|
- } else {
|
|
|
- // Ok, we didn't find anything.
|
|
|
- // Break out of loop, get unstuck.
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- // Pass 3:
|
|
|
- // - Find pairs & remove those numbers from corresponding group.
|
|
|
+ // println!("values {:?}", values);
|
|
|
|
|
|
- let grp = self.group.clone();
|
|
|
+ // Now, check for singles.
|
|
|
+ for v in values.iter() {
|
|
|
+ // println!("Check Value: {}", v);
|
|
|
|
|
|
- println!("Pass 3:");
|
|
|
- self.board.display();
|
|
|
- self.possible.display();
|
|
|
+ let mut count = 0;
|
|
|
+ let mut pos = 0;
|
|
|
|
|
|
- assert_eq!(self.board.width, self.possible.width);
|
|
|
+ for gidx in 0..width {
|
|
|
+ if this.possible.possible[grp[gidx as usize]].get(v as usize) {
|
|
|
+ // if this.possible[grp.0[gidx as usize] as usize].contains(&v) {
|
|
|
+ count += 1;
|
|
|
+ pos = grp[gidx as usize];
|
|
|
+ if count > 1 {
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ // print!(" IDX {} POS {} ", gidx, pos);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if count == 1 {
|
|
|
+ // don't need this, it was v!
|
|
|
+ // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
|
|
|
+ let xy = this.board.xy(pos);
|
|
|
+ // this.possible.display();
|
|
|
+ // this.board.display();
|
|
|
+ this.set(xy.0, xy.1, v + 1);
|
|
|
+ // println!("SET {} ({},{}) = {}", pos, xy.0 + 1, xy.1 + 1, v+1);
|
|
|
+
|
|
|
+ found_something = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Change to 0..WIDTH ... Keep it simple.
|
|
|
+ for i in 0..width {
|
|
|
+ // println!("Column {i}:");
|
|
|
+ let mut g = grp.column(i);
|
|
|
+ group_process(self, g);
|
|
|
+ // println!("Row {i}:");
|
|
|
+ g = grp.row(i);
|
|
|
+ group_process(self, g);
|
|
|
+ // println!("Cell {i}:");
|
|
|
+ g = grp.cell(i);
|
|
|
+ group_process(self, g);
|
|
|
+ }
|
|
|
|
|
|
- // Pair processing.
|
|
|
- for i in 0..width {
|
|
|
- let mut g = grp.cell(i);
|
|
|
- println!("Cell {}: {:?}", i, g);
|
|
|
+ if found_something == true {
|
|
|
+ pass2 = true;
|
|
|
+ // Ok, pass 2 found something.
|
|
|
+ found_something = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- for gidx in 0..WIDTH - 1 {
|
|
|
- let gpos = g[gidx as usize];
|
|
|
- if self.possible.possible[gpos].count_set() == 2 {
|
|
|
- // Found a pair
|
|
|
+ //
|
|
|
+ // - If pass1 set, reset the found_something (because it
|
|
|
+ // did find something)
|
|
|
+ // - Clear the pass1_again flag, we're done with pass 1.
|
|
|
+ if pass1 {
|
|
|
+ pass1_again = false;
|
|
|
+ found_something = true;
|
|
|
+ } else {
|
|
|
+ // Ok, we didn't find anything.
|
|
|
+ // Break out of loop, get unstuck.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- for fidx in gidx + 1..width {
|
|
|
- let fpos = g[fidx as usize];
|
|
|
- if self.possible.possible[fpos as usize].count_set() == 2 {
|
|
|
- // Ok, there's another pair
|
|
|
- // if self.possible[gpos as usize].is_subset(&self.possible[fpos as usize])
|
|
|
+ // Pass 3:
|
|
|
+ // - Find pairs & remove those numbers from corresponding group.
|
|
|
|
|
|
- if self.possible.possible[gpos] == self.possible.possible[fpos] {
|
|
|
- // Ok, they have the same values!
|
|
|
- // Ok, remove the items in the pair from the cell.
|
|
|
- // Don't touch the gpos/fpos records. Keep those!
|
|
|
+ let grp = self.group.clone();
|
|
|
|
|
|
- // This looks .. WRONG! only z+1 when using set(value)!
|
|
|
+ println!("Pass 3:");
|
|
|
+ self.board.display();
|
|
|
+ self.possible.display();
|
|
|
|
|
|
- let mut values: [u8; 2] = [0, 0];
|
|
|
- let mut vpos = 0;
|
|
|
- for z in self.possible.possible[gpos].iter() {
|
|
|
- values[vpos] = z;
|
|
|
- vpos += 1;
|
|
|
- }
|
|
|
+ assert_eq!(self.board.width, self.possible.width);
|
|
|
|
|
|
- // Ok, other then being off by 1 (because x,y starts at 0 not 1),
|
|
|
- // This is, at least, displaying the information properly.
|
|
|
+ // Pair processing.
|
|
|
+ for i in 0..width {
|
|
|
+ let mut g = grp.cell(i);
|
|
|
+ println!("Cell {}: {:?}", i, g);
|
|
|
|
|
|
- self.possible.display();
|
|
|
- println!(
|
|
|
- "Pairs {gpos}({},{}) and {fpos}({},{}) {:?}",
|
|
|
- self.possible.xy(gpos).0 + 1,
|
|
|
- self.possible.xy(gpos).1 + 1,
|
|
|
- self.possible.xy(fpos).0 + 1,
|
|
|
- self.possible.xy(fpos).1 + 1,
|
|
|
- values
|
|
|
- );
|
|
|
+ for gidx in 0..WIDTH - 1 {
|
|
|
+ let gpos = g[gidx as usize];
|
|
|
+ if self.possible.possible[gpos].count_set() == 2 {
|
|
|
+ // Found a pair
|
|
|
|
|
|
- let mut pair_removed = false;
|
|
|
+ for fidx in gidx + 1..width {
|
|
|
+ let fpos = g[fidx as usize];
|
|
|
+ if self.possible.possible[fpos as usize].count_set() == 2 {
|
|
|
+ // Ok, there's another pair
|
|
|
+ // if self.possible[gpos as usize].is_subset(&self.possible[fpos as usize])
|
|
|
|
|
|
- // Check to see if anything was removed.
|
|
|
+ if self.possible.possible[gpos] == self.possible.possible[fpos] {
|
|
|
+ // Ok, they have the same values!
|
|
|
+ // Ok, remove the items in the pair from the cell.
|
|
|
+ // Don't touch the gpos/fpos records. Keep those!
|
|
|
|
|
|
- for remove in 0..width {
|
|
|
- if (gidx == remove) || (fidx == remove) {
|
|
|
- // Skip the found pair indexes. Don't remove those!
|
|
|
- continue;
|
|
|
- }
|
|
|
+ // This looks .. WRONG! only z+1 when using set(value)!
|
|
|
|
|
|
- // Ok, these aren't the ones to save, so:
|
|
|
- let rpos = g[remove as usize];
|
|
|
- if self.possible.possible[rpos].get(values[0] as usize) {
|
|
|
- self.possible.possible[rpos].set(values[0] as usize, false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- println!("Removed {} from {}({},{})", values[0], rpos, self.possible.xy(rpos).0, self.possible.xy(rpos).1);
|
|
|
+ let mut values: [u8; 2] = [0, 0];
|
|
|
+ let mut vpos = 0;
|
|
|
+ for z in self.possible.possible[gpos].iter() {
|
|
|
+ values[vpos] = z;
|
|
|
+ vpos += 1;
|
|
|
}
|
|
|
|
|
|
- if self.possible.possible[rpos].get(values[1] as usize) {
|
|
|
- self.possible.possible[rpos].set(values[1] as usize, false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- println!("Removed {} from {}({},{})", values[1], rpos, self.possible.xy(rpos).0, self.possible.xy(rpos).1);
|
|
|
- }
|
|
|
- }
|
|
|
+ // Ok, other then being off by 1 (because x,y starts at 0 not 1),
|
|
|
+ // This is, at least, displaying the information properly.
|
|
|
|
|
|
- if pair_removed {
|
|
|
- println!("pair removed...");
|
|
|
+ self.possible.display();
|
|
|
println!(
|
|
|
- "--> Pairs {gpos}({},{}) and {fpos}({},{}) {:?}",
|
|
|
+ "Pairs {gpos}({},{}) and {fpos}({},{}) {:?}",
|
|
|
self.possible.xy(gpos).0 + 1,
|
|
|
self.possible.xy(gpos).1 + 1,
|
|
|
self.possible.xy(fpos).0 + 1,
|
|
|
self.possible.xy(fpos).1 + 1,
|
|
|
values
|
|
|
);
|
|
|
- }
|
|
|
|
|
|
- // Check the x's and y's to see if we can also process a row/column too.
|
|
|
- if self.possible.xy(gpos).0 == self.possible.xy(fpos).0 {
|
|
|
- // Matching X - process column
|
|
|
- let column = xy(gpos).0;
|
|
|
+ let mut pair_removed = false;
|
|
|
|
|
|
- vpos = 0;
|
|
|
- for z in self.possible.possible[gpos].iter() {
|
|
|
- values[vpos] = z + 1;
|
|
|
- vpos += 1;
|
|
|
- }
|
|
|
- for remove in 0..WIDTH {
|
|
|
- if (remove == xy(gpos).1) || (remove == xy(fpos).1) {
|
|
|
+ // Check to see if anything was removed.
|
|
|
+
|
|
|
+ for remove in 0..width {
|
|
|
+ if (gidx == remove) || (fidx == remove) {
|
|
|
+ // Skip the found pair indexes. Don't remove those!
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- let pos = self.possible.pos(column, remove);
|
|
|
-
|
|
|
- if self.possible.possible[pos].get(values[0] as usize) {
|
|
|
- self.possible.possible[pos]
|
|
|
- .set(values[0] as usize, false);
|
|
|
+ // Ok, these aren't the ones to save, so:
|
|
|
+ let rpos = g[remove as usize];
|
|
|
+ if self.possible.possible[rpos].get(values[0] as usize) {
|
|
|
+ self.possible.possible[rpos].set(values[0] as usize, false);
|
|
|
found_something = true;
|
|
|
pair_removed = true;
|
|
|
+ println!("Removed {} from {}({},{})", values[0], rpos, self.possible.xy(rpos).0, self.possible.xy(rpos).1);
|
|
|
}
|
|
|
|
|
|
- if self.possible.possible[pos].get(values[1] as usize) {
|
|
|
- self.possible.possible[pos]
|
|
|
- .set(values[1] as usize, false);
|
|
|
+ if self.possible.possible[rpos].get(values[1] as usize) {
|
|
|
+ self.possible.possible[rpos].set(values[1] as usize, false);
|
|
|
found_something = true;
|
|
|
pair_removed = true;
|
|
|
+ println!("Removed {} from {}({},{})", values[1], rpos, self.possible.xy(rpos).0, self.possible.xy(rpos).1);
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- if self.possible.xy(gpos).1 == self.possible.xy(fpos).1 {
|
|
|
- // Matching Y - process row
|
|
|
- let row = self.possible.xy(gpos).1;
|
|
|
|
|
|
- vpos = 0;
|
|
|
- for z in self.possible.possible[gpos].iter() {
|
|
|
- values[vpos] = z + 1;
|
|
|
- vpos += 1;
|
|
|
+ if pair_removed {
|
|
|
+ println!("pair removed...");
|
|
|
+ println!(
|
|
|
+ "--> Pairs {gpos}({},{}) and {fpos}({},{}) {:?}",
|
|
|
+ self.possible.xy(gpos).0 + 1,
|
|
|
+ self.possible.xy(gpos).1 + 1,
|
|
|
+ self.possible.xy(fpos).0 + 1,
|
|
|
+ self.possible.xy(fpos).1 + 1,
|
|
|
+ values
|
|
|
+ );
|
|
|
}
|
|
|
- for remove in 0..width {
|
|
|
- if (remove == self.possible.xy(gpos).0)
|
|
|
- || (remove == self.possible.xy(fpos).0)
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
- let pos = self.possible.pos(remove, row);
|
|
|
|
|
|
- if self.possible.possible[pos].get(values[0] as usize) {
|
|
|
- self.possible.possible[pos]
|
|
|
- .set(values[0] as usize, false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
+ // Check the x's and y's to see if we can also process a row/column too.
|
|
|
+ if self.possible.xy(gpos).0 == self.possible.xy(fpos).0 {
|
|
|
+ // Matching X - process column
|
|
|
+ let column = xy(gpos).0;
|
|
|
+
|
|
|
+ vpos = 0;
|
|
|
+ for z in self.possible.possible[gpos].iter() {
|
|
|
+ values[vpos] = z + 1;
|
|
|
+ vpos += 1;
|
|
|
}
|
|
|
+ for remove in 0..WIDTH {
|
|
|
+ if (remove == xy(gpos).1) || (remove == xy(fpos).1) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ let pos = self.possible.pos(column, remove);
|
|
|
+
|
|
|
+ if self.possible.possible[pos].get(values[0] as usize) {
|
|
|
+ self.possible.possible[pos]
|
|
|
+ .set(values[0] as usize, false);
|
|
|
+ found_something = true;
|
|
|
+ pair_removed = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.possible.possible[pos].get(values[1] as usize) {
|
|
|
+ self.possible.possible[pos]
|
|
|
+ .set(values[1] as usize, false);
|
|
|
+ found_something = true;
|
|
|
+ pair_removed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if self.possible.possible[pos].get(values[1] as usize) {
|
|
|
- self.possible.possible[pos]
|
|
|
- .set(values[1] as usize, false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
+ if self.possible.xy(gpos).1 == self.possible.xy(fpos).1 {
|
|
|
+ // Matching Y - process row
|
|
|
+ let row = self.possible.xy(gpos).1;
|
|
|
+
|
|
|
+ vpos = 0;
|
|
|
+ for z in self.possible.possible[gpos].iter() {
|
|
|
+ values[vpos] = z + 1;
|
|
|
+ vpos += 1;
|
|
|
+ }
|
|
|
+ for remove in 0..width {
|
|
|
+ if (remove == self.possible.xy(gpos).0)
|
|
|
+ || (remove == self.possible.xy(fpos).0)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ let pos = self.possible.pos(remove, row);
|
|
|
+
|
|
|
+ if self.possible.possible[pos].get(values[0] as usize) {
|
|
|
+ self.possible.possible[pos]
|
|
|
+ .set(values[0] as usize, false);
|
|
|
+ found_something = true;
|
|
|
+ pair_removed = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if self.possible.possible[pos].get(values[1] as usize) {
|
|
|
+ self.possible.possible[pos]
|
|
|
+ .set(values[1] as usize, false);
|
|
|
+ found_something = true;
|
|
|
+ pair_removed = true;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if pair_removed {
|
|
|
- if true {
|
|
|
- println!(
|
|
|
- "Pair found! {} {}: {} {:?} and {} {:?} !",
|
|
|
- gidx,
|
|
|
- fidx,
|
|
|
- gpos,
|
|
|
- self.board.xy(gpos),
|
|
|
- fpos,
|
|
|
- self.board.xy(fpos)
|
|
|
- );
|
|
|
- self.possible.display();
|
|
|
- // panic!("... We're lost ...");
|
|
|
+ if pair_removed {
|
|
|
+ if true {
|
|
|
+ println!(
|
|
|
+ "Pair found! {} {}: {} {:?} and {} {:?} !",
|
|
|
+ gidx,
|
|
|
+ fidx,
|
|
|
+ gpos,
|
|
|
+ self.board.xy(gpos),
|
|
|
+ fpos,
|
|
|
+ self.board.xy(fpos)
|
|
|
+ );
|
|
|
+ self.possible.display();
|
|
|
+ // panic!("... We're lost ...");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -1354,10 +1282,12 @@ impl AnySolver {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ println!("Pass 3 - Ending...");
|
|
|
+ found_something
|
|
|
+ */
|
|
|
}
|
|
|
- println!("Pass 3 - Ending...");
|
|
|
- found_something
|
|
|
- */
|
|
|
+ self.board.complete()
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1396,9 +1326,8 @@ mod tests {
|
|
|
assert!(solver.validate_board());
|
|
|
|
|
|
solver.reset_possible();
|
|
|
- // solver.solve_logic();
|
|
|
|
|
|
- while (solver.solve_logic()) {
|
|
|
+ if solver.solve_logic() {
|
|
|
solver.board.display();
|
|
|
}
|
|
|
|
|
@@ -1436,7 +1365,7 @@ mod tests {
|
|
|
assert!(solver.validate_board());
|
|
|
solver.board.display();
|
|
|
|
|
|
- while (solver.solve_logic()) {
|
|
|
+ if solver.solve_logic() {
|
|
|
solver.board.display();
|
|
|
}
|
|
|
|
|
@@ -1546,17 +1475,6 @@ mod tests {
|
|
|
let solutions = solver.board.brute_force_solver(2);
|
|
|
assert!(solutions.0 == 1);
|
|
|
}
|
|
|
-
|
|
|
- /*
|
|
|
- let mut board: AnyBoard = AnyBoard::new(5);
|
|
|
- let result = board.load_from_tld('b', '_', "_m_kzg____h_s_n____ftd_u_u_dh_____f_b_t_w_____yg_c_rl_o_tdhy__m__uvig_w_sk_eg___p_q_jikouys_r_d___mlq_t_sb_emcwg_dlzyo_kp_i_ng__ir_b_fp_vhz_ce_y_jm__w__m__o_k_xul_qbt_d_s__e____otv_dhegn___mfkpz_blr____s_dv_n_mjx_ckg_w_bo_p___kqyelwjcz_____nxumoisdh_z__fp_vbi_______dkx_eg__r_y_mlwf_u__q_i__o_chdv_j_i_he_r_____________p_zl_k_d_vbjh_y__e_p__s_tguc_q_s__qj_kpn_______ufw_hx__i_hvntirfxw_____lbckympjg___u_kz_m_bfn_yvx_h_ir_o____rgm_otlnx___ipfes_kwc____p__c_v_ugh_krj_m_w__x__x__ci_j_qk_mpo_dr_u_zb__ht_i_qe_wjvcy_bhkzx_ng_u_syv___u_c_hsfrlqo_t_e___pj_cn_h_slzr__j__mqgp_y_vd_m_bs_____t_o_n_h_____ez_f_e_ufd____p_g_z____cqr_x_");
|
|
|
- assert!(result.is_ok());
|
|
|
- board.display();
|
|
|
-
|
|
|
- let mut solver = AnySolver::new(&board);
|
|
|
- assert!(solver.validate_board());
|
|
|
- assert!(solver.brute_force_solver() == 1);
|
|
|
- */
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -1632,861 +1550,50 @@ mod tests {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
-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.
|
|
|
-
|
|
|
-Ok, the problem is that the possible doesn't make any sense to be
|
|
|
-unlinked in any way from the board. I think they have to be together.
|
|
|
-
|
|
|
-Right now, I have &mut of board, it might be better to just copy it.
|
|
|
-Then, I could derive Clone and Copy again for BoardPossible...
|
|
|
-
|
|
|
-(And simplify things immensely, getting rid of lifetimes...)
|
|
|
-
|
|
|
-Still thinking of having Possible as a structure, so I could implement
|
|
|
-funcs on just that, rather then having them in BoardPossible (wrong
|
|
|
-place for Possible's implementation).
|
|
|
- */
|
|
|
-
|
|
|
-/*
|
|
|
-#[derive(Debug, Clone, Copy)]
|
|
|
-pub struct SudokuPossible {
|
|
|
- pub possible: [Possible; MAX_SIZE as usize];
|
|
|
-}
|
|
|
-
|
|
|
- // 10 = WIDTH + 1
|
|
|
- // This would need to be changed in order to handle 4x4 or 5x5.
|
|
|
- // NOTE: We ignore bit 0, we use bits 1-9 (for 3x3).
|
|
|
- let mut initial: Bits = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
-
|
|
|
- let s = Sudoku {
|
|
|
- board: [0; MAX_SIZE as usize],
|
|
|
- possible: [initial; MAX_SIZE as usize],
|
|
|
-
|
|
|
-*/
|
|
|
+/*
|
|
|
+ /// 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();
|
|
|
+ let puzrange = Uniform::new(0, WIDTH);
|
|
|
+ let mut x = 0;
|
|
|
+ let mut y = 0;
|
|
|
+ let mut value: u8 = 0;
|
|
|
|
|
|
-/*
|
|
|
-#[derive(Debug)]
|
|
|
-pub struct BoardPossible<'a> {
|
|
|
- board: &'a mut Board,
|
|
|
- possible: [Bits; MAX_SIZE as usize],
|
|
|
-}
|
|
|
+ while value == 0 {
|
|
|
+ x = puzrange.sample(&mut rng);
|
|
|
+ y = puzrange.sample(&mut rng);
|
|
|
+ value = self.get(x, y);
|
|
|
+ }
|
|
|
|
|
|
-impl<'a> BoardPossible<'a> {
|
|
|
- pub fn new(board: &'a mut Board) -> Self {
|
|
|
- // 10 = WIDTH + 1
|
|
|
- // This would need to be changed in order to handle 4x4 or 5x5.
|
|
|
- // NOTE: We ignore bit 0, we use bits 1-9 (for 3x3).
|
|
|
- let mut initial: Bits = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
+ self.set(x, y, 0);
|
|
|
|
|
|
- let mut s = Self {
|
|
|
- board: board,
|
|
|
- possible: [initial; MAX_SIZE as usize],
|
|
|
- };
|
|
|
- s.reset_possible();
|
|
|
- s
|
|
|
- }
|
|
|
+ // Clone, and solve by logic.
|
|
|
+ let mut puzcopy = self.clone();
|
|
|
|
|
|
- pub fn clear_possible(&mut self) {
|
|
|
- let mut initial: Bits = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
- self.possible = [initial; MAX_SIZE as usize];
|
|
|
- }
|
|
|
+ puzcopy.reset_possible();
|
|
|
+ /*
|
|
|
+ puzcopy.display();
|
|
|
+ puzcopy.display_possible();
|
|
|
+ */
|
|
|
+ // If solvable, return true.
|
|
|
+ while puzcopy.solve(false) {}
|
|
|
|
|
|
- /// set (x,y) with value.
|
|
|
- /// - This updates the board.
|
|
|
- /// - this updates all the possible (row,column,cell).
|
|
|
- /// - Clears the possible for the (x,y).
|
|
|
- pub fn set(&mut self, x: u8, y: u8, value: u8) {
|
|
|
- self.board.0[pos(x, y)] = value;
|
|
|
+ /*
|
|
|
+ puzcopy.display();
|
|
|
+ puzcopy.display_possible();
|
|
|
+ */
|
|
|
|
|
|
- // update the possible
|
|
|
- let mut g: &Group = for_row(y);
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g].set(value, false);
|
|
|
- }
|
|
|
- g = for_column(x);
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g].set(value, false);
|
|
|
- }
|
|
|
- g = for_cell(which_cell(x, y));
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g].set(value, false);
|
|
|
+ if puzcopy.puzzle_complete() {
|
|
|
+ return true;
|
|
|
}
|
|
|
- self.possible[pos(x, y)].clear();
|
|
|
- }
|
|
|
-
|
|
|
- pub fn reset_possible(&mut self) {}
|
|
|
-
|
|
|
- /// Display the possibles.
|
|
|
- /// This should be in the Possible struct, not here.
|
|
|
- pub fn display(&self) {
|
|
|
- for y in 0..WIDTH {
|
|
|
- for x in 0..WIDTH {
|
|
|
- // print!("p={:?}", self.possible[pos(x, y) as usize]);
|
|
|
- let mut possible = String::new();
|
|
|
-
|
|
|
- for p in self.possible[pos(x, y) as usize].iter() {
|
|
|
- // print!("{},", p);
|
|
|
- possible += format!("{},", p).as_str();
|
|
|
- }
|
|
|
|
|
|
- // for i in 0..SIZE {
|
|
|
- // let &pos = self.possible[pos(x, y) as usize];
|
|
|
- print!("({},{}):", x, y);
|
|
|
- // print!("{:20}", format!("{:?}", self.possible[pos(x, y) as usize]));
|
|
|
- print!("{:9}", possible);
|
|
|
- /*
|
|
|
- if pos == 0 {
|
|
|
- print!(" ");
|
|
|
- } else {
|
|
|
- print!("{}", pos);
|
|
|
- }
|
|
|
- */
|
|
|
- // }
|
|
|
- // print!(" ");
|
|
|
- }
|
|
|
- println!("");
|
|
|
- }
|
|
|
+ // If not solvable, restore number, return false.
|
|
|
+ self.set(x, y, value);
|
|
|
+ return false;
|
|
|
}
|
|
|
-}
|
|
|
*/
|
|
|
-
|
|
|
-/*
|
|
|
- ___ _ _ ____ _
|
|
|
- / _ \| | __| | / ___|___ __| | ___
|
|
|
-| | | | |/ _` | | | / _ \ / _` |/ _ \
|
|
|
-| |_| | | (_| | | |__| (_) | (_| | __/
|
|
|
- \___/|_|\__,_| \____\___/ \__,_|\___|
|
|
|
-
|
|
|
- */
|
|
|
-
|
|
|
-#[derive(Debug, Clone, Copy)]
|
|
|
-pub struct Sudoku {
|
|
|
- pub board: [u8; MAX_SIZE as usize], // Board
|
|
|
- pub possible: [Bits; MAX_SIZE as usize], // BoardPossible
|
|
|
-}
|
|
|
-
|
|
|
-/// Translate x,y to position in board.
|
|
|
-/// This is used as usize, so return that instead of u8.
|
|
|
-pub const fn pos(x: u8, y: u8) -> usize {
|
|
|
- x as usize + (y as usize * WIDTH as usize)
|
|
|
-}
|
|
|
-
|
|
|
-/// Translate x,y (with starting index of 1) to position in board.
|
|
|
-pub const fn pos1(x: u8, y: u8) -> usize {
|
|
|
- (x as usize - 1) + ((y as usize - 1) * WIDTH as usize)
|
|
|
-}
|
|
|
-
|
|
|
-/// Translate post to x,y in board.
|
|
|
-pub const fn xy(pos: usize) -> (u8, u8) {
|
|
|
- ((pos % WIDTH as usize) as u8, (pos / WIDTH as usize) as u8)
|
|
|
-}
|
|
|
-
|
|
|
-const DEBUG_OUTPUT: bool = false;
|
|
|
-
|
|
|
-/*
|
|
|
-(0 .. 10)
|
|
|
- .map(|_| HashSet::<usize>::new())
|
|
|
- .collect();
|
|
|
-
|
|
|
-let arr: [Vec<u32>; 10] = [(); 10].map(|_| Vec::with_capacity(100));
|
|
|
-
|
|
|
-*/
|
|
|
-
|
|
|
-impl Sudoku {
|
|
|
- pub fn new() -> Self {
|
|
|
- // 10 = WIDTH + 1
|
|
|
- // This would need to be changed in order to handle 4x4 or 5x5.
|
|
|
- // NOTE: We ignore bit 0, we use bits 1-9 (for 3x3).
|
|
|
- let mut initial: Bits = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
-
|
|
|
- let s = Sudoku {
|
|
|
- board: [0; MAX_SIZE as usize],
|
|
|
- possible: [initial; MAX_SIZE as usize],
|
|
|
- };
|
|
|
- s
|
|
|
- }
|
|
|
-
|
|
|
- pub fn clear_possible(&mut self) {
|
|
|
- let mut initial = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
- self.possible = [initial; MAX_SIZE as usize];
|
|
|
- }
|
|
|
-
|
|
|
- pub fn clear(&mut self) {
|
|
|
- let mut initial = Bits(set_bits(10));
|
|
|
- initial.set(0, false);
|
|
|
-
|
|
|
- self.board = [0; MAX_SIZE as usize];
|
|
|
- self.possible = [initial; MAX_SIZE as usize];
|
|
|
- }
|
|
|
-
|
|
|
- /// Load puzzle from a string.
|
|
|
- /// 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;
|
|
|
- let mut y: u8 = 0;
|
|
|
-
|
|
|
- for ch in s.chars() {
|
|
|
- if ch != blank {
|
|
|
- self.set(x, y, (ch as u8 - start_ch as u8) + 1);
|
|
|
- }
|
|
|
- y += 1;
|
|
|
- if y >= WIDTH {
|
|
|
- y = 0;
|
|
|
- x += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Load puzzle from a string.
|
|
|
- /// 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: usize = 0;
|
|
|
-
|
|
|
- for ch in s.chars() {
|
|
|
- if ch != blank {
|
|
|
- self.set(xy(i).0, xy(i).1, (ch as u8 - start_ch as u8) + 1);
|
|
|
- }
|
|
|
- i += 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn save_to_tld(&self, mut start_ch: char, blank: char) -> String {
|
|
|
- let mut result = String::new();
|
|
|
- result.reserve(MAX_SIZE as usize);
|
|
|
- start_ch = (start_ch as u8 - 1) as char;
|
|
|
- let mut x: u8 = 0;
|
|
|
- let mut y: u8 = 0;
|
|
|
-
|
|
|
- for _i in 0..MAX_SIZE {
|
|
|
- if self.board[pos(x, y) as usize] == 0 {
|
|
|
- result.push(blank);
|
|
|
- } else {
|
|
|
- result.push((start_ch as u8 + self.board[pos(x, y) as usize]) as char);
|
|
|
- }
|
|
|
- y += 1;
|
|
|
- if y >= WIDTH {
|
|
|
- y = 0;
|
|
|
- x += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- result
|
|
|
- }
|
|
|
-
|
|
|
- pub fn save_to_tlr(&self, mut start_ch: char, blank: char) -> String {
|
|
|
- let mut result = String::new();
|
|
|
- result.reserve(MAX_SIZE as usize);
|
|
|
- start_ch = (start_ch as u8 - 1) as char;
|
|
|
- for i in 0..MAX_SIZE {
|
|
|
- if self.board[i as usize] == 0 {
|
|
|
- result.push(blank);
|
|
|
- } else {
|
|
|
- result.push((start_ch as u8 + self.board[i as usize]) as char);
|
|
|
- }
|
|
|
- }
|
|
|
- result
|
|
|
- }
|
|
|
-
|
|
|
- pub fn get(&self, x: u8, y: u8) -> u8 {
|
|
|
- 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
|
|
|
- let mut g = for_row(y);
|
|
|
- // g.for_row(x, y);
|
|
|
- for g in g.0 {
|
|
|
- // remove value from these sets.
|
|
|
- self.possible[g as usize].set(value, false);
|
|
|
- // self.possible[g as usize].take(&value);
|
|
|
- }
|
|
|
- g = for_column(x);
|
|
|
- // g.for_column(x, y);
|
|
|
- for g in g.0 {
|
|
|
- // remove value from these sets.
|
|
|
- self.possible[g as usize].set(value, false);
|
|
|
- // self.possible[g as usize].take(&value);
|
|
|
- }
|
|
|
- g = for_cell(which_cell(x, y));
|
|
|
- // g.for_block(x, y);
|
|
|
- for g in g.0 {
|
|
|
- // remove value from these sets.
|
|
|
- self.possible[g as usize].set(value, false);
|
|
|
- // self.possible[g as usize].take(&value);
|
|
|
- }
|
|
|
- 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();
|
|
|
- for y in 0..WIDTH {
|
|
|
- for x in 0..WIDTH {
|
|
|
- let item: u8 = self.board[pos(x as u8, y as u8) as usize];
|
|
|
- if item != 0 {
|
|
|
- let mut g: &Group = for_row(y);
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g as usize].set(item, false);
|
|
|
- }
|
|
|
- g = for_column(x);
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g as usize].set(item, false);
|
|
|
- }
|
|
|
- g = for_cell(which_cell(x, y));
|
|
|
- for g in g.0 {
|
|
|
- self.possible[g as usize].set(item, false);
|
|
|
- }
|
|
|
- self.possible[pos(x, y) as usize].clear();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Display the sudoku puzzle.
|
|
|
- pub fn display(&self) {
|
|
|
- println!("╔═══╦═══╦═══╗");
|
|
|
- for y in 0..WIDTH {
|
|
|
- print!("║");
|
|
|
- for x in 0..WIDTH {
|
|
|
- let item = self.board[pos(x as u8, y as u8) as usize];
|
|
|
- if item == 0 {
|
|
|
- print!(" ");
|
|
|
- } else if (item >= 1) && (item <= 9) {
|
|
|
- print!("{}", item);
|
|
|
- }
|
|
|
- if x % 3 == 2 {
|
|
|
- print!("║");
|
|
|
- }
|
|
|
- }
|
|
|
- println!("");
|
|
|
- if y % 3 == 2 {
|
|
|
- if y + 1 == WIDTH {
|
|
|
- println!("╚═══╩═══╩═══╝");
|
|
|
- } else {
|
|
|
- println!("╠═══╬═══╬═══╣");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 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 {
|
|
|
- // print!("p={:?}", self.possible[pos(x, y) as usize]);
|
|
|
- let mut possible = String::new();
|
|
|
-
|
|
|
- for p in self.possible[pos(x, y) as usize].iter() {
|
|
|
- // print!("{},", p);
|
|
|
- possible += format!("{},", p).as_str();
|
|
|
- }
|
|
|
-
|
|
|
- // for i in 0..SIZE {
|
|
|
- // let &pos = self.possible[pos(x, y) as usize];
|
|
|
- print!("({},{}):", x, y);
|
|
|
- // print!("{:20}", format!("{:?}", self.possible[pos(x, y) as usize]));
|
|
|
- print!("{:9}", possible);
|
|
|
- /*
|
|
|
- if pos == 0 {
|
|
|
- print!(" ");
|
|
|
- } else {
|
|
|
- print!("{}", pos);
|
|
|
- }
|
|
|
- */
|
|
|
- // }
|
|
|
- // print!(" ");
|
|
|
- }
|
|
|
- println!("");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 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 {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
- 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 as usize {
|
|
|
- if self.board[idx as usize] == 0 {
|
|
|
- // Ok, there's a blank here
|
|
|
-
|
|
|
- let (x, y) = xy(idx);
|
|
|
- if DEBUG_OUTPUT {
|
|
|
- println!("idx={} ({},{})", idx, x, y);
|
|
|
- self.display();
|
|
|
- }
|
|
|
-
|
|
|
- 'outer: for possible in 1..=9 {
|
|
|
- let mut g = for_row(y);
|
|
|
- for p in g.0 {
|
|
|
- if self.board[p as usize] == possible {
|
|
|
- continue 'outer;
|
|
|
- }
|
|
|
- }
|
|
|
- g = for_column(x);
|
|
|
- for p in g.0 {
|
|
|
- if self.board[p as usize] == possible {
|
|
|
- continue 'outer;
|
|
|
- }
|
|
|
- }
|
|
|
- // check cell
|
|
|
- let cell = which_cell(x, y);
|
|
|
- g = for_cell(cell);
|
|
|
- for p in g.0 {
|
|
|
- if self.board[p as usize] == possible {
|
|
|
- continue 'outer;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Ok, it could go here!
|
|
|
- if DEBUG_OUTPUT {
|
|
|
- println!("({},{})={}", x, y, possible);
|
|
|
- }
|
|
|
-
|
|
|
- self.board[idx as usize] = possible;
|
|
|
- if self.puzzle_complete() {
|
|
|
- if *total_solutions < solutions.capacity() as u16 {
|
|
|
- solutions.push(self.board);
|
|
|
- }
|
|
|
- *total_solutions += 1;
|
|
|
- /*
|
|
|
- println!("**SOLUTION**");
|
|
|
- self.display();
|
|
|
- println!("***");
|
|
|
- */
|
|
|
- break;
|
|
|
- } else {
|
|
|
- if self.calculate_possible(total_solutions, solutions) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- self.board[idx as usize] = 0;
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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,
|
|
|
- possible: [Bits(0); MAX_SIZE as usize],
|
|
|
- };
|
|
|
-
|
|
|
- let mut total_solutions: u16 = 0;
|
|
|
- let mut solutions = Vec::new();
|
|
|
- solutions.reserve(1);
|
|
|
- workset.calculate_possible(&mut total_solutions, &mut solutions);
|
|
|
-
|
|
|
- // return number of solutions.
|
|
|
- if solutions.len() > 0 {
|
|
|
- println!("*** A solution:");
|
|
|
- workset.board = solutions[0];
|
|
|
- workset.display();
|
|
|
- println!("***");
|
|
|
- }
|
|
|
- total_solutions
|
|
|
- }
|
|
|
-
|
|
|
- /// Make a sudoku puzzle.
|
|
|
- /// This gives us a fully solved puzzle.
|
|
|
- pub fn make(&mut self) {
|
|
|
- let mut rng = ChaCha20Rng::from_entropy();
|
|
|
-
|
|
|
- self.fill_board(&mut rng);
|
|
|
- // 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,
|
|
|
- possible: self.possible,
|
|
|
- };
|
|
|
-
|
|
|
- for idx in 0..MAX_SIZE as usize {
|
|
|
- if self.board[idx as usize] == 0 {
|
|
|
- let (x, y) = xy(idx);
|
|
|
- let mut available: [u8; WIDTH as usize] = [0; WIDTH as usize];
|
|
|
- let mut total_available: u8 = 0;
|
|
|
-
|
|
|
- for t in self.possible[idx as usize].iter() {
|
|
|
- available[total_available as usize] = t;
|
|
|
- total_available += 1;
|
|
|
- }
|
|
|
-
|
|
|
- if total_available == 0 {
|
|
|
- // No possible moves remain.
|
|
|
- /*
|
|
|
- self.board = backup.board;
|
|
|
- self.possible = backup.possible;
|
|
|
- */
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // Randomize the possible items.
|
|
|
- available[0..total_available as usize].shuffle(rng);
|
|
|
- for v_idx in 0..total_available {
|
|
|
- let value = available[v_idx as usize];
|
|
|
- self.set(x, y, value);
|
|
|
- if self.fill_board(rng) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- // failure
|
|
|
- self.board = backup.board;
|
|
|
- self.possible = backup.possible;
|
|
|
- }
|
|
|
-
|
|
|
- // We've run out of possible.
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // We've visited everything, and it isn't 0.
|
|
|
- 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();
|
|
|
- let puzrange = Uniform::new(0, WIDTH);
|
|
|
- let mut x = 0;
|
|
|
- let mut y = 0;
|
|
|
- let mut value: u8 = 0;
|
|
|
-
|
|
|
- while value == 0 {
|
|
|
- x = puzrange.sample(&mut rng);
|
|
|
- y = puzrange.sample(&mut rng);
|
|
|
- value = self.get(x, y);
|
|
|
- }
|
|
|
-
|
|
|
- self.set(x, y, 0);
|
|
|
-
|
|
|
- // Clone, and solve by logic.
|
|
|
- let mut puzcopy = self.clone();
|
|
|
-
|
|
|
- puzcopy.reset_possible();
|
|
|
- /*
|
|
|
- puzcopy.display();
|
|
|
- puzcopy.display_possible();
|
|
|
- */
|
|
|
- // If solvable, return true.
|
|
|
- while puzcopy.solve(false) {}
|
|
|
-
|
|
|
- /*
|
|
|
- puzcopy.display();
|
|
|
- puzcopy.display_possible();
|
|
|
- */
|
|
|
-
|
|
|
- if puzcopy.puzzle_complete() {
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- // If not solvable, restore number, return false.
|
|
|
- self.set(x, y, value);
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- fn make_(&mut self) {
|
|
|
- self.clear();
|
|
|
-
|
|
|
- let mut rng = ChaCha20Rng::from_entropy();
|
|
|
-
|
|
|
- let pick_one = |this: &Self, rng: &mut ChaCha20Rng, idx: u8| -> Option<u8> {
|
|
|
- let mut available: [u8; WIDTH as usize] = [0; WIDTH as usize];
|
|
|
- let mut total_available: u8 = 0;
|
|
|
-
|
|
|
- for t in this.possible[idx as usize].iter() {
|
|
|
- available[total_available as usize] = t;
|
|
|
- total_available += 1;
|
|
|
- }
|
|
|
- if total_available == 1 {
|
|
|
- return Some(available[0]);
|
|
|
- }
|
|
|
- if total_available == 0 {
|
|
|
- return None;
|
|
|
- }
|
|
|
- Some(available[rng.gen_range(0..total_available as usize)])
|
|
|
- };
|
|
|
-
|
|
|
- for i in 0..MAX_SIZE {
|
|
|
- let (x, y) = xy(i);
|
|
|
- if self.board[i as usize] == 0 {
|
|
|
- // Ok, we found a blank space.
|
|
|
- let value = pick_one(self, &mut rng, i);
|
|
|
- if value.is_some() {
|
|
|
- let value = value.unwrap();
|
|
|
- if DEBUG_OUTPUT {
|
|
|
- println!("Set({},{})={}", x, y, value);
|
|
|
- }
|
|
|
- self.set(x, y, value);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- */
|
|
|
-
|
|
|
- /// 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;
|
|
|
-
|
|
|
- for i in 0..MAX_SIZE as usize {
|
|
|
- if self.possible[i as usize].count_set() == 1 {
|
|
|
- // Get the value
|
|
|
- let value = self.possible[i as usize].iter().next().unwrap();
|
|
|
- // Found one!
|
|
|
- if debug {
|
|
|
- println!("Set1 {:?} to {}", xy(i), value);
|
|
|
- }
|
|
|
- self.set(xy(i).0, xy(i).1, value);
|
|
|
- found_something = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let mut g = Group::new();
|
|
|
- let mut values = Bits(0); // HashSet<u8> = HashSet::new();
|
|
|
-
|
|
|
- let mut group_process = |this: &mut Self, grp: &Group| {
|
|
|
- // Collect all the possible values within the group.
|
|
|
- values.clear();
|
|
|
- for gidx in 0..WIDTH {
|
|
|
- // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
- for v in this.possible[grp.0[gidx as usize] as usize].iter() {
|
|
|
- values.set(v, true);
|
|
|
- }
|
|
|
- // values.extend(this.possible[grp.0[gidx as usize] as usize]);
|
|
|
- // println!("now : {:?}", this.possible[grp.items[gidx as usize] as usize]);
|
|
|
- }
|
|
|
-
|
|
|
- // println!("values {:?}", values);
|
|
|
-
|
|
|
- // Now, check for singles.
|
|
|
- for v in values.iter() {
|
|
|
- let mut count = 0;
|
|
|
- let mut pos = 0;
|
|
|
- for gidx in 0..WIDTH {
|
|
|
- if this.possible[grp.0[gidx as usize] as usize].get(v) {
|
|
|
- // if this.possible[grp.0[gidx as usize] as usize].contains(&v) {
|
|
|
- count += 1;
|
|
|
- pos = grp.0[gidx as usize];
|
|
|
- if count > 1 {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if count == 1 {
|
|
|
- // don't need this, it was v!
|
|
|
- // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
|
|
|
- if debug {
|
|
|
- println!("Set2 {:?} to {}", xy(pos), v);
|
|
|
- }
|
|
|
- this.set(xy(pos).0, xy(pos).1, v);
|
|
|
- found_something = true;
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // Change to 0..WIDTH ... Keep it simple.
|
|
|
- for i in 0..WIDTH {
|
|
|
- let mut g = for_column(i);
|
|
|
- // g.for_column(i, 1);
|
|
|
- group_process(self, &g);
|
|
|
- g = for_row(i);
|
|
|
- // g.for_row(1, i);
|
|
|
- group_process(self, &g);
|
|
|
- g = for_cell(i);
|
|
|
- // g.for_iter(i);
|
|
|
- group_process(self, &g);
|
|
|
- }
|
|
|
-
|
|
|
- if found_something {
|
|
|
- return found_something;
|
|
|
- }
|
|
|
-
|
|
|
- if debug {
|
|
|
- println!("Looking for pairs...");
|
|
|
- }
|
|
|
- // PAIR processing.
|
|
|
- for i in 0..WIDTH {
|
|
|
- g.for_iter(i);
|
|
|
-
|
|
|
- for gidx in 0..WIDTH - 1 {
|
|
|
- let gpos = g.0[gidx as usize];
|
|
|
- if self.possible[gpos as usize].count_set() == 2 {
|
|
|
- // Found a pair
|
|
|
- for fidx in gidx + 1..WIDTH {
|
|
|
- let fpos = g.0[fidx as usize];
|
|
|
- if self.possible[fpos as usize].count_set() == 2 {
|
|
|
- // Ok, there's another pair
|
|
|
- // if self.possible[gpos as usize].is_subset(&self.possible[fpos as usize])
|
|
|
- if self.possible[gpos as usize] == self.possible[fpos as usize] {
|
|
|
- // Ok, they have the same values!
|
|
|
- // Ok, remove the items in the pair from the cell.
|
|
|
- // Don't touch the gpos/fpos records. Keep those!
|
|
|
- let mut values: [u8; 2] = [0, 0];
|
|
|
- let mut vpos = 0;
|
|
|
- for z in self.possible[gpos as usize].iter() {
|
|
|
- values[vpos] = z;
|
|
|
- vpos += 1;
|
|
|
- }
|
|
|
-
|
|
|
- let mut pair_removed = false;
|
|
|
-
|
|
|
- // Check to see if anything was removed.
|
|
|
-
|
|
|
- for remove in 0..WIDTH {
|
|
|
- if (gidx == remove) || (fidx == remove) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- // Ok, these aren't the ones to save, so:
|
|
|
- let rpos = g.0[remove as usize];
|
|
|
- if self.possible[rpos as usize].get(values[0]) {
|
|
|
- self.possible[rpos as usize].set(values[0], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
-
|
|
|
- if self.possible[rpos as usize].get(values[1]) {
|
|
|
- self.possible[rpos as usize].set(values[1], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Check the x's and y's to see if we can also process a row/column too.
|
|
|
- if xy(gpos).0 == xy(fpos).0 {
|
|
|
- // Matching X - process column
|
|
|
- let column = xy(gpos).0;
|
|
|
-
|
|
|
- vpos = 0;
|
|
|
- for z in self.possible[gpos as usize].iter() {
|
|
|
- values[vpos] = z;
|
|
|
- vpos += 1;
|
|
|
- }
|
|
|
- for remove in 0..WIDTH {
|
|
|
- if (remove == xy(gpos).1) || (remove == xy(fpos).1) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if self.possible[pos(column, remove) as usize]
|
|
|
- .get(values[0])
|
|
|
- {
|
|
|
- self.possible[pos(column, remove) as usize]
|
|
|
- .set(values[0], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
-
|
|
|
- if self.possible[pos(column, remove) as usize]
|
|
|
- .get(values[1])
|
|
|
- {
|
|
|
- self.possible[pos(column, remove) as usize]
|
|
|
- .set(values[1], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if xy(gpos).1 == xy(fpos).1 {
|
|
|
- // Matching Y - process row
|
|
|
- let row = xy(gpos).1;
|
|
|
-
|
|
|
- vpos = 0;
|
|
|
- for z in self.possible[gpos as usize].iter() {
|
|
|
- values[vpos] = z;
|
|
|
- vpos += 1;
|
|
|
- }
|
|
|
- for remove in 0..WIDTH {
|
|
|
- if (remove == xy(gpos).0) || (remove == xy(fpos).0) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if self.possible[pos(remove, row) as usize].get(values[0]) {
|
|
|
- self.possible[pos(remove, row) as usize]
|
|
|
- .set(values[0], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
-
|
|
|
- if self.possible[pos(remove, row) as usize].get(values[1]) {
|
|
|
- self.possible[pos(remove, row) as usize]
|
|
|
- .set(values[1], false);
|
|
|
- found_something = true;
|
|
|
- pair_removed = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if pair_removed {
|
|
|
- if debug {
|
|
|
- println!(
|
|
|
- "Pair found! {} {}: {} {:?} and {} {:?} !",
|
|
|
- gidx,
|
|
|
- fidx,
|
|
|
- gpos,
|
|
|
- xy(gpos),
|
|
|
- fpos,
|
|
|
- xy(fpos)
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- found_something
|
|
|
- }
|
|
|
-}
|
|
|
+
|