diff --git a/README.md b/README.md index 5aeab92..b695ca4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Recently started learning Rust and wanted to build something to practice because ## Roadmap 🗺️ - Generate a random sudoku board +- Choose between 3 difficulties (easy, medium, hard) - Sudoku solver - local web app - Playable game in web app diff --git a/src/main.rs b/src/main.rs index cdda9f9..8b47f26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,12 +7,12 @@ const SQUARE_SIZE: usize = 3; #[cfg(test)] mod tests { - use crate::{generate, print_board}; + use crate::{generate, print_board, resolv_backtrack}; #[test] fn board_valid() { const BOARD_SIZE: usize = 9; - let board = generate(BOARD_SIZE); + let board = generate(BOARD_SIZE, 1); print_board(&board); assert_eq!(board.len(), 9); @@ -28,26 +28,47 @@ mod tests { } hm.clear(); } + assert_eq!(resolv_backtrack(&mut board.clone(), 0, 0), true); } } fn main() { println!("Welcome to Sudoku-rust"); - let mut board; + let mut board = vec![]; loop { - println!("1. Generate"); - println!("2. Solve (coming later)"); + println!("1. Generate sudoku (9x9)"); + println!("2. Solve sudoku (backtracking)"); println!("3. Exit"); println!("Enter your choice: "); let choice = read_int(); match choice { 1 => { println!(); - board = generate(BOARD_SIZE); + println!("1. Easy"); + println!("2. Medium"); + println!("3. Hard"); + println!("Enter your choice: "); + + let difficulty = read_difficulty(); + + board = generate(BOARD_SIZE, difficulty); print_board(&board); } - 2 => println!("Coming later..."), + 2 => { + if board.len() == 0 { + println!("Please generate a board first"); + continue; + } + + if resolv_backtrack(&mut board, 0, 0) { + println!(); + print_board(&board); + } else { + // should never happen + println!("No solution found"); + } + } 3 => process::exit(0), _ => println!("Invalid choice"), } @@ -68,17 +89,41 @@ fn read_int() -> usize { } } -fn generate(size: usize) -> Vec> { +fn read_difficulty() -> usize { + loop { + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + + match input.trim().parse::() { + Ok(num) => { + if num > 0 && num <= 3 { + return num; + } else { + println!("Invalid input | please enter a number between 1 and 3"); + } + } + Err(_) => println!("Invalid input | please enter a number"), + } + } +} + +fn generate(size: usize, difficulty: usize) -> Vec> { let mut board = vec![vec![0; size]; size]; let mut rng = rand::thread_rng(); - for row in 0..size { - for col in 0..size { - let num: usize = rng.gen_range(0..=BOARD_SIZE); - if num == 0 { - board[row][col] = 0; - continue; - } else if is_num_valid(&board, row, col, num) { - board[row][col] = num; + let luck: f64; + match difficulty { + 1 => luck = 0.4, + 2 => luck = 0.45, + 3 => luck = 0.5, + _ => luck = 0.4, + } + resolv_backtrack(&mut board, 0, 0); // generate a valid board + for i in 0..size { + for j in 0..size { + if rng.gen_bool(luck) { + board[i][j] = 0; } } } @@ -122,3 +167,36 @@ fn print_board(board: &Vec>) { } } } + +// backtracking algorithm +// https://en.wikipedia.org/wiki/Sudoku_solving_algorithms#Backtracking +// inspired by https://gist.github.com/raeffu/8331328 + +fn resolv_backtrack(board: &mut Vec>, mut row: usize, mut col: usize) -> bool { + if col == board.len() { + col = 0; + row += 1; + if row == board.len() { + // end of board + return true; + } + } + + if board[row][col] != 0 { + return resolv_backtrack(board, row, col + 1); + } + + for num in 1..=board.len() { + if is_num_valid(board, row, col, num) { + board[row][col] = num; + if resolv_backtrack(board, row, col + 1) { + // found a number + return true; + } + // backtrack + board[row][col] = 0; + } + } + + false +}