diff --git a/Cargo.lock b/Cargo.lock index c2a62a0..1f6d5e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" -[[package]] -name = "argparse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47" - [[package]] name = "autocfg" version = "1.0.1" @@ -26,6 +20,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bstr" version = "0.2.13" @@ -50,6 +50,12 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + [[package]] name = "cfg-if" version = "0.1.10" @@ -62,6 +68,43 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "const_fn" version = "0.4.2" @@ -175,6 +218,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.17" @@ -198,6 +262,43 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "itoa" version = "0.4.6" @@ -221,9 +322,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.131" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linked-hash-map" @@ -231,6 +332,12 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "log" version = "0.4.11" @@ -274,6 +381,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "owned_ttf_parser" version = "0.12.1" @@ -307,6 +436,30 @@ dependencies = [ "time", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.18" @@ -315,18 +468,18 @@ checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.7" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -379,6 +532,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.5" @@ -492,25 +659,41 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "sudoku_solver" -version = "0.2.0" +version = "0.3.0" dependencies = [ - "argparse", + "clap", "csv", + "num_cpus", "printpdf", "rand", ] [[package]] name = "syn" -version = "1.0.69" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", ] [[package]] @@ -558,10 +741,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" [[package]] -name = "unicode-xid" -version = "0.2.1" +name = "unicode-ident" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "version_check" @@ -651,8 +834,74 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index 225e91d..c155522 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sudoku_solver" -version = "0.2.0" +version = "0.3.0" authors = ["Joel Therrien "] edition = "2021" @@ -8,8 +8,9 @@ edition = "2021" [dependencies] csv = "1.1.3" -argparse = "0.2.2" +clap = { version = "4.1.1", features = ['derive']} printpdf = "0.5.2" +num_cpus = "1.15.0" [dependencies.rand] version = "0.8.5" diff --git a/src/bin/generator.rs b/src/bin/generator.rs index 83d748f..600262f 100644 --- a/src/bin/generator.rs +++ b/src/bin/generator.rs @@ -2,15 +2,17 @@ use rand::prelude::*; use std::cmp::Ordering as ComparableOrdering; use std::error::Error; use std::io::Write; -use std::process::exit; use std::str::FromStr; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{mpsc, Arc}; use std::thread; use sudoku_solver::grid::{CellValue, Grid}; use sudoku_solver::solver::{SolveController, SolveStatistics}; +use clap::{Parser, ValueEnum}; -#[derive(Clone, Copy)] // Needed for argparse +use num_cpus; + +#[derive(Clone, Copy, ValueEnum, Debug)] enum Difficulty { Challenge, Hard, @@ -89,122 +91,91 @@ impl FromStr for Difficulty { } } +/// Generate Sudoku Puzzles +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct RunParams { + + /// Sets debug mode + #[arg(long, default_value_t=false)] + debug: bool, + + /// Sets maximum allowed puzzle hints + #[arg(short, long, default_value_t=81)] + max_hints: u64, + + /// Sets maximum puzzle attempts per generation + #[arg(long, default_value_t=100)] + attempts: usize, + + /// Sets output filename; filename should end in CSV or PDF. + /// + /// The type of file generated depends on the file extension used, so a PDF file will be generated for a .pdf file and + /// a CSV file will be generated for a .csv file. + #[arg(action)] + filename: String, + + /// Sets number puzzles + #[arg(short, long, default_value_t=1)] + puzzles: usize, + + /// Sets difficulty + #[arg(long, value_enum, default_value="hard")] + difficulty: Difficulty, + + /// Sets whether to print notes + /// + /// This parameter is only applicable for PDF outputs. + #[arg(short, long, default_value_t=false)] + notes: bool, + +} + fn main() { - let mut debug = false; - let mut max_hints = 81; - let mut max_attempts: Option = None; - let mut filename= String::new(); - let mut number_puzzles: usize = 1; - let mut difficulty = Difficulty::Hard; - let mut threads = 1; - let mut print_possibilities = false; - { - // this block limits scope of borrows by ap.refer() method - let mut ap = argparse::ArgumentParser::new(); - ap.set_description("Generate Sudoku puzzles"); - ap.refer(&mut debug) - .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); + let run_params: RunParams = RunParams::parse(); - ap.refer(&mut max_hints).add_option( - &["--hints"], - argparse::Store, - "Only return a puzzle with less than or equal to this number of hints", - ); + let solve_controller = run_params.difficulty.map_to_solve_controller(); + let total_attempts = run_params.puzzles * run_params.attempts; + let threads = num_cpus::get(); - ap.refer(&mut max_attempts) - .add_option(&["--attempts"], argparse::StoreOption, - "Number of puzzles attempted to find the appropriate puzzles; default is 100*(num-puzzles)"); + println!("Processing using {} threads", threads); - ap.refer(&mut filename).add_argument( - "filename", - argparse::Store, - "Filename to store puzzle(s) in as a CSV or pdf", - ).required(); - - ap.refer(&mut number_puzzles).add_option( - &["--num-puzzles"], - argparse::Store, - "Number of puzzles to generate; default 1" - ); - - ap.refer(&mut difficulty).add_option( - &["-d", "--difficulty"], - argparse::Store, - "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE", - ); - - ap.refer(&mut threads).add_option( - &["--threads"], - argparse::Store, - "Number of threads to use when generating possible puzzles", - ); - - ap.refer(&mut print_possibilities).add_option( - &["-p", "--possibilities"], - argparse::StoreTrue, - "Include each cell's possibilities in the output; applies only to PDF output", - ); - - ap.parse_args_or_exit(); - } - - let max_attempts = max_attempts.unwrap_or(100 * number_puzzles); - - let solve_controller = difficulty.map_to_solve_controller(); - - let mut generated_grids_vec = match threads.cmp(&1) { - ComparableOrdering::Less => { - eprintln!("--threads must be at least 1"); - exit(1); - } - ComparableOrdering::Equal => { - let mut rng = SmallRng::from_entropy(); - get_puzzle_matching_conditions( - &mut rng, - &difficulty, - &solve_controller, - max_hints, - &AtomicI64::new(number_puzzles as i64), - &AtomicI64::new(max_attempts as i64), - debug, - ) - } - - ComparableOrdering::Greater => run_multi_threaded( - max_attempts, - number_puzzles, - max_hints, - threads, - debug, - solve_controller, - difficulty, - ), - }; + let mut generated_grids_vec = run_multi_threaded( + total_attempts, + run_params.puzzles, + run_params.max_hints, + threads, + run_params.debug, + solve_controller, + run_params.difficulty, + ); - if generated_grids_vec.len() < number_puzzles { - println!("Unable to find {} puzzles in {} tries; instead {} puzzles were found.", number_puzzles, max_attempts, generated_grids_vec.len()); + if generated_grids_vec.len() < run_params.puzzles { + println!("Unable to find {} puzzles in {} tries; instead {} puzzles were found.", run_params.puzzles, total_attempts, generated_grids_vec.len()); return } let mut grids_vec: Vec = Vec::new(); - for _ in 0..number_puzzles { + for _ in 0..run_params.puzzles { // It may happen that we generated more puzzles than we needed - that's okay, we'll just ignore those grids_vec.push(generated_grids_vec.pop().unwrap().grid); } // check if we save to a csv or a pdf - if filename.ends_with(".pdf") { - sudoku_solver::pdf::draw_grids(&grids_vec, &filename, print_possibilities).unwrap(); - println!("Grid saved as pdf to {}", filename); + if run_params.filename.ends_with(".pdf") { + sudoku_solver::pdf::draw_grids(&grids_vec, &run_params.filename, run_params.notes).unwrap(); + println!("Grid saved as pdf to {}", run_params.filename); } else { - save_grids_csv(&grids_vec, &filename).unwrap(); - println!("Grid saved as CSV to {}", filename); + save_grids_csv(&grids_vec, &run_params.filename).unwrap(); + println!("Grid saved as CSV to {}", run_params.filename); } + + } struct GeneratedGrid { @@ -226,7 +197,7 @@ fn run_multi_threaded( max_attempts: usize, number_puzzles: usize, max_hints: u64, - threads: u64, + threads: usize, debug: bool, solve_controller: SolveController, difficulty: Difficulty, diff --git a/src/bin/solver.rs b/src/bin/solver.rs index 9d2a329..408756d 100644 --- a/src/bin/solver.rs +++ b/src/bin/solver.rs @@ -1,35 +1,35 @@ use std::str::FromStr; +use clap::Parser; use sudoku_solver::grid::Grid; use sudoku_solver::solver::solve_grid; +/// Test text here +/// +/// Second test text here setence; +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct RunParams { + /// Sets debug mode + #[arg(long, default_value_t=false)] + debug: bool, + + /// Path to puzzle CSV file to solve + #[arg(action)] + filename: String, +} + fn main() { - let mut debug = false; - let mut filename = String::new(); - { - // this block limits scope of borrows by ap.refer() method - let mut ap = argparse::ArgumentParser::new(); - ap.set_description("Solve Sudoku puzzles"); - ap.refer(&mut debug) - .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); + let params: RunParams = RunParams::parse(); - ap.refer(&mut filename).required().add_argument( - "filename", - argparse::Store, - "Path to puzzle CSV file", - ); - - ap.parse_args_or_exit(); - } - - if debug { + if params.debug { unsafe { sudoku_solver::grid::DEBUG = true; sudoku_solver::solver::DEBUG = true; } } - let mut grid = match read_grid(&filename) { + let mut grid = match read_grid(¶ms.filename) { Ok(grid) => grid, Err(e) => { eprintln!("Error while reading grid: \"{}\"", e);