Jelajahi Sumber

Updated main to use new ksudoku save/load.

If logic fails, show how far we got and try brute force.
Steve Thielemann 4 bulan lalu
induk
melakukan
24bfab46a2
5 mengubah file dengan 177 tambahan dan 77 penghapusan
  1. 26 11
      src/main.rs
  2. 118 66
      sudoku/src/ksudoku.rs
  3. 11 0
      sudoku/test_files/16-puzzle
  4. 11 0
      sudoku/test_files/25-puzzle
  5. 11 0
      sudoku/test_files/9-puzzle

+ 26 - 11
src/main.rs

@@ -3,7 +3,7 @@ use std::path::PathBuf;
 
 // https://docs.rs/clap/latest/clap/_tutorial/chapter_0/index.html
 
-use sudoku::ksudoku::{load_ksudoku, save_ksudoku};
+use sudoku::ksudoku::{load_ksudoku, save_ksudoku, Ksudoku};
 use sudoku::sudoku::*;
 
 fn main() {
@@ -46,9 +46,11 @@ fn main() {
         println!("Solution:");
         s.display();
         if let Some(filename) = args.get_one::<PathBuf>("file") {
-            let puz = p.save_to_tld('b', '_');
-            let sol = s.save_to_tld('b', '_');
-            let r = save_ksudoku(filename.to_path_buf(), &puz, &sol);
+            let mut ksudo = Ksudoku::default();
+            ksudo.puzzle = p.save_to_tld('b', '_');
+            ksudo.solution = s.save_to_tld('b', '_');
+            ksudo.order = 9; // Defaults currently 3x3
+            let r = save_ksudoku(filename.to_path_buf(), &ksudo);
             if r.is_ok() {
                 println!("Saved!");
             }
@@ -64,10 +66,19 @@ fn main() {
             println!("{:?}", result.unwrap_err());
             return;
         }
-        
+
         let puzzle = result.unwrap();
+
+        // verify it is known to us.
+
+        assert!(puzzle.specific_type == "Plain");
+        assert!(puzzle.puzzle_type == "sudoku");
+        if puzzle.order != 9 {
+            panic!("We only support (3x3) puzzles at this time.")
+        }
+
         // Ksudoku is stored TLD.
-        s.load_from_tld('b', '_', puzzle.as_str());
+        s.load_from_tld('b', '_', &puzzle.puzzle);
         s.display();
 
         if args.get_flag("brute") {
@@ -86,12 +97,16 @@ fn main() {
             }
 
             if !s.puzzle_complete() {
-                println!("Failed to solve by logic alone.  Trying bruteforce.");
+                if !debug {
+                    println!("This is as far we could get with logic alone:");
+                    s.display();
+                }
+                println!("Trying bruteforce.");
                 println!("Bruteforce solutions: {}", s.bruteforce_solver());
-            }
-
-            if !debug {
-                s.display();
+            } else {
+                if !debug {
+                    s.display();
+                }
             }
         }
     }

+ 118 - 66
sudoku/src/ksudoku.rs

@@ -1,13 +1,14 @@
 // Read a Ksudoku's XML save file.
 use std::collections::HashMap;
 use std::error;
-use std::fmt;
+// 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;
 
+/*
 #[derive(Debug, Clone)]
 struct UnsupportedGroup {
     message: String,
@@ -20,6 +21,7 @@ impl fmt::Display for UnsupportedGroup {
 }
 
 impl error::Error for UnsupportedGroup {}
+*/
 
 /*
 What I want from load_ksudoku:
@@ -40,28 +42,42 @@ have that as flexible as possible.
 save_ksudoku, would need to save those fields as well...
  */
 
+ #[derive(Debug)]
  pub struct Ksudoku {
+    /// puzzle string:
+    /// _ is blank, starts with 'b' (for 1), 'c' for 2.
     pub puzzle :String,
+    /// This contains the solution to the puzzle.
     pub solution: String,
+    /// "Plain"
     pub specific_type: String,
+    /// "sudoku"
     pub puzzle_type: String,
+    /// Puzzle width (9, 16, 25).
     pub order: u8,
  }
 
-pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn error::Error>> {
+ impl Default for Ksudoku {
+    fn default() -> Self {
+        Ksudoku {
+            puzzle: String::new(),
+            solution: String::new(),
+            specific_type: String::from("Plain"),
+            puzzle_type: String::from("sudoku"),
+            order: 0,
+        }
+    }
+}
+
+ /// Load ksudoku file, returning Ksudoku structure.
+ /// - It is up to the caller to determine if they can handle the types
+ ///   and size (order).
+pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<Ksudoku, Box<dyn error::Error>> {
     let fh = File::open(filename)?;
     let buffer = BufReader::new(fh);
     let parser = EventReader::new(buffer);
-
     let mut element_name: String = String::new();    
-
-    // I'm interested in values and (maybe) solution
-    let mut values: String = String::new();
-    let mut _solution = String::new();
-    let mut _specific_type = String::new();
-    let mut _puzzle_type = String::new();
-    let mut _order = String::new();
-    let mut _order_value: u8 = 0;
+    let mut details = Ksudoku::default();
 
     for e in parser {
         match e {
@@ -72,7 +88,6 @@ pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn erro
                 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"}
 
                     let mut attrs: HashMap<String, String> = HashMap::new();
@@ -83,43 +98,27 @@ pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn erro
 
                     let blank = String::new();
 
-                    _specific_type = attrs.get(&"specific-type".to_string()).unwrap_or(&blank).clone();
-                    _puzzle_type = attrs.get(&"type".to_string()).unwrap_or(&blank).clone();
-                    _order = attrs.get(&"order".to_string()).unwrap_or(&blank).clone();
+                    details.specific_type = attrs.get(&"specific-type".to_string()).unwrap_or(&blank).clone();
+                    details.puzzle_type = attrs.get(&"type".to_string()).unwrap_or(&blank).clone();
+                    let order  = attrs.get(&"order".to_string()).unwrap_or(&blank).clone();
 
-                    if !_order.is_empty() {
-                        let r = _order.parse::<u8>();
+                    if !order.is_empty() {
+                        let r = order.parse::<u8>();
                         if r.is_ok() {
-                            _order_value = r.unwrap();
+                            details.order = r.unwrap();
                         } else {
                             // Failed to convert order to u8...
                             return Err(Box::new(r.unwrap_err()));
                         }
                     }
-                    // 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),
-                        }));
-                    }
-                    // debug attrs
-                    // println!("{:?}", attrs);
                 }
             }
             Ok(XmlEvent::Characters(text)) => {
                 if element_name == "values" {
-                    values = text.clone();
+                    details.puzzle = text.clone();
                 }
                 if element_name == "solution" {
-                    _solution = text.clone();
+                    details.solution = text.clone();
                 }
             }
             Err(e) => {
@@ -129,13 +128,13 @@ pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<String, Box<dyn erro
         }
     }
 
