|
@@ -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();
|
|
|
+
|
|
|
+ }
|
|
|
+}
|