|
@@ -2,10 +2,12 @@
|
|
|
use crate::bits::*;
|
|
|
use crate::group::*;
|
|
|
|
|
|
-use strum::IntoEnumIterator;
|
|
|
use std::string::String;
|
|
|
+use strum::IntoEnumIterator;
|
|
|
extern crate rand_chacha;
|
|
|
+use rand::distributions::{Distribution, Uniform};
|
|
|
use rand::seq::SliceRandom;
|
|
|
+
|
|
|
use rand_chacha::rand_core::SeedableRng;
|
|
|
use rand_chacha::ChaCha20Rng;
|
|
|
|
|
@@ -128,16 +130,17 @@ impl AnyBoard {
|
|
|
self.board[index]
|
|
|
}
|
|
|
|
|
|
- /// Load from ksudoku file
|
|
|
- /// - uses load_from_tld
|
|
|
+ /// Load from ksudoku format
|
|
|
+ /// - uses load_from_tld with specifics
|
|
|
pub fn load_ksudoku(&mut self, s: &str) -> Result<(), Box<dyn error::Error>> {
|
|
|
self.load_from_tld('b', '_', s)
|
|
|
}
|
|
|
|
|
|
+ /// Save to ksudoku format
|
|
|
pub fn save_ksudoku(&self) -> String {
|
|
|
self.save_to_tld('b', '_')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/// Load puzzle from string (top,left) going down.
|
|
|
pub fn load_from_tld(
|
|
|
&mut self,
|
|
@@ -746,6 +749,17 @@ impl AnySolver {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ pub fn get(&self, x: u8, y: u8) -> u8 {
|
|
|
+ debug_assert!(
|
|
|
+ x < self.board.width && y < self.board.width,
|
|
|
+ "Expected ({}, {}) < {}",
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ self.board.width
|
|
|
+ );
|
|
|
+ self.board.get(x, y)
|
|
|
+ }
|
|
|
+
|
|
|
/// Make completed board.
|
|
|
/// - uses fill_board (recursive)
|
|
|
pub fn make(&mut self) {
|
|
@@ -787,14 +801,6 @@ impl AnySolver {
|
|
|
// self.board.display();
|
|
|
// self.possible.display();
|
|
|
|
|
|
- // I can't validate board, it might be invalid!
|
|
|
- // Really!
|
|
|
- /*
|
|
|
- if ! self.validate_board() {
|
|
|
- panic!("Whaaaat?!");
|
|
|
- }
|
|
|
- */
|
|
|
-
|
|
|
if self.fill_board(rng) {
|
|
|
return true;
|
|
|
}
|
|
@@ -811,6 +817,50 @@ impl AnySolver {
|
|
|
true
|
|
|
}
|
|
|
|
|
|
+ /// Make a puzzle by removing items
|
|
|
+ /// - Verify it still has a single solution.
|
|
|
+ pub fn make_puzzle(&mut self) {
|
|
|
+ let mut rng = ChaCha20Rng::from_entropy();
|
|
|
+
|
|
|
+ if !self.board.complete() {
|
|
|
+ self.fill_board(&mut rng);
|
|
|
+ }
|
|
|
+ self.reset_possible();
|
|
|
+ while self.remove(&mut rng) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Remove an item
|
|
|
+ /// - Remove a number, test if still solvable.
|
|
|
+ /// - Returns true when removal was successful, and still solvable.
|
|
|
+ /// - Return false when it can't.
|
|
|
+ fn remove(&mut self, rng: &mut ChaCha20Rng) -> bool {
|
|
|
+ let puzrange = Uniform::new(0, self.board.width);
|
|
|
+ let mut x = 0;
|
|
|
+ let mut y = 0;
|
|
|
+ let mut value: u8 = 0;
|
|
|
+
|
|
|
+ while value == 0 {
|
|
|
+ x = puzrange.sample(rng);
|
|
|
+ y = puzrange.sample(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.solve_logic();
|
|
|
+
|
|
|
+ if puzcopy.board.complete() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Not solvable, restore and return false.
|
|
|
+ self.set(x, y, value);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
fn logic_pass1(&mut self) -> bool {
|
|
|
// Pass 1: Look for singles in the possible sets.
|
|
|
let mut pass1 = false;
|
|
@@ -914,24 +964,10 @@ impl AnySolver {
|
|
|
|
|
|
for gr in Groups::iter() {
|
|
|
for i in 0..width {
|
|
|
- let g = grp.group(gr, i);
|
|
|
+ let g = grp.group(gr, i);
|
|
|
group_process(self, g);
|
|
|
}
|
|
|
}
|
|
|
- /*
|
|
|
- // 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 {
|
|
|
// Ok, pass 2 found something.
|
|
@@ -962,16 +998,18 @@ impl AnySolver {
|
|
|
// Pass 1: Look for singles in the possible sets.
|
|
|
while result1 {
|
|
|
result1 = self.logic_pass1();
|
|
|
+ /*
|
|
|
if result1 {
|
|
|
println!("Pass1");
|
|
|
};
|
|
|
+ */
|
|
|
}
|
|
|
|
|
|
let mut result2 = true;
|
|
|
while result2 {
|
|
|
result2 = self.logic_pass2();
|
|
|
if result2 {
|
|
|
- println!("Pass2");
|
|
|
+ // println!("Pass2");
|
|
|
found_more = true;
|
|
|
};
|
|
|
}
|
|
@@ -1305,7 +1343,6 @@ impl AnySolver {
|
|
|
*/
|
|
|
}
|
|
|
self.board.complete()
|
|
|
-
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1567,51 +1604,3 @@ mod tests {
|
|
|
assert!(!solver.validate_board());
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-/*
|
|
|
- /// 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;
|
|
|
- }
|
|
|
-*/
|
|
|
-
|