1use crate::core::{Board, Solution, SolvePath, SolveStep, TechniqueFlags};
7use std::fmt;
8
9impl fmt::Display for Solution {
11 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12 writeln!(f, "{}", self.board)?;
13 write!(f, "\n{}", self.solve_path)?;
14 Ok(())
15 }
16}
17
18impl fmt::Display for Board {
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 writeln!(f, "{}", format_grid(self).join("\n"))?;
22 write!(f, "Line format: {}", format_line(self))?;
23 Ok(())
24 }
25}
26
27impl fmt::Display for TechniqueFlags {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 if self.is_empty() {
31 return write!(f, "None");
32 }
33 if self.is_all() {
34 return write!(f, "All Techniques");
35 }
36
37 let mut techniques = Vec::new();
38
39 if self.contains(TechniqueFlags::NAKED_SINGLES) {
40 techniques.push("Naked Singles");
41 }
42 if self.contains(TechniqueFlags::HIDDEN_SINGLES) {
43 techniques.push("Hidden Singles");
44 }
45 if self.contains(TechniqueFlags::NAKED_PAIRS) {
46 techniques.push("Naked Pairs");
47 }
48 if self.contains(TechniqueFlags::HIDDEN_PAIRS) {
49 techniques.push("Hidden Pairs");
50 }
51 if self.contains(TechniqueFlags::LOCKED_CANDIDATES) {
52 techniques.push("Locked Candidates");
53 }
54 if self.contains(TechniqueFlags::XWING) {
55 techniques.push("X-Wing");
56 }
57
58 write!(f, "{}", techniques.join(", "))
59 }
60}
61
62impl fmt::Display for SolvePath {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 let formatted_lines = format_solve_path(self, 5);
66 write!(f, "{}", formatted_lines.join("\n"))
67 }
68}
69
70impl fmt::Display for SolveStep {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 SolveStep::Placement {
75 row,
76 col,
77 value,
78 flags,
79 } => {
80 write!(
81 f,
82 "Value {} is placed on R{}C{} by {}",
83 value, row, col, flags
84 )
85 }
86 SolveStep::CandidateElimination {
87 row,
88 col,
89 value,
90 flags,
91 } => {
92 write!(
93 f,
94 "Value {} is eliminated from R{}C{} by {}",
95 value, row, col, flags
96 )
97 }
98 }
99 }
100}
101
102pub(crate) fn format_grid(board: &Board) -> Vec<String> {
108 let mut grid = Vec::new();
109 let horizontal_line = "+-------+-------+-------+";
110
111 grid.push(horizontal_line.to_string()); for (r, row) in board.cells.iter().enumerate().take(9) {
114 let mut line = String::from("|"); for (c, &cell) in row.iter().enumerate().take(9) {
116 match cell {
117 0 => line.push_str(" ."), n => line.push_str(&format!(" {}", n)), }
120 if (c + 1) % 3 == 0 {
121 line.push_str(" |"); }
123 }
124 grid.push(line); if (r + 1) % 3 == 0 {
127 grid.push(horizontal_line.to_string()); }
129 }
130
131 grid
132}
133
134pub(crate) fn format_line(board: &Board) -> String {
139 board
140 .cells
141 .iter()
142 .flatten()
143 .map(|&n| (n + b'0') as char)
144 .collect()
145}
146
147pub(crate) fn format_solve_path(solve_path: &SolvePath, chunk_size: usize) -> Vec<String> {
153 if solve_path.steps.is_empty() {
154 return vec!["(No moves recorded)".to_string()];
155 }
156
157 let mut result = Vec::new();
158 let mut current_technique = None;
159 let mut current_moves = Vec::new();
160
161 for step in &solve_path.steps {
162 let (r, c, val, flags, action_code) = match step {
164 SolveStep::Placement {
165 row,
166 col,
167 value,
168 flags,
169 } => (*row, *col, *value, *flags, step.code()),
170 SolveStep::CandidateElimination {
171 row,
172 col,
173 value,
174 flags,
175 } => (*row, *col, *value, *flags, step.code()),
176 };
177
178 let technique_name = format!("{}", flags);
179
180 if current_technique.as_ref() != Some(&technique_name) {
181 if let Some(tech) = current_technique {
183 result.push(format!("{}:", tech));
184 for chunk in current_moves.chunks(chunk_size) {
186 result.push(format!(" {}", chunk.join(" ")));
187 }
188 current_moves.clear();
189 }
190 current_technique = Some(technique_name);
191 }
192
193 current_moves.push(format!("R{}C{}={},A={}", r + 1, c + 1, val, action_code));
194 }
195
196 if let Some(tech) = current_technique {
198 result.push(format!("{}:", tech));
199 for chunk in current_moves.chunks(chunk_size) {
200 result.push(format!(" {}", chunk.join(" ")));
201 }
202 }
203
204 result
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::core::{SolvePath, SolveStep, TechniqueFlags};
211
212 #[test]
213 fn test_format_grid() {
214 let board = Board::new([
215 [5, 3, 0, 6, 7, 8, 9, 1, 2],
216 [6, 7, 2, 1, 9, 5, 3, 4, 8],
217 [1, 9, 8, 3, 4, 2, 5, 6, 7],
218 [8, 5, 9, 7, 6, 1, 4, 2, 3],
219 [4, 2, 6, 8, 5, 3, 7, 9, 1],
220 [7, 1, 3, 9, 2, 4, 8, 5, 6],
221 [9, 6, 1, 5, 3, 7, 2, 8, 4],
222 [2, 8, 7, 4, 1, 9, 6, 3, 5],
223 [3, 4, 5, 2, 8, 6, 1, 7, 9],
224 ]);
225
226 let expected = vec![
227 "+-------+-------+-------+",
228 "| 5 3 . | 6 7 8 | 9 1 2 |",
229 "| 6 7 2 | 1 9 5 | 3 4 8 |",
230 "| 1 9 8 | 3 4 2 | 5 6 7 |",
231 "+-------+-------+-------+",
232 "| 8 5 9 | 7 6 1 | 4 2 3 |",
233 "| 4 2 6 | 8 5 3 | 7 9 1 |",
234 "| 7 1 3 | 9 2 4 | 8 5 6 |",
235 "+-------+-------+-------+",
236 "| 9 6 1 | 5 3 7 | 2 8 4 |",
237 "| 2 8 7 | 4 1 9 | 6 3 5 |",
238 "| 3 4 5 | 2 8 6 | 1 7 9 |",
239 "+-------+-------+-------+",
240 ];
241
242 assert_eq!(expected, format_grid(&board));
243 }
244
245 #[test]
246 fn test_format_line() {
247 let board = Board::new([
248 [5, 3, 0, 6, 7, 8, 9, 1, 2],
249 [6, 7, 2, 1, 9, 5, 3, 4, 8],
250 [1, 9, 8, 3, 4, 2, 5, 6, 7],
251 [8, 5, 9, 7, 6, 1, 4, 2, 3],
252 [4, 2, 6, 8, 5, 3, 7, 9, 1],
253 [7, 1, 3, 9, 2, 4, 8, 5, 6],
254 [9, 6, 1, 5, 3, 7, 2, 8, 4],
255 [2, 8, 7, 4, 1, 9, 6, 3, 5],
256 [3, 4, 5, 2, 8, 6, 1, 7, 9],
257 ]);
258
259 let expected =
260 "530678912672195348198342567859761423426853791713924856961537284287419635345286179";
261 assert_eq!(expected, format_line(&board));
262 }
263
264 #[test]
265 fn test_format_grid_empty_board() {
266 let board = Board::default();
267
268 let expected = vec![
269 "+-------+-------+-------+",
270 "| . . . | . . . | . . . |",
271 "| . . . | . . . | . . . |",
272 "| . . . | . . . | . . . |",
273 "+-------+-------+-------+",
274 "| . . . | . . . | . . . |",
275 "| . . . | . . . | . . . |",
276 "| . . . | . . . | . . . |",
277 "+-------+-------+-------+",
278 "| . . . | . . . | . . . |",
279 "| . . . | . . . | . . . |",
280 "| . . . | . . . | . . . |",
281 "+-------+-------+-------+",
282 ];
283
284 assert_eq!(expected, format_grid(&board));
285 }
286
287 #[test]
288 fn test_format_line_empty_board() {
289 let board = Board::default();
290 let expected =
291 "000000000000000000000000000000000000000000000000000000000000000000000000000000000";
292 assert_eq!(expected, format_line(&board));
293 }
294
295 #[test]
296 fn test_display_empty_mask() {
297 let mask = TechniqueFlags::empty();
298 assert_eq!(format!("{}", mask), "None");
299 }
300
301 #[test]
302 fn test_display_single_technique() {
303 let mask = TechniqueFlags::NAKED_SINGLES;
304 assert_eq!(format!("{}", mask), "Naked Singles");
305
306 let mask = TechniqueFlags::XWING;
307 assert_eq!(format!("{}", mask), "X-Wing");
308 }
309
310 #[test]
311 fn test_display_multiple_techniques() {
312 let mask = TechniqueFlags::EASY;
313 assert_eq!(format!("{}", mask), "Naked Singles, Hidden Singles");
314
315 let mask = TechniqueFlags::NAKED_SINGLES
316 | TechniqueFlags::XWING
317 | TechniqueFlags::LOCKED_CANDIDATES;
318 assert_eq!(
319 format!("{}", mask),
320 "Naked Singles, Locked Candidates, X-Wing"
321 );
322 }
323
324 #[test]
325 fn test_empty_path() {
326 let solve_path = SolvePath { steps: Vec::new() }; let expected = vec!["(No moves recorded)"];
328 assert_eq!(format_solve_path(&solve_path, 5), expected);
329 }
330
331 #[test]
332 fn test_single_technique_multiple_moves_with_chunking() {
333 let steps = vec![
334 SolveStep::Placement {
336 row: 0,
337 col: 0,
338 value: 1,
339 flags: TechniqueFlags::NAKED_SINGLES,
340 },
341 SolveStep::Placement {
342 row: 0,
343 col: 1,
344 value: 2,
345 flags: TechniqueFlags::NAKED_SINGLES,
346 },
347 SolveStep::Placement {
348 row: 0,
349 col: 2,
350 value: 3,
351 flags: TechniqueFlags::NAKED_SINGLES,
352 },
353 SolveStep::Placement {
354 row: 0,
355 col: 3,
356 value: 4,
357 flags: TechniqueFlags::NAKED_SINGLES,
358 },
359 SolveStep::Placement {
360 row: 0,
361 col: 4,
362 value: 5,
363 flags: TechniqueFlags::NAKED_SINGLES,
364 },
365 SolveStep::Placement {
366 row: 0,
367 col: 5,
368 value: 6,
369 flags: TechniqueFlags::NAKED_SINGLES,
370 },
371 ];
372 let solve_path = SolvePath { steps }; let chunk_size = 2; let expected = vec![
376 "Naked Singles:",
377 " R1C1=1,A=plac R1C2=2,A=plac",
378 " R1C3=3,A=plac R1C4=4,A=plac",
379 " R1C5=5,A=plac R1C6=6,A=plac",
380 ];
381 assert_eq!(format_solve_path(&solve_path, chunk_size), expected);
382 }
383
384 #[test]
385 fn test_multiple_techniques_and_mixed_chunking() {
386 let steps = vec![
387 SolveStep::Placement {
388 row: 0,
389 col: 0,
390 value: 1,
391 flags: TechniqueFlags::NAKED_SINGLES,
392 },
393 SolveStep::Placement {
394 row: 0,
395 col: 1,
396 value: 2,
397 flags: TechniqueFlags::NAKED_SINGLES,
398 },
399 SolveStep::Placement {
400 row: 1,
401 col: 0,
402 value: 3,
403 flags: TechniqueFlags::HIDDEN_SINGLES,
404 },
405 SolveStep::Placement {
406 row: 1,
407 col: 1,
408 value: 4,
409 flags: TechniqueFlags::HIDDEN_SINGLES,
410 },
411 SolveStep::Placement {
412 row: 1,
413 col: 2,
414 value: 5,
415 flags: TechniqueFlags::HIDDEN_SINGLES,
416 },
417 SolveStep::CandidateElimination {
418 row: 2,
419 col: 0,
420 value: 6,
421 flags: TechniqueFlags::HIDDEN_PAIRS,
422 }, ];
424 let solve_path = SolvePath { steps }; let chunk_size = 3; let expected = vec![
428 "Naked Singles:",
429 " R1C1=1,A=plac R1C2=2,A=plac",
430 "Hidden Singles:",
431 " R2C1=3,A=plac R2C2=4,A=plac R2C3=5,A=plac",
432 "Hidden Pairs:",
433 " R3C1=6,A=elim",
434 ];
435 assert_eq!(format_solve_path(&solve_path, chunk_size), expected);
436 }
437}