1use crate::core::{Board, Solution, SolvePath, 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 writeln!(f, "{}", 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.cells).join("\n"))?;
22 writeln!(f, "Line format: {}", format_line(&self.cells))?;
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 {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let path: Vec<(usize, usize, u8, TechniqueFlags, &str)> = self
65 .steps
66 .iter()
67 .map(|step| match step {
68 crate::core::SolveStep::Placement {
69 row,
70 col,
71 value,
72 flags,
73 } => (*row, *col, *value, *flags, step.code()),
74 crate::core::SolveStep::CandidateElimination {
75 row,
76 col,
77 value,
78 flags,
79 } => (*row, *col, *value, *flags, step.code()),
80 })
81 .collect();
82
83 let formatted_lines = format_solve_path(&path, 5);
84 write!(f, "{}", formatted_lines.join("\n"))
85 }
86}
87
88pub fn format_grid(board: &[[u8; 9]; 9]) -> Vec<String> {
94 let mut grid = Vec::new();
95 let horizontal_line = "+-------+-------+-------+";
96
97 grid.push(horizontal_line.to_string()); for (r, row) in board.iter().enumerate().take(9) {
100 let mut line = String::from("|"); for (c, &cell) in row.iter().enumerate().take(9) {
102 match cell {
103 0 => line.push_str(" ."), n => line.push_str(&format!(" {}", n)), }
106 if (c + 1) % 3 == 0 {
107 line.push_str(" |"); }
109 }
110 grid.push(line); if (r + 1) % 3 == 0 {
113 grid.push(horizontal_line.to_string()); }
115 }
116
117 grid
118}
119
120pub fn format_line(board: &[[u8; 9]; 9]) -> String {
125 board
126 .iter()
127 .flatten()
128 .map(|&n| (n + b'0') as char)
129 .collect()
130}
131
132pub fn format_solve_path(
138 path: &[(usize, usize, u8, TechniqueFlags, &str)],
139 chunk_size: usize,
140) -> Vec<String> {
141 if path.is_empty() {
142 return vec!["(No moves recorded)".to_string()];
143 }
144
145 let mut result = Vec::new();
146 let mut current_technique = None;
147 let mut current_moves = Vec::new();
148
149 for (r, c, val, flags, action) in path {
150 let technique_name = format!("{}", flags);
151
152 if current_technique.as_ref() != Some(&technique_name) {
153 if let Some(tech) = current_technique {
155 result.push(format!("{}:", tech));
156 for chunk in current_moves.chunks(chunk_size) {
158 result.push(format!(" {}", chunk.join(" ")));
159 }
160 current_moves.clear();
161 }
162 current_technique = Some(technique_name);
163 }
164
165 current_moves.push(format!("R{}C{}={},A={}", r + 1, c + 1, val, action));
166 }
167
168 if let Some(tech) = current_technique {
170 result.push(format!("{}:", tech));
171 for chunk in current_moves.chunks(chunk_size) {
172 result.push(format!(" {}", chunk.join(" ")));
173 }
174 }
175
176 result
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_format_grid() {
185 let board = [
186 [5, 3, 0, 6, 7, 8, 9, 1, 2],
187 [6, 7, 2, 1, 9, 5, 3, 4, 8],
188 [1, 9, 8, 3, 4, 2, 5, 6, 7],
189 [8, 5, 9, 7, 6, 1, 4, 2, 3],
190 [4, 2, 6, 8, 5, 3, 7, 9, 1],
191 [7, 1, 3, 9, 2, 4, 8, 5, 6],
192 [9, 6, 1, 5, 3, 7, 2, 8, 4],
193 [2, 8, 7, 4, 1, 9, 6, 3, 5],
194 [3, 4, 5, 2, 8, 6, 1, 7, 9],
195 ];
196
197 let expected = vec![
198 "+-------+-------+-------+",
199 "| 5 3 . | 6 7 8 | 9 1 2 |",
200 "| 6 7 2 | 1 9 5 | 3 4 8 |",
201 "| 1 9 8 | 3 4 2 | 5 6 7 |",
202 "+-------+-------+-------+",
203 "| 8 5 9 | 7 6 1 | 4 2 3 |",
204 "| 4 2 6 | 8 5 3 | 7 9 1 |",
205 "| 7 1 3 | 9 2 4 | 8 5 6 |",
206 "+-------+-------+-------+",
207 "| 9 6 1 | 5 3 7 | 2 8 4 |",
208 "| 2 8 7 | 4 1 9 | 6 3 5 |",
209 "| 3 4 5 | 2 8 6 | 1 7 9 |",
210 "+-------+-------+-------+",
211 ];
212
213 assert_eq!(expected, format_grid(&board));
214 }
215
216 #[test]
217 fn test_format_line() {
218 let board = [
219 [5, 3, 0, 6, 7, 8, 9, 1, 2],
220 [6, 7, 2, 1, 9, 5, 3, 4, 8],
221 [1, 9, 8, 3, 4, 2, 5, 6, 7],
222 [8, 5, 9, 7, 6, 1, 4, 2, 3],
223 [4, 2, 6, 8, 5, 3, 7, 9, 1],
224 [7, 1, 3, 9, 2, 4, 8, 5, 6],
225 [9, 6, 1, 5, 3, 7, 2, 8, 4],
226 [2, 8, 7, 4, 1, 9, 6, 3, 5],
227 [3, 4, 5, 2, 8, 6, 1, 7, 9],
228 ];
229
230 let expected =
231 "530678912672195348198342567859761423426853791713924856961537284287419635345286179";
232 assert_eq!(expected, format_line(&board));
233 }
234
235 #[test]
236 fn test_format_grid_empty_board() {
237 let board = [[0; 9]; 9];
238
239 let expected = vec![
240 "+-------+-------+-------+",
241 "| . . . | . . . | . . . |",
242 "| . . . | . . . | . . . |",
243 "| . . . | . . . | . . . |",
244 "+-------+-------+-------+",
245 "| . . . | . . . | . . . |",
246 "| . . . | . . . | . . . |",
247 "| . . . | . . . | . . . |",
248 "+-------+-------+-------+",
249 "| . . . | . . . | . . . |",
250 "| . . . | . . . | . . . |",
251 "| . . . | . . . | . . . |",
252 "+-------+-------+-------+",
253 ];
254
255 assert_eq!(expected, format_grid(&board));
256 }
257
258 #[test]
259 fn test_format_line_empty_board() {
260 let board = [[0; 9]; 9];
261 let expected =
262 "000000000000000000000000000000000000000000000000000000000000000000000000000000000";
263 assert_eq!(expected, format_line(&board));
264 }
265
266 #[test]
267 fn test_display_empty_mask() {
268 let mask = TechniqueFlags::empty();
269 assert_eq!(format!("{}", mask), "None");
270 }
271
272 #[test]
273 fn test_display_single_technique() {
274 let mask = TechniqueFlags::NAKED_SINGLES;
275 assert_eq!(format!("{}", mask), "Naked Singles");
276
277 let mask = TechniqueFlags::XWING;
278 assert_eq!(format!("{}", mask), "X-Wing");
279 }
280
281 #[test]
282 fn test_display_multiple_techniques() {
283 let mask = TechniqueFlags::EASY;
284 assert_eq!(format!("{}", mask), "Naked Singles, Hidden Singles");
285
286 let mask = TechniqueFlags::NAKED_SINGLES
287 | TechniqueFlags::XWING
288 | TechniqueFlags::LOCKED_CANDIDATES;
289 assert_eq!(
290 format!("{}", mask),
291 "Naked Singles, Locked Candidates, X-Wing"
292 );
293 }
294}