From d901ea441dae9f446ab5bbc25bffdc300f0e0e5d Mon Sep 17 00:00:00 2001 From: WanderingPenwing Date: Sat, 30 Nov 2024 02:47:04 +0100 Subject: [PATCH] meh, better architecture of the cells, but worse behavior --- src/cell.rs | 132 ++++++++++++ src/main.rs | 595 +++++++++++++++++++++++++++++----------------------- src/ui.rs | 90 ++++++++ 3 files changed, 555 insertions(+), 262 deletions(-) create mode 100644 src/cell.rs create mode 100644 src/ui.rs diff --git a/src/cell.rs b/src/cell.rs new file mode 100644 index 0000000..d93c51b --- /dev/null +++ b/src/cell.rs @@ -0,0 +1,132 @@ +use std::fmt; +use std::collections::HashSet; +use rand::thread_rng; +use rand::Rng; + +const STATE_DISPLAY: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +pub enum CellError { + StateNotAllowed, + NoAllowedState, + StateAlreadySet, +} + +pub enum CollapseOption { + Random, + First, + Set(usize), +} + +pub enum RemoveResult { + NumAllowed(usize), + Collapsed(usize), + Filled, +} + +impl fmt::Display for CellError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CellError::StateNotAllowed => write!(f, "Cell Error : Tried to set a state not allowed."), + CellError::NoAllowedState => write!(f, "Cell Error : The cell has no allowed states."), + CellError::StateAlreadySet => write!(f, "Cell Error : Tried to overwrite an existing state"), + } + } +} + +#[derive(Clone)] +pub struct Cell { + state: Option, + all_states: Vec, + allowed_states: Vec, +} + +impl Cell { + pub fn new(states: Vec) -> Self { + Self { + state: None, + all_states: states.clone(), + allowed_states: states, + } + } + + pub fn get(&self) -> Option { + return self.state + } + + pub fn get_allowed(&self) -> Vec { + return self.allowed_states.clone() + } + + pub fn get_num_allowed(&self) -> usize { + return self.allowed_states.len() + } + + pub fn is_none(&self) -> bool { + return self.state.is_none() + } + + pub fn collapse(&mut self, option: &CollapseOption) -> Result { + if !self.state.is_none() { + return Err(CellError::StateAlreadySet) + } + if self.allowed_states.len() == 0 { + return Err(CellError::NoAllowedState) + } + if let CollapseOption::Set(state) = option { + if !self.allowed_states.contains(&state) { + return Err(CellError::StateNotAllowed) + } + self.state = Some(*state); + return Ok(*state) + } + let choice: usize = if let CollapseOption::Random = option { + thread_rng().gen_range(0..self.allowed_states.len()) + } else { + 0 + }; + self.state = Some(self.allowed_states[choice]); + return Ok(self.allowed_states[choice]) + } + + pub fn remove_allowed(&mut self, states: &HashSet) -> Result { + if !self.state.is_none() { + return Ok(RemoveResult::Filled) + } + + self.allowed_states.retain(|&x| !states.contains(&x)); + + if self.allowed_states.len() == 0 { + return Err(CellError::NoAllowedState) + } + + if self.allowed_states.len() == 1 { + self.state = Some(self.allowed_states[0]); + return Ok(RemoveResult::Collapsed(self.allowed_states[0])) + } + return Ok(RemoveResult::NumAllowed(self.allowed_states.len())) + } + + pub fn reset_allowed(&mut self) { + self.allowed_states = self.all_states.clone(); + } + + pub fn reset(&mut self) { + self.state = None; + self.reset_allowed(); + } +} + +impl fmt::Display for Cell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Some(state) = self.state else { + return write!(f, " ") + }; + + let display_state = match STATE_DISPLAY.chars().nth(state) { + Some(state) => state.to_string(), + None => "#".to_string(), + }; + + write!(f, "{}", display_state) + } +} diff --git a/src/main.rs b/src/main.rs index ee6cde8..be498df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,18 @@ use std::fmt; 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; -const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +mod cell; +use cell::Cell; +use cell::CollapseOption; -enum LineType { - Top, - Middle, - Bottom, -} +mod ui; +use ui::DisplayMode; enum WaveError { Contradiction, - NoEmptyCell, - NoPossibility, NoHistory, } @@ -26,135 +20,43 @@ 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), -} - -#[derive(Clone)] -struct Cell { - value: Option, - max: usize, - possibilities: Vec -} - -impl Cell { - fn set(&mut self, value: usize) { - self.value = if value > 0 && value <= self.max { - Some(value) - } else { - None - }; - } - - fn reset_possibilities(&mut self) { - self.possibilities = (1..=self.max).collect(); - } - - fn remove_possibilities(&mut self, possibilities: &HashSet) { - self.possibilities.retain(|&x| !possibilities.contains(&x)) - } - - fn new(max: usize) -> Self { - Self { - value: None, - max, - possibilities: (1..=max).collect(), - } - } -} - -impl fmt::Display for Cell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - 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) - } -} - -struct Choice { +struct Step { cell_selected: [usize; 2], - possibilities: Vec, - selected_value: usize, + state_selected: usize, + num_allowed_states: usize, } struct Sudoku { grid: Vec>, - rng: rand::rngs::ThreadRng, - history: Vec, + history: Vec, + last_history: usize, size: usize, square_size: usize, + debug_display: bool, + collapse_option: CollapseOption, } impl Sudoku { fn new(order: usize) -> Self { let size = order*order; - let sudoku_grid: Vec> = vec![vec![Cell::new(size); size]; size]; + let states = (1..=size).collect(); + let sudoku_grid: Vec> = vec![vec![Cell::new(states); size]; size]; Self { grid: sudoku_grid, - rng: rand::thread_rng(), history: vec![], + last_history: 0, size, square_size: order, + debug_display: false, + collapse_option: CollapseOption::Random, } } - 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); - } - - 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) -> Result<(), WaveError> { let mut row_used_values: Vec> = vec![HashSet::new(); self.size]; let mut column_used_values: Vec> = vec![HashSet::new(); self.size]; @@ -162,7 +64,7 @@ impl Sudoku { 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 { + let Some(value) = self.grid[row_index][column_index].get() else { continue }; if row_used_values[row_index].contains(&value) || column_used_values[column_index].contains(&value) || column_used_values[column_index].contains(&value) { @@ -173,53 +75,128 @@ impl Sudoku { square_used_values[row_index/self.square_size][column_index/self.square_size].insert(value); } } - + 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); + self.remove_allowed(row_index, column_index, &used_values)?; } } Ok(()) } - fn propagate_collapse(&mut self, _debug_display: bool) -> Result<(), WaveError> { - let Some(last_choice) = self.history.last() else { - 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 { + fn propagate_collapse(&mut self) -> Result<(), WaveError> { + if self.last_history >= self.history.len() { + if self.debug_display { + println!("x nothing to propagate") + } return Ok(()) - }; + } + + let collapsed_row = self.history[self.last_history].cell_selected[0]; + let collapsed_column = self.history[self.last_history].cell_selected[1]; + let collapsed_state = self.history[self.last_history].state_selected; + self.last_history += 1; - let mut collapsed_possibility = HashSet::new(); - collapsed_possibility.insert(collapsed_value); - - for column_index in 0..self.size { - self.grid[collapsed_row][column_index].remove_possibilities(&collapsed_possibility); + if self.debug_display { + println!("- propagating {}", collapsed_state); + } + + let mut collapsed_possibility = HashSet::new(); + collapsed_possibility.insert(collapsed_state); + + for column_index in 0..self.size { + if column_index == collapsed_column { + continue; + } + if self.grid[collapsed_row][column_index].get() == Some(collapsed_state) { + return Err(WaveError::Contradiction) + } + self.remove_allowed(collapsed_row, column_index, &collapsed_possibility)?; } - for row_index in 0..self.size { - self.grid[row_index][collapsed_column].remove_possibilities(&collapsed_possibility); + if row_index == collapsed_row { + continue; + } + if self.grid[row_index][collapsed_column].get() == Some(collapsed_state) { + return Err(WaveError::Contradiction) + } + self.remove_allowed(row_index, collapsed_column, &collapsed_possibility)?; } - for row_index in 0..self.square_size { for column_index in 0..self.square_size { let row = (collapsed_row/self.square_size)*self.square_size + row_index; let column = (collapsed_column/self.square_size)*self.square_size + column_index; - self.grid[row][column].remove_possibilities(&collapsed_possibility); + if row == collapsed_row && column == collapsed_column { + continue; + } + if self.grid[row][column].get() == Some(collapsed_state) { + return Err(WaveError::Contradiction) + } + self.remove_allowed(row, column, &collapsed_possibility)?; } } - Ok(()) - } - fn collapse(&mut self, debug_display: bool) -> Result<(), WaveError> { + let mut missing_states: HashSet = (1..=self.size).collect(); + for column_index in 0..self.size { + if let Some(state) = self.grid[collapsed_row][column_index].get() { + missing_states.remove(&state); + continue + } + for allowed_state in self.grid[collapsed_row][column_index].get_allowed() { + missing_states.remove(&allowed_state); + } + } + if !missing_states.is_empty() { + if self.debug_display { + println!("xxxxxxxx missing row state : {:?}", missing_states); + } + return Err(WaveError::Contradiction) + } + missing_states = (1..=self.size).collect(); + for row_index in 0..self.size { + if let Some(state) = self.grid[row_index][collapsed_column].get() { + missing_states.remove(&state); + continue + } + for allowed_state in self.grid[row_index][collapsed_column].get_allowed() { + missing_states.remove(&allowed_state); + } + } + if !missing_states.is_empty() { + if self.debug_display { + println!("xxxxxxxxx missing column state"); + } + return Err(WaveError::Contradiction) + } + missing_states = (1..=self.size).collect(); + for row_index in 0..self.square_size { + for column_index in 0..self.square_size { + let row = (collapsed_row/self.square_size)*self.square_size + row_index; + let column = (collapsed_column/self.square_size)*self.square_size + column_index; + if let Some(state) = self.grid[row][column].get() { + missing_states.remove(&state); + continue + } + for allowed_state in self.grid[row][column].get_allowed() { + missing_states.remove(&allowed_state); + } + } + } + if !missing_states.is_empty() { + if self.debug_display { + println!("xxxxxxxxxx missing square state"); + } + return Err(WaveError::Contradiction) + } + + Ok(()) + } + + fn collapse(&mut self) -> Result<(), WaveError> { let mut min_row_index: usize = 0; let mut min_column_index: usize = 0; let mut min_len: usize = self.size; @@ -228,11 +205,11 @@ impl Sudoku { for row_index in 0..self.size { for column_index in 0..self.size { - if !self.grid[row_index][column_index].value.is_none() { + if !self.grid[row_index][column_index].is_none() { continue; } grid_has_empty_cell = true; - let possibilities_len = self.grid[row_index][column_index].possibilities.len(); + let possibilities_len = self.grid[row_index][column_index].get_num_allowed(); if possibilities_len < min_len { min_row_index = row_index; min_column_index = column_index; @@ -242,169 +219,252 @@ impl Sudoku { } if !grid_has_empty_cell { - if debug_display { + if self.debug_display { println!("x no empty cells"); } - return Err(WaveError::NoEmptyCell) - } - - 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; + return self.collapse_cell(min_row_index, min_column_index) +// let Err(reason) = self.collapse_cell(min_row_index, min_column_index) else { +// return Ok(()) +// }; +// +// if let WaveError::Contradiction = reason { +// return self.backtrack() +// } else { +// return Err(reason) +// } + } +// + fn backtrack(&mut self) -> Result<(), WaveError> { + let mut fork: Option = None; - while let Some(choice) = self.history.pop() { - 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); + while let Some(step) = self.history.pop() { + self.last_history -= 1; + self.grid[step.cell_selected[0]][step.cell_selected[1]].reset(); + if step.num_allowed_states > 1 { + fork = Some(step); break; } + if self.debug_display { + println!("* backtracking [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected); + } } - let Some(choice) = fork else { - if debug_display { + let Some(step) = fork else { + if self.debug_display { println!("x backtracked to start"); } return Err(WaveError::NoHistory) }; + if self.debug_display { + println!("* fork [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected); + } - self.reset_possibilities()?; + self.reset_allowed(); - 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) + let mut state_selected_set = HashSet::new(); + state_selected_set.insert(step.state_selected); + + self.remove_allowed(step.cell_selected[0], step.cell_selected[1], &state_selected_set)?; + Ok(()) } - - fn reset_possibilities(&mut self) -> Result<(), WaveError> { + + fn reset_allowed(&mut self) { for row in &mut self.grid { for cell in row { - cell.reset_possibilities(); + cell.reset_allowed(); } } - - return self.update_possibilities(); } - 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 Err(WaveError::NoPossibility) - }; - - self.history.push(Choice { - cell_selected: [row_index, column_index], - possibilities: self.grid[row_index][column_index].possibilities.clone(), - selected_value, - }); - - self.grid[row_index][column_index].set(selected_value); - if debug_display { - println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); + fn remove_allowed(&mut self, row_index: usize, column_index: usize, set_to_remove: &HashSet) -> Result<(), WaveError> { + match self.grid[row_index][column_index].remove_allowed(set_to_remove) { + Ok(result) => { + let cell::RemoveResult::Collapsed(state) = result else { + return Ok(()) + }; + if self.debug_display { + println!("* collapsed by removal [{}][{}] to {}", row_index, column_index, state) + } + self.history.push(Step { + cell_selected: [row_index, column_index], + state_selected: state, + num_allowed_states: 1, + }); + return Ok(()) + } + Err(reason) => { + if self.debug_display { + println!("x failed to update states allowed of [{}][{}] : {}", row_index, column_index, reason); + } + return Err(WaveError::Contradiction) + } + } + } + + fn collapse_cell(&mut self, row_index: usize, column_index: usize) -> Result<(), WaveError> { + match self.grid[row_index][column_index].collapse(&self.collapse_option) { + Ok(state_selected) => { + let num_allowed_states = self.grid[row_index][column_index].get_num_allowed(); + if self.debug_display { + println!("# collapsing [{}][{}] ({}) to {}", row_index, column_index, num_allowed_states, state_selected); + } + self.history.push(Step { + cell_selected: [row_index, column_index], + state_selected, + num_allowed_states, + }); + return Ok(()) + } + Err(reason) => { + if self.debug_display { + println!("x could not collapse [{}][{}] : {}", row_index, column_index, reason); + } + return Err(WaveError::Contradiction) + } } - Ok(()) } +// +// fn solve(&mut self) -> Result<(), WaveError> { +// let now = Instant::now(); +// +// self.display(DisplayMode::Full); +// if self.debug_display { +// println!("--------"); +// } +// +// +// let initial_grid = self.grid.clone(); +// +// println!("# started"); +// +// self.update_possibilities()?; +// +// let mut step_counter: usize = 0; +// let mut reset_counter: usize = 0; +// +// loop { +// match self.collapse() { +// Ok(()) => {} +// Err(reason) => { +// if let WaveError::NoEmptyCell = reason { +// break; +// } else { +// return Err(reason) +// } +// } +// } +// self.propagate_collapse()?; +// +// +// if step_counter%(2*self.size*self.size) == 0 { +// self.grid = initial_grid.clone(); +// self.reset_allowed(); +// self.history = vec![]; +// self.update_possibilities()?; +// reset_counter += 1; +// } +// +// if !self.debug_display { +// let bar_size = (self.size + self.square_size - 1)*2 + 1; +// let progress = self.history.len()*bar_size/(self.size*self.size - filled_cells_number); +// let to_do = bar_size - progress; +// print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do)); +// } +// step_counter += 1; +// } +// +// println!(); +// +// if reset_counter > 0 { +// println!("# {} resets", reset_counter); +// } +// self.display(DisplayMode::Full); +// +// Ok(()) +// } - fn solve(&mut self, debug_display: bool) -> Result<(), WaveError> { + fn solve(&mut self) -> Result<(), WaveError> { let now = Instant::now(); - self.display(DisplayMode::Full); - if debug_display { - println!("--------"); - } - - let mut filled_cells_number: usize = 0; + let mut n_start_cells: usize = 0; for row in &self.grid { for cell in row { - if !cell.value.is_none() { - filled_cells_number += 1; + if !cell.is_none() { + n_start_cells += 1; } } } - - let initial_grid = self.grid.clone(); - + let mut propagation_counter: usize = 0; + let mut collapse_counter: usize = 0; println!("# started"); - - self.update_possibilities()?; - - let mut step_counter: usize = 0; - let mut reset_counter: usize = 0; - - while let Ok(_) = self.collapse(debug_display) { - //self.update_possibilities(); - self.propagate_collapse(debug_display)?; - step_counter += 1; + if self.debug_display { + println!("--------"); + } + self.update_possibilities()?; - if step_counter%(2*self.size*self.size) == 0 { - self.grid = initial_grid.clone(); - self.update_possibilities()?; - self.history = vec![]; - reset_counter += 1; - } + while n_start_cells + self.history.len() < self.size * self.size { + if self.debug_display { + println!("## while, h={}/{}", self.last_history, self.history.len()); + } + while self.last_history < self.history.len() && n_start_cells + self.history.len() < self.size * self.size { + let mut backtrack = 0; + match self.propagate_collapse() { + Ok(_) => {}, + Err(reason) => { + if let WaveError::Contradiction = reason { + backtrack = 1; + } else { + return Err(reason) + } + } + }; + while backtrack > 0 { + backtrack -=1; + self.backtrack()?; + match self.update_possibilities() { + Ok(_) => {}, + Err(reason) => { + if let WaveError::Contradiction = reason { + backtrack += 1; + } else { + return Err(reason) + } + } + }; + } + propagation_counter += 1; + } + self.collapse()?; + collapse_counter += 1; - if !debug_display { + if !self.debug_display { let bar_size = (self.size + self.square_size - 1)*2 + 1; - let progress = self.history.len()*bar_size/(self.size*self.size - filled_cells_number); + let progress = self.history.len()*bar_size/(self.size*self.size - n_start_cells); let to_do = bar_size - progress; print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do)); } - } - - println!(); + } + println!(); - let elapsed = now.elapsed(); - - if debug_display { + if self.debug_display { println!("--------"); } - 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(()) + let elapsed = now.elapsed(); + println!("# finished in {} propagations ({} forced collapse), {:.2?} ({:.2?}/propagation)", propagation_counter, collapse_counter, elapsed, elapsed/(propagation_counter as u32)); + self.display(DisplayMode::Full); + Ok(()) } - 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 CHAR_SET.find(c) { - Some(value) => { - if value <= self.size { - self.grid[row_index][column_index].set(value); - } - } - None => {} - } - } - let height = self.size + self.square_size + 2; - print!("\x1b[{}A", height); - for _ in 0..height { - println!("{}"," ".repeat((self.size + self.square_size - 1)*2 + 5)); - } - print!("\x1b[{}A", height); + fn random_mode(&mut self, collapse_random: bool) { + if collapse_random { + self.collapse_option = CollapseOption::Random; + } else { + self.collapse_option = CollapseOption::First } - } + } } fn main() { @@ -419,13 +479,24 @@ fn main() { process::exit(1); }); - let mut sudoku = Sudoku::new(min(size, 5)); + let mut sudoku = Sudoku::new(size); if args.contains(&"--ask".to_string()) { - sudoku.ask(); + if let Err(reason) = sudoku.ask() { + println!("{}",reason); + } + } + + if args.contains(&"--debug".to_string()) { + sudoku.debug_mode(true); + } + + if args.contains(&"--norand".to_string()) { + sudoku.random_mode(false); } - if let Err(reason) = sudoku.solve(args.contains(&"--debug".to_string())) { + if let Err(reason) = sudoku.solve() { println!("{}",reason); + sudoku.display(DisplayMode::Full); } } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..ad6b100 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,90 @@ +use text_io::read; + +use crate::Sudoku; +use crate::cell; + +const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +enum LineType { + Top, + Middle, + Bottom, +} + +pub enum DisplayMode { + Full, + Focus(usize), +} + +impl Sudoku { + pub 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); + } + + 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); + } + + pub fn ask(&mut self) -> Result<(), cell::CellError> { + 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 CHAR_SET.find(c) { + Some(value) => { + if value <= self.size { + self.grid[row_index][column_index].collapse(&cell::CollapseOption::Set(value))?; + } + } + None => {} + } + } + let height = self.size + self.square_size + 2; + print!("\x1b[{}A", height); + for _ in 0..height { + println!("{}"," ".repeat((self.size + self.square_size - 1)*2 + 5)); + } + print!("\x1b[{}A", height); + } + Ok(()) + } + + pub fn debug_mode(&mut self, debug_display: bool) { + self.debug_display = debug_display; + } +}