better errors

This commit is contained in:
WanderingPenwing 2024-11-29 17:59:39 +01:00
parent 3d2f846858
commit 2f0dbb727d

View file

@ -7,12 +7,32 @@ use std::process;
use text_io::read; use text_io::read;
use std::cmp::min; use std::cmp::min;
const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
enum LineType { enum LineType {
Top, Top,
Middle, Middle,
Bottom, 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 { enum DisplayMode {
Full, Full,
Focus(usize), Focus(usize),
@ -53,11 +73,16 @@ impl Cell {
impl fmt::Display for Cell { impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(value) = self.value { let Some(value) = self.value else {
write!(f, "{}", to_base35(value)) return write!(f, " ")
} else { };
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 { fn new(order: usize) -> Self {
let size = order*order; let size = order*order;
let sudoku_grid: Vec<Vec<Cell>> = vec![vec![Cell::new(size); size]; size]; let sudoku_grid: Vec<Vec<Cell>> = vec![vec![Cell::new(size); size]; size];
Self { Self {
grid: sudoku_grid, grid: sudoku_grid,
rng: rand::thread_rng(), rng: rand::thread_rng(),
@ -112,7 +136,6 @@ impl Sudoku {
} }
println!("{}",line); println!("{}",line);
} }
self.draw_line(LineType::Bottom); self.draw_line(LineType::Bottom);
} }
@ -122,7 +145,6 @@ impl Sudoku {
LineType::Middle => ("", "", ""), LineType::Middle => ("", "", ""),
LineType::Bottom => ("", "", ""), LineType::Bottom => ("", "", ""),
}; };
let mut line = left_corner.to_string(); let mut line = left_corner.to_string();
for i in 0..(self.square_size) { for i in 0..(self.square_size) {
line += &"".repeat(self.square_size*2+1); line += &"".repeat(self.square_size*2+1);
@ -133,7 +155,7 @@ impl Sudoku {
println!("{}{}", line, right_corner); println!("{}{}", line, right_corner);
} }
fn update_possibilities(&mut self) { fn update_possibilities(&mut self) -> Result<(), WaveError> {
let mut row_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size]; let mut row_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size];
let mut column_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size]; let mut column_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size];
let mut square_used_values: Vec<Vec<HashSet<usize>>> = vec![vec![HashSet::new(); self.square_size]; self.square_size]; let mut square_used_values: Vec<Vec<HashSet<usize>>> = 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 { let Some(value) = self.grid[row_index][column_index].value else {
continue 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); row_used_values[row_index].insert(value);
column_used_values[column_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); 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); 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 { let Some(last_choice) = self.history.last() else {
self.update_possibilities(); return self.update_possibilities()
return
}; };
let collapsed_row = last_choice.cell_selected[0]; let collapsed_row = last_choice.cell_selected[0];
let collapsed_column = last_choice.cell_selected[1]; let collapsed_column = last_choice.cell_selected[1];
let Some(collapsed_value) = self.grid[collapsed_row][collapsed_column].value else { let Some(collapsed_value) = self.grid[collapsed_row][collapsed_column].value else {
return return Ok(())
}; };
let mut collapsed_possibility = HashSet::new(); let mut collapsed_possibility = HashSet::new();
@ -191,9 +216,10 @@ impl Sudoku {
self.grid[row][column].remove_possibilities(&collapsed_possibility); 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_row_index: usize = 0;
let mut min_column_index: usize = 0; let mut min_column_index: usize = 0;
let mut min_len: usize = self.size; let mut min_len: usize = self.size;
@ -219,13 +245,21 @@ impl Sudoku {
if debug_display { if debug_display {
println!("x no empty cells"); 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<Choice> = None; let mut fork: Option<Choice> = None;
while let Some(choice) = self.history.pop() { while let Some(choice) = self.history.pop() {
@ -243,34 +277,34 @@ impl Sudoku {
if debug_display { if debug_display {
println!("x backtracked to start"); println!("x backtracked to start");
} }
return false return Err(WaveError::NoHistory)
}; };
self.reset_possibilities(); self.reset_possibilities()?;
let mut selected_value = HashSet::new(); let mut selected_value = HashSet::new();
selected_value.insert(choice.selected_value); selected_value.insert(choice.selected_value);
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&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) 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 row in &mut self.grid {
for cell in row { for cell in row {
cell.reset_possibilities(); 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 { let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) else {
if debug_display { if debug_display {
println!("x no possibilities for [{}][{}]", row_index, column_index); println!("x no possibilities for [{}][{}]", row_index, column_index);
} }
return false return Err(WaveError::NoPossibility)
}; };
self.history.push(Choice { self.history.push(Choice {
@ -283,10 +317,10 @@ impl Sudoku {
if debug_display { if debug_display {
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); 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(); let now = Instant::now();
self.display(DisplayMode::Full); self.display(DisplayMode::Full);
@ -302,16 +336,27 @@ impl Sudoku {
} }
} }
} }
let initial_grid = self.grid.clone();
println!("# started"); println!("# started");
self.update_possibilities(); self.update_possibilities()?;
let mut counter: usize = 0;
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.update_possibilities();
self.propagate_collapse(debug_display); self.propagate_collapse(debug_display)?;
counter +=1; 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 { if !debug_display {
let bar_size = (self.size + self.square_size - 1)*2 + 1; let bar_size = (self.size + self.square_size - 1)*2 + 1;
@ -320,6 +365,7 @@ impl Sudoku {
print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do)); print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do));
} }
} }
println!(); println!();
let elapsed = now.elapsed(); let elapsed = now.elapsed();
@ -327,8 +373,13 @@ impl Sudoku {
if debug_display { if debug_display {
println!("--------"); 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); self.display(DisplayMode::Full);
Ok(())
} }
fn ask(&mut self) { fn ask(&mut self) {
@ -337,7 +388,7 @@ impl Sudoku {
print!("> "); print!("> ");
let line: String = read!("{}\n"); let line: String = read!("{}\n");
for (column_index, c) in line.chars().enumerate() { for (column_index, c) in line.chars().enumerate() {
match "0123456789abcdefghijklmnopqrstuvwxyz".find(c) { match CHAR_SET.find(c) {
Some(value) => { Some(value) => {
if value <= self.size { if value <= self.size {
self.grid[row_index][column_index].set(value); self.grid[row_index][column_index].set(value);
@ -348,7 +399,7 @@ impl Sudoku {
} }
let height = self.size + self.square_size + 2; let height = self.size + self.square_size + 2;
print!("\x1b[{}A", height); print!("\x1b[{}A", height);
for i in 0..height { for _ in 0..height {
println!("{}"," ".repeat((self.size + self.square_size - 1)*2 + 5)); println!("{}"," ".repeat((self.size + self.square_size - 1)*2 + 5));
} }
print!("\x1b[{}A", height); 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() { fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.len() < 2 { if args.len() < 2 {
@ -392,5 +425,7 @@ fn main() {
sudoku.ask(); sudoku.ask();
} }
sudoku.solve(args.contains(&"--debug".to_string())); if let Err(reason) = sudoku.solve(args.contains(&"--debug".to_string())) {
println!("{}",reason);
}
} }