use clap::{ColorChoice, Parser, Subcommand};
use rustoku_lib::core::TechniqueFlags;
use rustoku_lib::{Rustoku, generate_board};
mod csv;
#[derive(Parser, Debug)]
#[command(
version,
about = "🦀 Rustoku: Lightning-fast Sudoku solver 🦀",
long_about = "Rustoku solves and generates puzzles, delivering unparalleled speed and clarity",
color = ColorChoice::Always,
)]
pub struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Generate {
#[arg(short, long, default_value_t = 30)] clues: usize,
},
Solve {
#[command(subcommand)]
solve_command: SolveCommands,
},
Check {
puzzle: String,
},
Show {
puzzle: String,
},
}
#[derive(Subcommand, Debug)]
pub enum SolveCommands {
Any {
puzzle: String,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
human: bool,
},
All {
puzzle: String,
#[arg(short, long)]
verbose: bool,
#[arg(short = 'u', long = "until", default_value_t = 0_usize)]
until: usize,
#[arg(long)]
human: bool,
},
Csv {
#[arg(value_name = "FILE")]
file: String,
#[arg(short, long)]
output: Option<String>,
#[arg(long)]
human: bool,
#[arg(long)]
stats_only: bool,
},
}
fn main() {
let cli = Cli::parse();
let result = match cli.command {
Commands::Generate { clues } => handle_generate(clues),
Commands::Solve { solve_command } => match solve_command {
SolveCommands::Any {
puzzle,
verbose,
human,
} => handle_solve_any(&puzzle, verbose, human),
SolveCommands::All {
puzzle,
verbose,
until,
human,
} => handle_solve_all(&puzzle, verbose, until, human),
SolveCommands::Csv {
file,
output,
human,
stats_only,
} => csv::solve_csv_file(&file, output, human, stats_only),
},
Commands::Check { puzzle } => handle_check(&puzzle),
Commands::Show { puzzle } => handle_show(&puzzle),
};
if let Err(e) = result {
eprintln!("💥 Error: {e}");
std::process::exit(1);
}
}
fn handle_generate(clues: usize) -> Result<(), rustoku_lib::RustokuError> {
generate_board(clues).map(|board| {
println!("🎲 Generated puzzle with {clues} clues:");
println!("{board}")
})
}
fn handle_solve_any(
puzzle: &str,
verbose: bool,
human: bool,
) -> Result<(), rustoku_lib::RustokuError> {
let techniques = if human {
TechniqueFlags::all()
} else {
TechniqueFlags::EASY
};
Rustoku::builder()
.board_from_str(puzzle)
.and_then(|b| b.techniques(techniques).build())
.map(|mut rustoku| match rustoku.solve_any() {
None => println!("🚫 No solution found"),
Some(solution) => {
println!("🎯 Solution found:");
if verbose {
println!("{}\n\n{}", solution.board, solution.solve_path);
} else {
println!("{}", solution.board);
}
}
})
}
fn handle_solve_all(
puzzle: &str,
verbose: bool,
until: usize,
human: bool,
) -> Result<(), rustoku_lib::RustokuError> {
let techniques = if human {
TechniqueFlags::all()
} else {
TechniqueFlags::EASY
};
Rustoku::builder()
.board_from_str(puzzle)
.and_then(|b| b.techniques(techniques).build())
.map(|mut rustoku| {
let solutions = if until > 0 {
rustoku.solve_until(until)
} else {
rustoku.solve_all()
};
match solutions.len() {
0 => println!("🚫 No solutions found"),
1 => {
println!("🎯 Found 1 unique solution:");
if verbose {
println!("{}\n\n{}", solutions[0].board, solutions[0].solve_path);
} else {
println!("{}", solutions[0].board);
}
}
n => {
println!("🔍 Found {n} solutions:");
solutions.iter().enumerate().for_each(|(i, solution)| {
println!("\n--- Solution {} ---", i + 1);
if verbose {
println!("{}\n\n{}", solution.board, solution.solve_path);
} else {
println!("{}", solution.board);
}
});
println!("\n✅ All solutions displayed");
}
}
})
}
fn handle_check(puzzle: &str) -> Result<(), rustoku_lib::RustokuError> {
Rustoku::builder()
.board_from_str(puzzle)
.and_then(|b| b.build())
.map(|rustoku| {
if rustoku.is_solved() {
println!("✅ Puzzle is solved correctly!");
} else {
println!("❌ Puzzle is not solved correctly");
}
})
}
fn handle_show(puzzle: &str) -> Result<(), rustoku_lib::RustokuError> {
Rustoku::builder()
.board_from_str(puzzle)
.and_then(|b| b.build())
.map(|rustoku| {
println!("🎨 Show puzzle:");
println!("{}", rustoku.board);
})
}