ksudoku.rs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Read a Ksudoku's XML save file.
  2. use std::collections::HashMap;
  3. use std::error;
  4. // use std::fmt;
  5. use std::fs::File;
  6. use std::io::BufReader;
  7. use xml::reader::{EventReader, XmlEvent};
  8. use xml::writer::XmlEvent as WrXmlEvent;
  9. use xml::EmitterConfig;
  10. /*
  11. What I want from load_ksudoku:
  12. - Puzzle string
  13. - Solution string (so I can validate it/check my code)
  14. - specific-type & type (combined as a string is ok - I think)
  15. - order (as a number)
  16. I get values (the puzzle), I also need solution.
  17. Having order will allow me to use AnyBoard::new(order) and load it up.
  18. */
  19. #[derive(Debug)]
  20. pub struct Ksudoku {
  21. /// puzzle string:
  22. /// _ is blank, starts with 'b' (for 1), 'c' for 2.
  23. pub puzzle: String,
  24. /// This contains the solution to the puzzle.
  25. pub solution: String,
  26. /// "Plain"
  27. pub specific_type: String,
  28. /// "sudoku"
  29. pub puzzle_type: String,
  30. /// Puzzle width (9, 16, 25).
  31. pub order: u8,
  32. }
  33. impl Default for Ksudoku {
  34. fn default() -> Self {
  35. Ksudoku {
  36. puzzle: String::new(),
  37. solution: String::new(),
  38. specific_type: String::from("Plain"),
  39. puzzle_type: String::from("sudoku"),
  40. order: 0,
  41. }
  42. }
  43. }
  44. /// Load ksudoku file, returning Ksudoku structure.
  45. /// - It is up to the caller to determine if they can handle the types
  46. /// and size (order).
  47. pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<Ksudoku, Box<dyn error::Error>> {
  48. let fh = File::open(filename)?;
  49. let buffer = BufReader::new(fh);
  50. let parser = EventReader::new(buffer);
  51. let mut element_name: String = String::new();
  52. let mut details = Ksudoku::default();
  53. for e in parser {
  54. match e {
  55. Ok(XmlEvent::StartElement {
  56. name, attributes, ..
  57. }) => {
  58. element_name = name.local_name.clone();
  59. if element_name == "graph" {
  60. // Check the attributes here
  61. // <graph specific-type="Plain" type="sudoku" order="9"/>
  62. // {"specific-type": "Plain", "type": "sudoku", "order": "9"}
  63. let mut attrs: HashMap<String, String> = HashMap::new();
  64. for a in attributes {
  65. attrs.insert(a.name.to_string(), a.value);
  66. }
  67. let blank = String::new();
  68. details.specific_type = attrs
  69. .get(&"specific-type".to_string())
  70. .unwrap_or(&blank)
  71. .clone();
  72. details.puzzle_type = attrs.get(&"type".to_string()).unwrap_or(&blank).clone();
  73. let order = attrs.get(&"order".to_string()).unwrap_or(&blank).clone();
  74. if !order.is_empty() {
  75. let r = order.parse::<u8>();
  76. if r.is_ok() {
  77. details.order = r.unwrap();
  78. } else {
  79. // Failed to convert order to u8...
  80. return Err(Box::new(r.unwrap_err()));
  81. }
  82. }
  83. }
  84. }
  85. Ok(XmlEvent::Characters(text)) => {
  86. if element_name == "values" {
  87. details.puzzle = text.clone();
  88. }
  89. if element_name == "solution" {
  90. details.solution = text.clone();
  91. }
  92. }
  93. Err(e) => {
  94. return Err(Box::new(e));
  95. }
  96. _ => {}
  97. }
  98. }
  99. Ok(details)
  100. }
  101. /// Write out a ksudoku puzzle file
  102. pub fn save_ksudoku(
  103. filename: std::path::PathBuf,
  104. data: &Ksudoku,
  105. ) -> Result<(), Box<dyn error::Error>> {
  106. let fh = File::create(filename)?;
  107. let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
  108. let mut event: WrXmlEvent = WrXmlEvent::start_element("ksudoku").into();
  109. writer.write(event)?;
  110. event = WrXmlEvent::start_element("game")
  111. .attr("had-help", "0")
  112. .attr("msecs-elapsed", "0")
  113. .into();
  114. writer.write(event)?;
  115. event = WrXmlEvent::start_element("puzzle").into();
  116. writer.write(event)?;
  117. // Convert order to a string.
  118. let order = data.order.to_string();
  119. event = WrXmlEvent::start_element("graph")
  120. .attr("specific-type", &data.specific_type)
  121. .attr("type", &data.puzzle_type)
  122. .attr("order", &order)
  123. .into();
  124. writer.write(event)?;
  125. event = WrXmlEvent::end_element().into();
  126. writer.write(event)?;
  127. // values (puzzle)
  128. event = WrXmlEvent::start_element("values").into();
  129. writer.write(event)?;
  130. event = WrXmlEvent::characters(&data.puzzle).into();
  131. writer.write(event)?;
  132. event = WrXmlEvent::end_element().into();
  133. writer.write(event)?;
  134. // solution
  135. event = WrXmlEvent::start_element("solution").into();
  136. writer.write(event)?;
  137. event = WrXmlEvent::characters(&data.solution).into();
  138. writer.write(event)?;
  139. event = WrXmlEvent::end_element().into();
  140. writer.write(event)?;
  141. // Apparently, the events are consumed...
  142. event = WrXmlEvent::end_element().into();
  143. writer.write(event)?;
  144. event = WrXmlEvent::end_element().into();
  145. writer.write(event)?;
  146. event = WrXmlEvent::end_element().into();
  147. writer.write(event)?;
  148. Ok(())
  149. }
  150. #[cfg(test)]
  151. mod tests {
  152. use crate::ksudoku::*;
  153. use crate::sudoku::*;
  154. use std::path::PathBuf;
  155. fn test_files_path() -> PathBuf {
  156. let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
  157. d.push("test_files");
  158. d
  159. }
  160. #[test]
  161. fn load_puzzles() {
  162. let mut testfile = test_files_path();
  163. testfile.push("missing");
  164. let result = load_ksudoku(testfile);
  165. assert!(result.is_err());
  166. let mut testfile = test_files_path();
  167. testfile.push("9-puzzle");
  168. let result = load_ksudoku(testfile);
  169. assert!(result.is_ok());
  170. let result = result.unwrap();
  171. assert!(result.puzzle_type == "sudoku");
  172. assert!(result.specific_type == "Plain");
  173. let size = (result.order as f64).sqrt() as u8;
  174. let mut board = AnyBoard::new(size);
  175. let load = board.load_from_tld('b', '_', &result.puzzle);
  176. assert!(load.is_ok());
  177. // Ok, puzzle is loaded!
  178. let strings = board.to_strings();
  179. assert_eq!(
  180. strings,
  181. vec![
  182. " 4 8 ",
  183. " 6 1 2 ",
  184. "3 9 5 6",
  185. " 72 16 ",
  186. " 3 9 ",
  187. " 18 35 ",
  188. "9 3 6 2",
  189. " 4 5 8 ",
  190. " 3 7 ",
  191. ]
  192. );
  193. let mut testfile = test_files_path();
  194. testfile.push("16-puzzle");
  195. let result = load_ksudoku(testfile);
  196. assert!(result.is_ok());
  197. let result = result.unwrap();
  198. assert!(result.puzzle_type == "sudoku");
  199. assert!(result.specific_type == "Plain");
  200. let size = (result.order as f64).sqrt() as u8;
  201. let mut board = AnyBoard::new(size);
  202. let load = board.load_from_tld('b', '_', &result.puzzle);
  203. assert!(load.is_ok());
  204. // Ok, puzzle is loaded!
  205. let strings: Vec<String> = board.to_strings();
  206. assert_eq!(
  207. strings,
  208. vec![
  209. " D O C IN",
  210. "A ENC IL ",
  211. "NG AP J MHC ",
  212. " P H D A FOL",
  213. "IOHKFB A L P",
  214. " BN M E L ID ",
  215. "LE O AN K BC ",
  216. " C H JO EF",
  217. "EN GP A F ",
  218. " OG J IM L NC",
  219. " MK B C N PG ",
  220. "C L F PEMIKO",
  221. "OPC N H F J ",
  222. " EHJ I MA CK",
  223. " FM IPA D",
  224. "FM L H P "
  225. ]
  226. );
  227. // board.display();
  228. let mut testfile = test_files_path();
  229. testfile.push("25-puzzle");
  230. let result = load_ksudoku(testfile);
  231. assert!(result.is_ok());
  232. let result = result.unwrap();
  233. assert!(result.puzzle_type == "sudoku");
  234. assert!(result.specific_type == "Plain");
  235. let size = (result.order as f64).sqrt() as u8;
  236. let mut board = AnyBoard::new(size);
  237. let load = board.load_from_tld('b', '_', &result.puzzle);
  238. assert!(load.is_ok());
  239. // Ok, puzzle is loaded!
  240. // board.display();
  241. let strings: Vec<String> = board.to_strings();
  242. assert_eq!(
  243. strings,
  244. vec![
  245. " T DPF Y H R WSX L ",
  246. "L QF J X C G UB D",
  247. " CK S LNRP G UTQO H MA ",
  248. "JG H S XELDUPM F B RT",
  249. "Y N RQ UCDOK AISJL HP G E",
  250. "F OA N UK VQI HY B DT C",
  251. " S A C VUE GJQ N I R ",
  252. " CPD JGMIA OELSU VBK ",
  253. " G LE D BHT XMW K PI Y ",
  254. " EXIBOWFLY VAMTJUGQS ",
  255. "G HV TMI EWF BR O",
  256. " A JFUK W P D M GLXE N ",
  257. "R LN G O QI F",
  258. " S TCYP B H O X JNAK M ",
  259. "M XK ALJ UHQ GP Y",
  260. " VTRYBSEFM KWOICJNLG ",
  261. " U XD J WCN RTA E QY P ",
  262. " HQN COVTJ EBGDL WSF ",
  263. " F X Y LWB SVJ R T O ",
  264. "E CJ R AN GOF XH V MD B",
  265. "S V OI ANHDC TGLQJ YF X P",
  266. "CX L K RFUYBWO V A DQ",
  267. " FR H DQOC K INBW T UY ",
  268. "T JL G I P F OC W",
  269. " B KMV Q J H GRI E "
  270. ]
  271. );
  272. }
  273. }