Browse Source

Checking graph values in ksudoku puzzle save.

We now verify that it's a 3x3 (9) puzzle, and display the error.
Steve Thielemann 4 months ago
parent
commit
2c9969df8a
3 changed files with 97 additions and 17 deletions
  1. 7 1
      src/main.rs
  2. 44 11
      sudoku/src/ksudoku.rs
  3. 46 5
      sudoku/src/sudoku.rs

+ 7 - 1
src/main.rs

@@ -57,7 +57,13 @@ fn main() {
     }
 
     if let Some(filename) = args.get_one::<PathBuf>("file") {
-        let puzzle = load_ksudoku(filename.to_path_buf()).unwrap();
+        let result = load_ksudoku(filename.to_path_buf());
+        if result.is_err() {
+            println!("Failed to load file: {}", filename.display());
+            println!("{:?}", result.err());
+            return;
+        }
+        let puzzle = result.unwrap();
         // Ksudoku is stored TLD.
         s.load_from_tld('b', '_', puzzle.as_str());
         s.display();

+ 44 - 11
sudoku/src/ksudoku.rs

@@ -1,14 +1,27 @@
 // Read a Ksudoku's XML save file.
+use std::collections::HashMap;
+use std::error;
+use std::fmt;
 use std::fs::File;
 use std::io::BufReader;
 use xml::reader::{EventReader, XmlEvent};
 use xml::writer::XmlEvent as WrXmlEvent;
 use xml::EmitterConfig;
-use std::collections::HashMap;
 
-use std::error::Error;
+#[derive(Debug, Clone)]
+struct UnsupportedGroup {
+    message: String,
+}
+
+impl fmt::Display for UnsupportedGroup {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Unsupported group: {}", self.message)
+    }
+}
 
-pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn Error>> {
+impl error::Error for UnsupportedGroup {}
+
+pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn error::Error>> {
     let fh = File::open(filename)?;
     let buffer = BufReader::new(fh);
     let parser = EventReader::new(buffer);
@@ -18,21 +31,38 @@ pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn Erro
 
     for e in parser {
         match e {
-            Ok(XmlEvent::StartElement { name, attributes, .. }) => {
+            Ok(XmlEvent::StartElement {
+                name, attributes, ..
+            }) => {
                 element_name = name.local_name.clone();
                 if element_name == "graph" {
                     // Check the attributes here
                     // <graph specific-type="Plain" type="sudoku" order="9"/>
                     // Verify these are correct / not some other puzzle type!
+                    // {"specific-type": "Plain", "type": "sudoku", "order": "9"}
 
-                    // This might be overkill here, I just need to verify Plain/sudoku/9...
-                    
                     let mut attrs: HashMap<String, String> = HashMap::new();
+
                     for a in attributes {
                         attrs.insert(a.name.to_string(), a.value);
                     }
-                    // {"specific-type": "Plain", "type": "sudoku", "order": "9"}
 
+                    let blank = String::new();
+
+                    // Format in specific order here.
+                    let expected = format!(
+                        "{}-{}-{}",
+                        attrs.get(&"specific-type".to_string()).unwrap_or(&blank),
+                        attrs.get(&"type".to_string()).unwrap_or(&blank),
+                        attrs.get(&"order".to_string()).unwrap_or(&blank)
+                    );
+                    
+                    if expected != "Plain-sudoku-9" {
+                        // println!("Unknown ksudoku type! {}", expected);
+                        return Err(Box::new(UnsupportedGroup {
+                            message: format!("Unsupported Ksudoku game type: {}", expected),
+                        }));
+                    }
                     println!("{:?}", attrs);
                 }
             }
@@ -55,7 +85,7 @@ pub fn save_ksudoku(
     filename: std::path::PathBuf,
     puz: &String,
     sol: &String,
-) -> Result<(), Box<dyn Error>> {
+) -> Result<(), Box<dyn error::Error>> {
     let fh = File::create(filename)?;
     let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
 
@@ -68,8 +98,11 @@ pub fn save_ksudoku(
     writer.write(event)?;
     event = WrXmlEvent::start_element("puzzle").into();
     writer.write(event)?;
-    event = WrXmlEvent::start_element("graph").attr("specific-type", "Plain")
-    .attr("type", "sudoku").attr("order", "9").into();
+    event = WrXmlEvent::start_element("graph")
+        .attr("specific-type", "Plain")
+        .attr("type", "sudoku")
+        .attr("order", "9")
+        .into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
     writer.write(event)?;
@@ -91,7 +124,7 @@ pub fn save_ksudoku(
     writer.write(event)?;
 
     // Apparently, the events are consumed...
-    
+
     event = WrXmlEvent::end_element().into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();

+ 46 - 5
sudoku/src/sudoku.rs

@@ -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;