Skip to content

Commit a3ee666

Browse files
committed
Enhance library integration with the CLI
1 parent b519031 commit a3ee666

File tree

2 files changed

+92
-7
lines changed

2 files changed

+92
-7
lines changed

rustoku-cli/src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ fn main() {
6060
let cli = Cli::parse();
6161

6262
let result = match cli.command {
63-
Commands::Generate { clues } => generate_board(clues).map(|board| print!("{}", board)),
63+
Commands::Generate { clues } => generate_board(clues).map(|board| println!("{}", board)),
6464
Commands::Solve { solve_command } => match solve_command {
6565
SolveCommands::Any { puzzle } => Rustoku::new_from_str(&puzzle).map(|mut rustoku| {
6666
rustoku = rustoku.with_techniques(TechniqueFlags::all());
6767
match rustoku.solve_any() {
6868
None => println!("No solution found."),
69-
Some(solution) => print!("{}", solution),
69+
Some(solution) => println!("{}", solution),
7070
}
7171
}),
7272
SolveCommands::All { puzzle } => Rustoku::new_from_str(&puzzle).map(|mut rustoku| {
@@ -77,7 +77,7 @@ fn main() {
7777
} else {
7878
solutions.iter().enumerate().for_each(|(i, solution)| {
7979
println!("\n--- Solution {} ---", i + 1);
80-
print!("{}", solution);
80+
println!("{}", solution);
8181
});
8282
println!("\nFound {} solution(s).", solutions.len());
8383
}
@@ -90,7 +90,7 @@ fn main() {
9090
);
9191
}),
9292
Commands::Show { puzzle } => Rustoku::new_from_str(&puzzle).map(|rustoku| {
93-
print!("{}", rustoku.board);
93+
println!("{}", rustoku.board);
9494
}),
9595
};
9696

rustoku-lib/src/format.rs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
//! This module provides functions to format the Sudoku board and its solve path
44
//! in various ways.
55
6-
use crate::core::{Board, Solution, SolvePath, TechniqueFlags};
6+
use crate::core::{Board, Solution, SolvePath, SolveStep, TechniqueFlags};
77
use std::fmt;
88

99
/// Formats the solution into a human-readable string representation.
1010
impl fmt::Display for Solution {
1111
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1212
writeln!(f, "{}", self.board)?;
13-
writeln!(f, "{}", self.solve_path)?;
13+
write!(f, "\n{}", self.solve_path)?;
1414
Ok(())
1515
}
1616
}
@@ -19,7 +19,7 @@ impl fmt::Display for Solution {
1919
impl fmt::Display for Board {
2020
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2121
writeln!(f, "{}", format_grid(&self.cells).join("\n"))?;
22-
writeln!(f, "Line format: {}", format_line(&self.cells))?;
22+
write!(f, "Line format: {}", format_line(&self.cells))?;
2323
Ok(())
2424
}
2525
}
@@ -59,6 +59,7 @@ impl fmt::Display for TechniqueFlags {
5959
}
6060
}
6161

62+
/// Formats the solve path into a human-readable string representation.
6263
impl fmt::Display for SolvePath {
6364
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6465
let path: Vec<(usize, usize, u8, TechniqueFlags, &str)> = self
@@ -85,6 +86,38 @@ impl fmt::Display for SolvePath {
8586
}
8687
}
8788

89+
/// Formats the solve step into a human-readable string representation
90+
impl fmt::Display for SolveStep {
91+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92+
match self {
93+
SolveStep::Placement {
94+
row,
95+
col,
96+
value,
97+
flags,
98+
} => {
99+
write!(
100+
f,
101+
"Value {} is placed on R{}C{} by {}",
102+
value, row, col, flags
103+
)
104+
}
105+
SolveStep::CandidateElimination {
106+
row,
107+
col,
108+
value,
109+
flags,
110+
} => {
111+
write!(
112+
f,
113+
"Value {} is eliminated from R{}C{} by {}",
114+
value, row, col, flags
115+
)
116+
}
117+
}
118+
}
119+
}
120+
88121
/// Formats the Sudoku board into a grid representation.
89122
///
90123
/// This function takes a 9x9 Sudoku board and formats it into a grid with
@@ -179,6 +212,7 @@ pub fn format_solve_path(
179212
#[cfg(test)]
180213
mod tests {
181214
use super::*;
215+
use crate::core::TechniqueFlags;
182216

183217
#[test]
184218
fn test_format_grid() {
@@ -291,4 +325,55 @@ mod tests {
291325
"Naked Singles, Locked Candidates, X-Wing"
292326
);
293327
}
328+
329+
#[test]
330+
fn test_empty_path() {
331+
let path: Vec<(usize, usize, u8, TechniqueFlags, &str)> = Vec::new();
332+
let expected = vec!["(No moves recorded)".to_string()];
333+
assert_eq!(format_solve_path(&path, 5), expected);
334+
}
335+
336+
#[test]
337+
fn test_single_technique_multiple_moves_with_chunking() {
338+
let path = vec![
339+
(0, 0, 1, TechniqueFlags::NAKED_SINGLES, "plac"),
340+
(0, 1, 2, TechniqueFlags::NAKED_SINGLES, "plac"),
341+
(0, 2, 3, TechniqueFlags::NAKED_SINGLES, "plac"),
342+
(0, 3, 4, TechniqueFlags::NAKED_SINGLES, "plac"),
343+
(0, 4, 5, TechniqueFlags::NAKED_SINGLES, "plac"),
344+
(0, 5, 6, TechniqueFlags::NAKED_SINGLES, "plac"),
345+
];
346+
let chunk_size = 2; // Each line will have 2 moves
347+
348+
let expected = vec![
349+
"Naked Singles:".to_string(),
350+
" R1C1=1,A=plac R1C2=2,A=plac".to_string(),
351+
" R1C3=3,A=plac R1C4=4,A=plac".to_string(),
352+
" R1C5=5,A=plac R1C6=6,A=plac".to_string(),
353+
];
354+
assert_eq!(format_solve_path(&path, chunk_size), expected);
355+
}
356+
357+
#[test]
358+
fn test_multiple_techniques_and_mixed_chunking() {
359+
let path = vec![
360+
(0, 0, 1, TechniqueFlags::NAKED_SINGLES, "plac"),
361+
(0, 1, 2, TechniqueFlags::NAKED_SINGLES, "plac"),
362+
(1, 0, 3, TechniqueFlags::HIDDEN_SINGLES, "plac"),
363+
(1, 1, 4, TechniqueFlags::HIDDEN_SINGLES, "plac"),
364+
(1, 2, 5, TechniqueFlags::HIDDEN_SINGLES, "plac"),
365+
(2, 0, 6, TechniqueFlags::HIDDEN_PAIRS, "elim"),
366+
];
367+
let chunk_size = 3; // Each line will have 3 moves
368+
369+
let expected: Vec<String> = vec![
370+
"Naked Singles:".to_string(),
371+
" R1C1=1,A=plac R1C2=2,A=plac".to_string(),
372+
"Hidden Singles:".to_string(),
373+
" R2C1=3,A=plac R2C2=4,A=plac R2C3=5,A=plac".to_string(),
374+
"Hidden Pairs:".to_string(),
375+
" R3C1=6,A=elim".to_string(),
376+
];
377+
assert_eq!(format_solve_path(&path, chunk_size), expected);
378+
}
294379
}

0 commit comments

Comments
 (0)