-    Ok(values)
+    Ok(details)
 }
 
+/// Write out a ksudoku puzzle file
 pub fn save_ksudoku(
     filename: std::path::PathBuf,
-    puz: &String,
-    sol: &String,
+    data: &Ksudoku,
 ) -> Result<(), Box<dyn error::Error>> {
     let fh = File::create(filename)?;
     let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
@@ -149,10 +148,14 @@ pub fn save_ksudoku(
     writer.write(event)?;
     event = WrXmlEvent::start_element("puzzle").into();
     writer.write(event)?;
+
+    // Convert order to a string.
+    let order = data.order.to_string();
+
     event = WrXmlEvent::start_element("graph")
-        .attr("specific-type", "Plain")
-        .attr("type", "sudoku")
-        .attr("order", "9")
+        .attr("specific-type", &data.specific_type)
+        .attr("type", &data.puzzle_type)
+        .attr("order", &order )
         .into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
@@ -161,7 +164,7 @@ pub fn save_ksudoku(
     // values (puzzle)
     event = WrXmlEvent::start_element("values").into();
     writer.write(event)?;
-    event = WrXmlEvent::characters(puz).into();
+    event = WrXmlEvent::characters(&data.puzzle).into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
     writer.write(event)?;
@@ -169,7 +172,7 @@ pub fn save_ksudoku(
     // solution
     event = WrXmlEvent::start_element("solution").into();
     writer.write(event)?;
-    event = WrXmlEvent::characters(sol).into();
+    event = WrXmlEvent::characters(&data.solution).into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
     writer.write(event)?;
@@ -182,27 +185,76 @@ pub fn save_ksudoku(
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
     writer.write(event)?;
-    /*
-    let pStr: String = puzzle.save_to_tld(start_ch: 'b', blank: '_');
-    let solStr: solution.save_to_tld(start_ch: 'b', blank: '_');
-
-    let ksudoko = Ksudoku {
-        game: Game {
-            help: 0,
-            elapsed: 0,
-            puzzle: Puzzle {
-                graph: Graph {
-                    order: 9,
-                    game_type: String::from("sudoku"),
-                    specific_type: String::from("Plain"),
-                },
-                values: puz.to_string(),
-                solution: sol.to_string(),
-            },
-        },
-    };
-    let fh = File::create(filename)?;
-    let _res = to_writer(fh, &ksudoko)?;
-    */
+
     Ok(())
 }
+
+#[cfg(test)]
+
+mod tests {
+    use crate::ksudoku::*;
+    use crate::sudoku::*;
+    use std::path::PathBuf;
+
+    fn test_files_path() -> PathBuf {
+        
+    let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+    d.push("test_files");
+        d
+    }
+    
+    #[test]
+    fn load_puzzles() {
+        let mut testfile = test_files_path();
+        testfile.push("9-puzzle");
+
+        let mut result = load_ksudoku(testfile);
+        assert!(result.is_ok());
+        let result = result.unwrap();
+        assert!(result.puzzle_type == "sudoku");
+        assert!(result.specific_type == "Plain");
+
+        let size = (result.order as f64).sqrt() as u8;
+        let mut board = AnyBoard::new(size);
+        let mut load = board.load_from_tld('b', '_', &result.puzzle);
+        assert!(load.is_ok());
+        
+        // Ok, puzzle is loaded!
+        board.display();
+
+        let mut testfile = test_files_path();
+        testfile.push("16-puzzle");
+
+        let mut result = load_ksudoku(testfile);
+        assert!(result.is_ok());
+        let result = result.unwrap();
+        assert!(result.puzzle_type == "sudoku");
+        assert!(result.specific_type == "Plain");
+
+        let size = (result.order as f64).sqrt() as u8;
+        let mut board = AnyBoard::new(size);
+        let mut load = board.load_from_tld('b', '_', &result.puzzle);
+        assert!(load.is_ok());
+        
+        // Ok, puzzle is loaded!
+        board.display();
+        
+        let mut testfile = test_files_path();
+        testfile.push("25-puzzle");
+
+        let mut result = load_ksudoku(testfile);
+        assert!(result.is_ok());
+        let result = result.unwrap();
+        assert!(result.puzzle_type == "sudoku");
+        assert!(result.specific_type == "Plain");
+
+        let size = (result.order as f64).sqrt() as u8;
+        let mut board = AnyBoard::new(size);
+        let mut load = board.load_from_tld('b', '_', &result.puzzle);
+        assert!(load.is_ok());
+        
+        // Ok, puzzle is loaded!
+        board.display();
+
+    }
+}

+ 11 - 0
sudoku/test_files/16-puzzle

@@ -0,0 +1,11 @@
+<!DOCTYPE ksudoku>
+<ksudoku>
+ <game had-help="0" msecs-elapsed="21525">
+  <puzzle>
+   <graph type="sudoku" specific-type="Plain" order="16"/>
+   <values>_bo_j_m_f__dp__ge_h_pcfdo___q__n___qio______df___f__l___hpnm_i___obig_p_qhl__k_m_dq_cn______o_g_p_____bi_kc__jn______fo____gi______eb____jd______jk__ml_bn_____i_m_b______oq_nj_d_n__jck_m_fgbq___i_medp___n__b___dg______qjk___j__p___fgohl_d_qo__mq__g_d_p_le_</values>
+   <solution>ibonjkmhfqedpclgelhjpcfdogibqmknmpgqionbjlkcdfhekfcdlgeqhpnmbiojnobigdpjqhlefkcmldqkcnhmpfjioegbpgmfeqbidkcoljnhcejhkfolnbmgiqpdghpebiqnljdkcomfqjkcfmlobnghepdifmlbdpgecioqhnjkdinohjckempfgbqlhqilmedpkcfnjgbobndgolicmeqjkhfpjcepnbkfgohlmdiqokfmqhjgidbpnlec</solution>
+  </puzzle>
+  <history/>
+ </game>
+</ksudoku>

+ 11 - 0
sudoku/test_files/25-puzzle

@@ -0,0 +1,11 @@
+<!DOCTYPE ksudoku>
+<ksudoku>
+ <game had-help="0" msecs-elapsed="14569">
+  <puzzle>
+   <graph type="sudoku" specific-type="Plain" order="25"/>
+   <values>_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_</values>
+   <solution>vmykzglrwihpsxnbcjeftdquouidhnkxsofrbztjwpqlmvygecprlbontdhyqcmfeuvigxwjskzegfwcpzqvjikouystrndxhbmlqjtxsbuemcwgvdlzyohkpfirngnxirsbufpdvhzocelyqjmktwjzmpwoikrxulyqbthdvsfnecgcuotvqdhegnwismfkpzjblryxhfsldvyntmjxeckgiwrboupzqbkqyelwjcztrfgpnxumoisdhvztwfpmvbisyjuhcqdkxlegnornygmlwfzuekqxisrobchdvtjpixheurcogqbdwvtymnjpszlfkrdkvbjhxyloenpfisztgucwqmsocqjtkpndmzlrgeufwvhxybiohvntirfxwzsqedlbckympjgulwujkzpmdbfncyvxghqirtosedqrgmhotlnxubjipfeszkwcvyfbpzycevsughtkrjnmowlqxidxseciyjgqklmpowdrvutzbfnhtpirqemwjvcydbhkzxfngoulsyvzdxugcbhsfrlqowtienkmpjkcnohfslzretjwxmqgpuyivdbmlbsgxqiptvoknuhjydrcezwfwejufdnykopigmzvlsbcqrhxt</solution>
+  </puzzle>
+  <history/>
+ </game>
+</ksudoku>

+ 11 - 0
sudoku/test_files/9-puzzle

@@ -0,0 +1,11 @@
+<!DOCTYPE ksudoku>
+<ksudoku>
+ <game had-help="0" msecs-elapsed="7629">
+  <puzzle>
+   <graph specific-type="Plain" type="sudoku" order="9"/>
+   <values>__d___j___g__d__e_e__h_b__d__jc_id___b_____f___fb_dg__i__g_f__h_c__j__i___g___c__</values>
+   <solution>hfdeicjgbbgcfdjheiejihgbfcdghjcfidbedbejhgifccifbedghjidbgcfejhfchdjebigjegibhcdf</solution>
+  </puzzle>
+  <history/>
+ </game>
+</ksudoku>