Browse Source

Working sudoku.make() that makes a complete puzzle.

Steve Thielemann 9 months ago
parent
commit
029aca2671
3 changed files with 138 additions and 57 deletions
  1. 24 11
      src/main.rs
  2. 3 0
      sudoku/Cargo.toml
  3. 111 46
      sudoku/src/sudoku.rs

+ 24 - 11
src/main.rs

@@ -7,24 +7,37 @@ use sudoku::sudoku::*;
 
 #[derive(Parser)]
 struct Cli {
-    /// The path to the file to read
-    filename: std::path::PathBuf,
+    /// The path to the file to
+    #[arg(short = 'f', long = "file", value_name = "FILENAME")]
+    filename: Option<std::path::PathBuf>,
+
+    /// Make a sudoku
+    #[arg(short = 'm', long = "make", action = clap::ArgAction::SetTrue)]
+    make: Option<bool>,
 }
 
 fn main() {
     let args = Cli::parse();
-
-    let puzzle = load_ksudoku(args.filename).unwrap();
-
     let mut s = Sudoku::new();
-    // Ksudoku is stored TLD.
-    s.load_from_tld('b', '_', puzzle.as_str());
-    s.display();
-    s.display_possible();
 
-    while s.solve() {
-        println!("Try it again...");
+    if args.make.is_some() {
+        s.make();
         s.display();
         s.display_possible();
+    } else {
+        if args.filename.is_some() {
+            let puzzle = load_ksudoku(args.filename.unwrap()).unwrap();
+
+            // Ksudoku is stored TLD.
+            s.load_from_tld('b', '_', puzzle.as_str());
+            s.display();
+            s.display_possible();
+
+            while s.solve() {
+                println!("Try it again...");
+                s.display();
+                s.display_possible();
+            }
+        }
     }
 }

+ 3 - 0
sudoku/Cargo.toml

@@ -10,6 +10,9 @@ serde = "1.0.196"
 serde-xml-rs = "0.6.0"
 serde_derive = "1.0.196"
 bit_field = "0.10.2"
+rand = "0.8.5"
+rand_chacha = "0.3.1"
+rand_core = { version = "0.6.4", features = ["getrandom"] }
 
 [lib]
 name = "sudoku"

+ 111 - 46
sudoku/src/sudoku.rs

@@ -1,7 +1,7 @@
 // pub mod group;
 use crate::group::*;
 
-use std::collections::HashSet;
+// use std::collections::HashSet;
 use std::string::String;
 
 /// Width of the sudoku board.
@@ -12,11 +12,17 @@ const MAX_SIZE: u8 = 81;
 // Use bitfields instead of HashSets.
 use bit_field::BitField;
 
+extern crate rand_chacha;
+use rand::prelude::*;
+use rand_chacha::rand_core::SeedableRng;
+use rand_chacha::ChaCha20Rng;
+use rand::seq::SliceRandom;
+
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub struct Possible(u16);
 
 pub const fn set_bits(bits: u8) -> u16 {
-    (1 << (bits )) - 1
+    (1 << (bits)) - 1
 }
 
 impl Possible {
@@ -26,12 +32,12 @@ impl Possible {
 
     pub fn set(&mut self, bit: u8, value: bool) {
         // print!("{} set {}={}", self.0, bit, value);
-        self.0.set_bit((bit-1) as usize, value);
+        self.0.set_bit((bit - 1) as usize, value);
         // println!("{}", self.0);
     }
 
     pub fn get(&self, bit: u8) -> bool {
-        self.0.get_bit((bit-1) as usize)
+        self.0.get_bit((bit - 1) as usize)
     }
 
     pub fn set_bits(&mut self, bits: u8) {
@@ -162,31 +168,15 @@ impl Sudoku {
 
         let s = Sudoku {
             board: [0; MAX_SIZE as usize],
-            // possible: [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9)),
             possible: [initial; MAX_SIZE as usize],
-            // possible: [HashSet::from_iter(1..=9); MAX_SIZE as usize],
-            // possible: [[0; SIZE as usize]; MAX_SIZE as usize],
-            // possible: [(0..MAX_SIZE).map( |_| (1..=9).collect())],
-            // possible: [(1..=9).map(|_| HashSet::new()).collect(); MAX_SIZE as usize],
         };
         s
     }
 
     pub fn clear(&mut self) {
         let initial = Possible(set_bits(9));
-
-        for x in 0..MAX_SIZE {
-            self.board[x as usize] = 0;
-            self.possible[x as usize] = initial;
-            // self.possible = [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9));
-            /*
-            self.possible[x as usize].clear();
-            for i in 1..=9 {
-                self.possible[x as usize].insert(i);
-            }
-            */
-            // self.possible[x as usize] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
-        }
+        self.board = [0; MAX_SIZE as usize];
+        self.possible = [initial; MAX_SIZE as usize];
     }
 
     /// Load puzzle from a string.
@@ -257,6 +247,7 @@ impl Sudoku {
         }
         result
     }
+
     pub fn set(&mut self, x: u8, y: u8, value: u8) {
         self.board[pos(x, y) as usize] = value;
         // Ok, update the possible
@@ -284,30 +275,6 @@ impl Sudoku {
         self.possible[pos(x, y) as usize].clear();
     }
 
-    pub fn set2(&mut self, x: u8, y: u8, value: u8) {
-        self.board[pos(x, y) as usize] = value;
-        // Ok, update the possible
-        let mut g = Group::new();
-        g.for_row(x, y);
-        for g in g.0 {
-            // remove value from these sets.
-            self.possible[g as usize].set(value, false);
-        }
-
-        g.for_column(x, y);
-        for g in g.0 {
-            // remove value from these sets.
-            self.possible[g as usize].set(value, false);
-        }
-
-        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[pos(x, y) as usize].clear();
-    }
-
     pub fn display(&self) {
         println!("╔═══╦═══╦═══╗");
         for y in 0..WIDTH {
@@ -364,6 +331,104 @@ impl Sudoku {
         }
     }
 
+    pub fn puzzle_complete(&self) -> bool {
+        for i in 0..MAX_SIZE {
+            if self.board[i as usize] == 0 {
+                return false;
+            }
+        }
+        true
+    }
+
+    pub fn make(&mut self) {
+        let mut rng = ChaCha20Rng::from_entropy();
+
+        self.fill_board(&mut rng);
+    }
+
+    pub fn fill_board(&mut self, rng : &mut ChaCha20Rng ) -> bool {
+        let backup = Sudoku{ board: self.board,
+            possible: self.possible 
+        };
+
+        for idx in 0..MAX_SIZE {
+            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;
+    }
+
+    pub 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();
+                println!("Set({},{})={}", x, y, value);
+                self.set(x, y, value);
+                }
+            }
+        }
+    }
+
     pub fn solve(&mut self) -> bool {
         // Pass 1: Look for singles in the possible sets.
         let mut found_something = false;