123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- // 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;
- /*
- What I want from load_ksudoku:
- - Puzzle string
- - Solution string (so I can validate it/check my code)
- - specific-type & type (combined as a string is ok - I think)
- - order (as a number)
- I get values (the puzzle), I also need solution.
- Having order will allow me to use AnyBoard::new(order) and load it up.
- */
- #[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,
- }
- 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();
- let mut details = Ksudoku::default();
- for e in parser {
- match e {
- 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"/>
- // {"specific-type": "Plain", "type": "sudoku", "order": "9"}
- let mut attrs: HashMap<String, String> = HashMap::new();
- for a in attributes {
- attrs.insert(a.name.to_string(), a.value);
- }
- let blank = String::new();
- 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 r.is_ok() {
- details.order = r.unwrap();
- } else {
- // Failed to convert order to u8...
- return Err(Box::new(r.unwrap_err()));
- }
- }
- }
- }
- Ok(XmlEvent::Characters(text)) => {
- if element_name == "values" {
- details.puzzle = text.clone();
- }
- if element_name == "solution" {
- details.solution = text.clone();
- }
- }
- Err(e) => {
- return Err(Box::new(e));
- }
- _ => {}
- }
- }
- Ok(details)
- }
- /// Write out a ksudoku puzzle file
- pub fn save_ksudoku(
- filename: std::path::PathBuf,
- data: &Ksudoku,
- ) -> Result<(), Box<dyn error::Error>> {
- let fh = File::create(filename)?;
- let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
- let mut event: WrXmlEvent = WrXmlEvent::start_element("ksudoku").into();
- writer.write(event)?;
- event = WrXmlEvent::start_element("game")
- .attr("had-help", "0")
- .attr("msecs-elapsed", "0")
- .into();
- 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", &data.specific_type)
- .attr("type", &data.puzzle_type)
- .attr("order", &order)
- .into();
- writer.write(event)?;
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- // values (puzzle)
- event = WrXmlEvent::start_element("values").into();
- writer.write(event)?;
- event = WrXmlEvent::characters(&data.puzzle).into();
- writer.write(event)?;
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- // solution
- event = WrXmlEvent::start_element("solution").into();
- writer.write(event)?;
- event = WrXmlEvent::characters(&data.solution).into();
- writer.write(event)?;
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- // Apparently, the events are consumed...
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- event = WrXmlEvent::end_element().into();
- writer.write(event)?;
- 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("missing");
- let result = load_ksudoku(testfile);
- assert!(result.is_err());
- let mut testfile = test_files_path();
- testfile.push("9-puzzle");
- let 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 load = board.load_from_tld('b', '_', &result.puzzle);
- assert!(load.is_ok());
- // Ok, puzzle is loaded!
- let strings = board.to_strings();
- assert_eq!(
- strings,
- vec![
- " 4 8 ",
- " 6 1 2 ",
- "3 9 5 6",
- " 72 16 ",
- " 3 9 ",
- " 18 35 ",
- "9 3 6 2",
- " 4 5 8 ",
- " 3 7 ",
- ]
- );
- let mut testfile = test_files_path();
- testfile.push("16-puzzle");
- let 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 load = board.load_from_tld('b', '_', &result.puzzle);
- assert!(load.is_ok());
- // Ok, puzzle is loaded!
- let strings: Vec<String> = board.to_strings();
- assert_eq!(
- strings,
- vec![
- " D O C IN",
- "A ENC IL ",
- "NG AP J MHC ",
- " P H D A FOL",
- "IOHKFB A L P",
- " BN M E L ID ",
- "LE O AN K BC ",
- " C H JO EF",
- "EN GP A F ",
- " OG J IM L NC",
- " MK B C N PG ",
- "C L F PEMIKO",
- "OPC N H F J ",
- " EHJ I MA CK",
- " FM IPA D",
- "FM L H P "
- ]
- );
- // board.display();
- let mut testfile = test_files_path();
- testfile.push("25-puzzle");
- let 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 load = board.load_from_tld('b', '_', &result.puzzle);
- assert!(load.is_ok());
- // Ok, puzzle is loaded!
- // board.display();
- let strings: Vec<String> = board.to_strings();
- assert_eq!(
- strings,
- vec![
- " T DPF Y H R WSX L ",
- "L QF J X C G UB D",
- " CK S LNRP G UTQO H MA ",
- "JG H S XELDUPM F B RT",
- "Y N RQ UCDOK AISJL HP G E",
- "F OA N UK VQI HY B DT C",
- " S A C VUE GJQ N I R ",
- " CPD JGMIA OELSU VBK ",
- " G LE D BHT XMW K PI Y ",
- " EXIBOWFLY VAMTJUGQS ",
- "G HV TMI EWF BR O",
- " A JFUK W P D M GLXE N ",
- "R LN G O QI F",
- " S TCYP B H O X JNAK M ",
- "M XK ALJ UHQ GP Y",
- " VTRYBSEFM KWOICJNLG ",
- " U XD J WCN RTA E QY P ",
- " HQN COVTJ EBGDL WSF ",
- " F X Y LWB SVJ R T O ",
- "E CJ R AN GOF XH V MD B",
- "S V OI ANHDC TGLQJ YF X P",
- "CX L K RFUYBWO V A DQ",
- " FR H DQOC K INBW T UY ",
- "T JL G I P F OC W",
- " B KMV Q J H GRI E "
- ]
- );
- }
- }
|