better errors
This commit is contained in:
parent
3d2f846858
commit
2f0dbb727d
143
src/main.rs
143
src/main.rs
|
@ -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) {
|
let Err(reason) = self.collapse_cell(min_row_index, min_column_index, debug_display) else {
|
||||||
return true
|
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);
|
||||||
|
@ -303,15 +337,26 @@ impl Sudoku {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let initial_grid = self.grid.clone();
|
||||||
|
|
||||||
println!("# started");
|
println!("# started");
|
||||||
|
|
||||||
self.update_possibilities();
|
self.update_possibilities()?;
|
||||||
let mut counter: usize = 0;
|
|
||||||
|
|
||||||
while self.collapse(debug_display) {
|
let mut step_counter: usize = 0;
|
||||||
|
let mut reset_counter: usize = 0;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue