diff --git a/src/main.rs b/src/main.rs index 5acbd1e..ee52349 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,33 @@ use std::fmt; -//use std::collections::HashSet; +use std::collections::HashSet; use rand::seq::SliceRandom; +use std::time::Instant; +use std::env; +use std::process; +use text_io::read; +use std::cmp::min; + +enum LineType { + Top, + Middle, + Bottom, +} + +enum DisplayMode { + Full, + Focus(usize), +} #[derive(Clone)] struct Cell { - value: Option, - possibilities: Vec + value: Option, + max: usize, + possibilities: Vec } impl Cell { - fn set(&mut self, value: u8) { - self.value = if value > 0 && value < 10 { + fn set(&mut self, value: usize) { + self.value = if value > 0 && value <= self.max { Some(value) } else { None @@ -18,108 +35,140 @@ impl Cell { } fn reset_possibilities(&mut self) { - self.possibilities = (1..=9).collect(); + self.possibilities = (1..=self.max).collect(); } - fn remove_possibilities(&mut self, possibilities: &Vec) { + fn remove_possibilities(&mut self, possibilities: &HashSet) { self.possibilities.retain(|&x| !possibilities.contains(&x)) } -} -impl Default for Cell { - fn default() -> Self { + fn new(max: usize) -> Self { Self { value: None, - possibilities: (1..=9).collect(), + max, + possibilities: (1..=max).collect(), } } } impl fmt::Display for Cell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(v) = self.value { - write!(f, "{}", v) + if let Some(value) = self.value { + write!(f, "{}", to_base35(value)) } else { - write!(f, " ") + write!(f, " ") } } } struct Choice { cell_selected: [usize; 2], - possibilities: Vec, - selected_value: u8, + possibilities: Vec, + selected_value: usize, } struct Sudoku { - grid: [[Cell; 9]; 9], + grid: Vec>, rng: rand::rngs::ThreadRng, history: Vec, + size: usize, + square_size: usize, } impl Sudoku { - fn new(sudoku_set: [[u8; 9]; 9]) -> Self { - let mut sudoku_grid: [[Cell; 9]; 9] = [[(); 9]; 9].map(|_| [(); 9].map(|_| Cell::default())); - - for (row_index, row) in sudoku_set.iter().enumerate(){ - for (column_index, value) in row.iter().enumerate() { - sudoku_grid[row_index][column_index].set(*value) - } - } + fn new(order: usize) -> Self { + let size = order*order; + let sudoku_grid: Vec> = vec![vec![Cell::new(size); size]; size]; Self { grid: sudoku_grid, rng: rand::thread_rng(), history: vec![], + size, + square_size: order, } } - fn display(&self) { - println!("# sudoku grid : "); - for row in &self.grid { - let mut line: String = "[".to_string(); - for cell in row { - line += &format!("{}|",cell); + fn display(&self, display_mode: DisplayMode) { + self.draw_line(LineType::Top); + for (row_index, row) in self.grid.iter().enumerate() { + if row_index % self.square_size == 0 && row_index != 0 { + self.draw_line(LineType::Middle); } - line.pop(); - println!("{}]",line); + + let mut line: String = "║ ".to_string(); + for (cell_index, cell) in row.iter().enumerate() { + + line += &format!("{} ",cell); + + if cell_index % self.square_size == self.square_size - 1 { + line+= "║ "; + } + } + if let DisplayMode::Focus(focus_row) = display_mode { + if row_index == focus_row { + line += "<"; + } + } + println!("{}",line); } + + self.draw_line(LineType::Bottom); + } + + fn draw_line(&self, line_type: LineType) { + let (left_corner, intersection, right_corner) = match line_type { + LineType::Top => ("╔", "╦", "╗"), + LineType::Middle => ("╠", "╬", "╣"), + LineType::Bottom => ("╚", "╩", "╝"), + }; + + let mut line = left_corner.to_string(); + for i in 0..(self.square_size) { + line += &"═".repeat(self.square_size*2+1); + if i < self.square_size - 1 { + line += intersection; + } + } + println!("{}{}", line, right_corner); } fn update_possibilities(&mut self) { - let mut row_used_values: [Vec;9] = [vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![]]; - let mut column_used_values: [Vec;9] = [vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![]]; - let mut square_used_values: [[Vec;3];3] = [[vec![],vec![],vec![]],[vec![],vec![],vec![]],[vec![],vec![],vec![]]]; + let mut row_used_values: Vec> = vec![HashSet::new(); self.size]; + let mut column_used_values: Vec> = vec![HashSet::new(); self.size]; + let mut square_used_values: Vec>> = vec![vec![HashSet::new(); self.square_size]; self.square_size]; - for row_index in 0..9 { - for column_index in 0..9 { + for row_index in 0..self.size { + for column_index in 0..self.size { let Some(value) = self.grid[row_index][column_index].value else { continue }; - row_used_values[row_index].push(value); - column_used_values[column_index].push(value); - square_used_values[row_index/3][column_index/3].push(value); + row_used_values[row_index].insert(value); + column_used_values[column_index].insert(value); + square_used_values[row_index/self.square_size][column_index/self.square_size].insert(value); } } - for row_index in 0..9 { - for column_index in 0..9 { - self.grid[row_index][column_index].remove_possibilities(&row_used_values[row_index]); - self.grid[row_index][column_index].remove_possibilities(&column_used_values[column_index]); - self.grid[row_index][column_index].remove_possibilities(&square_used_values[row_index/3][column_index/3]); + for row_index in 0..self.size { + for column_index in 0..self.size { + let mut used_values = row_used_values[row_index].clone(); + used_values.extend(&column_used_values[column_index]); + used_values.extend(&square_used_values[row_index/self.square_size][column_index/self.square_size]); + + self.grid[row_index][column_index].remove_possibilities(&used_values); } } } - fn collapse(&mut self) -> bool { + fn collapse(&mut self, debug_display: bool) -> bool { let mut min_row_index: usize = 0; let mut min_column_index: usize = 0; - let mut min_len: usize = 9; + let mut min_len: usize = self.size; let mut grid_has_empty_cell: bool = false; - for row_index in 0..9 { - for column_index in 0..9 { + for row_index in 0..self.size { + for column_index in 0..self.size { if !self.grid[row_index][column_index].value.is_none() { continue; } @@ -134,18 +183,22 @@ impl Sudoku { } if !grid_has_empty_cell { - println!("x no empty cells"); + if debug_display { + println!("x no empty cells"); + } return false } - if self.collapse_cell(min_row_index, min_column_index) { + if self.collapse_cell(min_row_index, min_column_index, debug_display) { return true } let mut fork: Option = None; while let Some(choice) = self.history.pop() { - println!("* backtracking"); + if debug_display { + println!("* backtracking"); + } self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None; if choice.possibilities.len() > 1 { fork = Some(choice); @@ -154,13 +207,19 @@ impl Sudoku { } let Some(choice) = fork else { - println!("x backtracked to start"); + if debug_display { + println!("x backtracked to start"); + } return false }; self.reset_possibilities(); - self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&vec![choice.selected_value]); - return self.collapse_cell(choice.cell_selected[0], choice.cell_selected[1]) + + let mut selected_value = HashSet::new(); + selected_value.insert(choice.selected_value); + + self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&selected_value); + return self.collapse_cell(choice.cell_selected[0], choice.cell_selected[1], debug_display) } fn reset_possibilities(&mut self) { @@ -173,9 +232,11 @@ impl Sudoku { self.update_possibilities(); } - fn collapse_cell(&mut self, row_index: usize, column_index: usize) -> bool { + fn collapse_cell(&mut self, row_index: usize, column_index: usize, debug_display: bool) -> bool { let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) else { - println!("x no possibilities for [{}][{}]", row_index, column_index); + if debug_display { + println!("x no possibilities for [{}][{}]", row_index, column_index); + } return false }; @@ -186,60 +247,93 @@ impl Sudoku { }); self.grid[row_index][column_index].set(selected_value); - println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); + if debug_display { + println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); + } return true } - fn solve(&mut self, display: bool) { - if display { - self.display(); - println!("--------"); - } + fn solve(&mut self, debug_display: bool) { + let now = Instant::now(); + + self.display(DisplayMode::Full); + if debug_display { + println!("--------"); + } + + println!("# started"); self.update_possibilities(); - let mut counter: usize = 0; - - while self.collapse() { + + while self.collapse(debug_display) { self.update_possibilities(); counter +=1; } - if display { - println!("--------"); + let elapsed = now.elapsed(); - println!("finished with {} steps", counter); + if debug_display { + println!("--------"); + } + println!("# finished in {} steps, {:.2?} ({:.2?}/step)", counter, elapsed, elapsed/(counter as u32)); + self.display(DisplayMode::Full); + } - self.display(); + fn ask(&mut self) { + for row_index in 0..self.size { + self.display(DisplayMode::Focus(row_index)); + print!("> "); + let line: String = read!("{}\n"); + for (column_index, c) in line.chars().enumerate() { + match "0123456789abcdefghijklmnopqrstuvwxyz".find(c) { + Some(value) => { + if value <= self.size { + self.grid[row_index][column_index].set(value); + } + } + None => {} + } + } } } } - // [6,0,0,0,0,2,1,0,0], // has to backtrack - // [0,3,2,9,0,0,0,7,0], - // [0,0,0,0,0,8,0,0,0], - // [2,0,0,0,0,0,4,0,0], - // [7,0,3,0,0,0,9,1,0], - // [0,8,0,0,9,4,0,0,0], - // [0,0,4,0,0,0,6,0,0], - // [1,2,0,7,0,0,0,0,5], - // [0,0,0,0,0,0,0,9,4], +fn to_base35(mut n: usize) -> String { + let chars = "0123456789abcdefghijklmnopqrstuvwxyz"; + if n == 0 { + return chars[0..1].to_string(); // Special case for 0 + } + let mut result = String::new(); + + while n > 0 { + let remainder = n % 35; + result.push(chars.chars().nth(remainder).unwrap()); + n /= 35; + } + + // The result will be in reverse order, so we need to reverse it + result.chars().rev().collect() +} fn main() { - let sudoku_set: [[u8; 9]; 9] = [ - [0,0,0,0,0,0,0,0,0], - [0,3,2,9,0,0,0,7,0], - [0,0,0,0,0,8,0,0,0], - [2,0,0,0,0,0,4,0,0], - [7,0,3,0,0,0,9,1,0], - [0,8,0,0,9,4,0,0,0], - [0,0,4,0,0,0,6,0,0], - [1,2,0,7,0,0,0,0,5], - [0,0,0,0,0,0,0,9,4], - ]; + let args: Vec = env::args().collect(); + if args.len() < 2 { + eprintln!("Usage: {} [--debug] [--ask]", args[0]); + process::exit(1); + } - let mut sudoku = Sudoku::new(sudoku_set); + let size: usize = args[1].parse().unwrap_or_else(|_| { + eprintln!("Invalid size argument"); + process::exit(1); + }); - sudoku.solve(true); + let mut sudoku = Sudoku::new(min(size, 5)); + + if args.contains(&"--ask".to_string()) { + sudoku.ask(); + } + + sudoku.solve(args.contains(&"--debug".to_string())); }