diff --git a/src/main.rs b/src/main.rs index e229bfe..ee6cde8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,12 +7,32 @@ use std::process; use text_io::read; use std::cmp::min; +const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + enum LineType { Top, Middle, Bottom, } +enum WaveError { + Contradiction, + NoEmptyCell, + NoPossibility, + NoHistory, +} + +impl fmt::Display for WaveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WaveError::Contradiction => write!(f, "Error: The puzzle contradicts itself."), + WaveError::NoEmptyCell => write!(f, "Error: No empty cells left to fill."), + WaveError::NoPossibility => write!(f, "Error: The cell has no possibility."), + WaveError::NoHistory => write!(f, "Error: Tried to backtrack but the History is empty."), + } + } +} + enum DisplayMode { Full, Focus(usize), @@ -53,11 +73,16 @@ impl Cell { impl fmt::Display for Cell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(value) = self.value { - write!(f, "{}", to_base35(value)) - } else { - write!(f, " ") - } + let Some(value) = self.value else { + return write!(f, " ") + }; + + let display_value = match CHAR_SET.chars().nth(value) { + Some(value) => value.to_string(), + None => "#".to_string(), + }; + + write!(f, "{}", display_value) } } @@ -79,7 +104,6 @@ impl Sudoku { 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(), @@ -112,7 +136,6 @@ impl Sudoku { } println!("{}",line); } - self.draw_line(LineType::Bottom); } @@ -122,7 +145,6 @@ impl Sudoku { 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); @@ -133,7 +155,7 @@ impl Sudoku { println!("{}{}", line, right_corner); } - fn update_possibilities(&mut self) { + fn update_possibilities(&mut self) -> Result<(), WaveError> { 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]; @@ -143,6 +165,9 @@ impl Sudoku { let Some(value) = self.grid[row_index][column_index].value else { continue }; + if row_used_values[row_index].contains(&value) || column_used_values[column_index].contains(&value) || column_used_values[column_index].contains(&value) { + return Err(WaveError::Contradiction) + } 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); @@ -158,19 +183,19 @@ impl Sudoku { self.grid[row_index][column_index].remove_possibilities(&used_values); } } + Ok(()) } - fn propagate_collapse(&mut self, _debug_display: bool) { + fn propagate_collapse(&mut self, _debug_display: bool) -> Result<(), WaveError> { let Some(last_choice) = self.history.last() else { - self.update_possibilities(); - return + return self.update_possibilities() }; let collapsed_row = last_choice.cell_selected[0]; let collapsed_column = last_choice.cell_selected[1]; let Some(collapsed_value) = self.grid[collapsed_row][collapsed_column].value else { - return + return Ok(()) }; let mut collapsed_possibility = HashSet::new(); @@ -191,9 +216,10 @@ impl Sudoku { self.grid[row][column].remove_possibilities(&collapsed_possibility); } } + Ok(()) } - fn collapse(&mut self, debug_display: bool) -> bool { + fn collapse(&mut self, debug_display: bool) -> Result<(), WaveError> { let mut min_row_index: usize = 0; let mut min_column_index: usize = 0; let mut min_len: usize = self.size; @@ -219,13 +245,21 @@ impl Sudoku { if debug_display { println!("x no empty cells"); } - return false + return Err(WaveError::NoEmptyCell) } - - if self.collapse_cell(min_row_index, min_column_index, debug_display) { - return true - } + let Err(reason) = self.collapse_cell(min_row_index, min_column_index, debug_display) else { + return Ok(()) + }; + + if let WaveError::NoPossibility = reason { + return self.backtrack(debug_display) + } else { + return Err(reason) + } + } + + fn backtrack(&mut self, debug_display: bool) -> Result<(), WaveError> { let mut fork: Option = None; while let Some(choice) = self.history.pop() { @@ -243,34 +277,34 @@ impl Sudoku { if debug_display { println!("x backtracked to start"); } - return false + return Err(WaveError::NoHistory) }; - self.reset_possibilities(); + self.reset_possibilities()?; 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) { + fn reset_possibilities(&mut self) -> Result<(), WaveError> { for row in &mut self.grid { for cell in row { cell.reset_possibilities(); } } - self.update_possibilities(); + return self.update_possibilities(); } - fn collapse_cell(&mut self, row_index: usize, column_index: usize, debug_display: bool) -> bool { + fn collapse_cell(&mut self, row_index: usize, column_index: usize, debug_display: bool) -> Result<(), WaveError> { let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) else { if debug_display { println!("x no possibilities for [{}][{}]", row_index, column_index); } - return false + return Err(WaveError::NoPossibility) }; self.history.push(Choice { @@ -283,10 +317,10 @@ impl Sudoku { if debug_display { println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); } - return true + Ok(()) } - fn solve(&mut self, debug_display: bool) { + fn solve(&mut self, debug_display: bool) -> Result<(), WaveError> { let now = Instant::now(); self.display(DisplayMode::Full); @@ -302,16 +336,27 @@ impl Sudoku { } } } + + let initial_grid = self.grid.clone(); println!("# started"); - self.update_possibilities(); - let mut counter: usize = 0; + self.update_possibilities()?; + + let mut step_counter: usize = 0; + let mut reset_counter: usize = 0; - while self.collapse(debug_display) { + while let Ok(_) = self.collapse(debug_display) { //self.update_possibilities(); - self.propagate_collapse(debug_display); - counter +=1; + self.propagate_collapse(debug_display)?; + step_counter += 1; + + if step_counter%(2*self.size*self.size) == 0 { + self.grid = initial_grid.clone(); + self.update_possibilities()?; + self.history = vec![]; + reset_counter += 1; + } if !debug_display { let bar_size = (self.size + self.square_size - 1)*2 + 1; @@ -320,6 +365,7 @@ impl Sudoku { print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do)); } } + println!(); let elapsed = now.elapsed(); @@ -327,8 +373,13 @@ impl Sudoku { if debug_display { println!("--------"); } - println!("# finished in {} steps, {:.2?} ({:.2?}/step)", counter, elapsed, elapsed/(counter as u32)); + println!("# finished in {} steps, {:.2?} ({:.2?}/step)", step_counter, elapsed, elapsed/(step_counter as u32)); + if reset_counter > 0 { + println!("# {} resets", reset_counter); + } self.display(DisplayMode::Full); + + Ok(()) } fn ask(&mut self) { @@ -337,7 +388,7 @@ impl Sudoku { print!("> "); let line: String = read!("{}\n"); for (column_index, c) in line.chars().enumerate() { - match "0123456789abcdefghijklmnopqrstuvwxyz".find(c) { + match CHAR_SET.find(c) { Some(value) => { if value <= self.size { self.grid[row_index][column_index].set(value); @@ -348,7 +399,7 @@ impl Sudoku { } let height = self.size + self.square_size + 2; print!("\x1b[{}A", height); - for i in 0..height { + for _ in 0..height { println!("{}"," ".repeat((self.size + self.square_size - 1)*2 + 5)); } print!("\x1b[{}A", height); @@ -356,24 +407,6 @@ impl Sudoku { } } -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 args: Vec = env::args().collect(); if args.len() < 2 { @@ -392,5 +425,7 @@ fn main() { sudoku.ask(); } - sudoku.solve(args.contains(&"--debug".to_string())); + if let Err(reason) = sudoku.solve(args.contains(&"--debug".to_string())) { + println!("{}",reason); + } }