meh, better architecture of the cells, but worse behavior
This commit is contained in:
parent
2f0dbb727d
commit
d901ea441d
132
src/cell.rs
Normal file
132
src/cell.rs
Normal file
|
@ -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<usize>,
|
||||||
|
all_states: Vec<usize>,
|
||||||
|
allowed_states: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cell {
|
||||||
|
pub fn new(states: Vec<usize>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: None,
|
||||||
|
all_states: states.clone(),
|
||||||
|
allowed_states: states,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<usize> {
|
||||||
|
return self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_allowed(&self) -> Vec<usize> {
|
||||||
|
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<usize, CellError> {
|
||||||
|
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<usize>) -> Result<RemoveResult, CellError> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
587
src/main.rs
587
src/main.rs
|
@ -1,24 +1,18 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use rand::seq::SliceRandom;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::process;
|
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 {
|
mod ui;
|
||||||
Top,
|
use ui::DisplayMode;
|
||||||
Middle,
|
|
||||||
Bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WaveError {
|
enum WaveError {
|
||||||
Contradiction,
|
Contradiction,
|
||||||
NoEmptyCell,
|
|
||||||
NoPossibility,
|
|
||||||
NoHistory,
|
NoHistory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,135 +20,43 @@ impl fmt::Display for WaveError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
WaveError::Contradiction => write!(f, "Error: The puzzle contradicts itself."),
|
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."),
|
WaveError::NoHistory => write!(f, "Error: Tried to backtrack but the History is empty."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DisplayMode {
|
struct Step {
|
||||||
Full,
|
|
||||||
Focus(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Cell {
|
|
||||||
value: Option<usize>,
|
|
||||||
max: usize,
|
|
||||||
possibilities: Vec<usize>
|
|
||||||
}
|
|
||||||
|
|
||||||
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<usize>) {
|
|
||||||
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 {
|
|
||||||
cell_selected: [usize; 2],
|
cell_selected: [usize; 2],
|
||||||
possibilities: Vec<usize>,
|
state_selected: usize,
|
||||||
selected_value: usize,
|
num_allowed_states: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Sudoku {
|
struct Sudoku {
|
||||||
grid: Vec<Vec<Cell>>,
|
grid: Vec<Vec<Cell>>,
|
||||||
rng: rand::rngs::ThreadRng,
|
history: Vec<Step>,
|
||||||
history: Vec<Choice>,
|
last_history: usize,
|
||||||
size: usize,
|
size: usize,
|
||||||
square_size: usize,
|
square_size: usize,
|
||||||
|
debug_display: bool,
|
||||||
|
collapse_option: CollapseOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sudoku {
|
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 states = (1..=size).collect();
|
||||||
|
let sudoku_grid: Vec<Vec<Cell>> = vec![vec![Cell::new(states); size]; size];
|
||||||
Self {
|
Self {
|
||||||
grid: sudoku_grid,
|
grid: sudoku_grid,
|
||||||
rng: rand::thread_rng(),
|
|
||||||
history: vec![],
|
history: vec![],
|
||||||
|
last_history: 0,
|
||||||
size,
|
size,
|
||||||
square_size: order,
|
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> {
|
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];
|
||||||
|
@ -162,7 +64,7 @@ impl Sudoku {
|
||||||
|
|
||||||
for row_index in 0..self.size {
|
for row_index in 0..self.size {
|
||||||
for column_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
|
continue
|
||||||
};
|
};
|
||||||
if row_used_values[row_index].contains(&value) || column_used_values[column_index].contains(&value) || column_used_values[column_index].contains(&value) {
|
if row_used_values[row_index].contains(&value) || column_used_values[column_index].contains(&value) || column_used_values[column_index].contains(&value) {
|
||||||
|
@ -180,46 +82,121 @@ impl Sudoku {
|
||||||
used_values.extend(&column_used_values[column_index]);
|
used_values.extend(&column_used_values[column_index]);
|
||||||
used_values.extend(&square_used_values[row_index/self.square_size][column_index/self.square_size]);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn propagate_collapse(&mut self, _debug_display: bool) -> Result<(), WaveError> {
|
fn propagate_collapse(&mut self) -> Result<(), WaveError> {
|
||||||
let Some(last_choice) = self.history.last() else {
|
if self.last_history >= self.history.len() {
|
||||||
return self.update_possibilities()
|
if self.debug_display {
|
||||||
};
|
println!("x nothing to propagate")
|
||||||
|
}
|
||||||
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 Ok(())
|
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;
|
||||||
|
|
||||||
|
if self.debug_display {
|
||||||
|
println!("- propagating {}", collapsed_state);
|
||||||
|
}
|
||||||
|
|
||||||
let mut collapsed_possibility = HashSet::new();
|
let mut collapsed_possibility = HashSet::new();
|
||||||
collapsed_possibility.insert(collapsed_value);
|
collapsed_possibility.insert(collapsed_state);
|
||||||
|
|
||||||
for column_index in 0..self.size {
|
for column_index in 0..self.size {
|
||||||
self.grid[collapsed_row][column_index].remove_possibilities(&collapsed_possibility);
|
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 {
|
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 row_index in 0..self.square_size {
|
||||||
for column_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 row = (collapsed_row/self.square_size)*self.square_size + row_index;
|
||||||
let column = (collapsed_column/self.square_size)*self.square_size + column_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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut missing_states: HashSet<usize> = (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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collapse(&mut self, debug_display: bool) -> Result<(), WaveError> {
|
fn collapse(&mut self) -> 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;
|
||||||
|
@ -228,11 +205,11 @@ impl Sudoku {
|
||||||
|
|
||||||
for row_index in 0..self.size {
|
for row_index in 0..self.size {
|
||||||
for column_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;
|
continue;
|
||||||
}
|
}
|
||||||
grid_has_empty_cell = true;
|
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 {
|
if possibilities_len < min_len {
|
||||||
min_row_index = row_index;
|
min_row_index = row_index;
|
||||||
min_column_index = column_index;
|
min_column_index = column_index;
|
||||||
|
@ -242,167 +219,250 @@ impl Sudoku {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !grid_has_empty_cell {
|
if !grid_has_empty_cell {
|
||||||
if debug_display {
|
if self.debug_display {
|
||||||
println!("x no empty cells");
|
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(())
|
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> {
|
return self.collapse_cell(min_row_index, min_column_index)
|
||||||
let mut fork: Option<Choice> = None;
|
// let Err(reason) = self.collapse_cell(min_row_index, min_column_index) else {
|
||||||
|
// return Ok(())
|
||||||
while let Some(choice) = self.history.pop() {
|
// };
|
||||||
if debug_display {
|
//
|
||||||
println!("* backtracking");
|
// if let WaveError::Contradiction = reason {
|
||||||
|
// return self.backtrack()
|
||||||
|
// } else {
|
||||||
|
// return Err(reason)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None;
|
//
|
||||||
if choice.possibilities.len() > 1 {
|
fn backtrack(&mut self) -> Result<(), WaveError> {
|
||||||
fork = Some(choice);
|
let mut fork: Option<Step> = None;
|
||||||
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
if self.debug_display {
|
||||||
|
println!("* backtracking [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(choice) = fork else {
|
let Some(step) = fork else {
|
||||||
if debug_display {
|
if self.debug_display {
|
||||||
println!("x backtracked to start");
|
println!("x backtracked to start");
|
||||||
}
|
}
|
||||||
return Err(WaveError::NoHistory)
|
return Err(WaveError::NoHistory)
|
||||||
};
|
};
|
||||||
|
if self.debug_display {
|
||||||
self.reset_possibilities()?;
|
println!("* fork [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected);
|
||||||
|
|
||||||
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) -> Result<(), WaveError> {
|
self.reset_allowed();
|
||||||
for row in &mut self.grid {
|
|
||||||
for cell in row {
|
|
||||||
cell.reset_possibilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.update_possibilities();
|
let mut state_selected_set = HashSet::new();
|
||||||
}
|
state_selected_set.insert(step.state_selected);
|
||||||
|
|
||||||
fn collapse_cell(&mut self, row_index: usize, column_index: usize, debug_display: bool) -> Result<(), WaveError> {
|
self.remove_allowed(step.cell_selected[0], step.cell_selected[1], &state_selected_set)?;
|
||||||
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);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve(&mut self, debug_display: bool) -> Result<(), WaveError> {
|
fn reset_allowed(&mut self) {
|
||||||
let now = Instant::now();
|
for row in &mut self.grid {
|
||||||
|
for cell in row {
|
||||||
self.display(DisplayMode::Full);
|
cell.reset_allowed();
|
||||||
if debug_display {
|
}
|
||||||
println!("--------");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut filled_cells_number: usize = 0;
|
fn remove_allowed(&mut self, row_index: usize, column_index: usize, set_to_remove: &HashSet<usize>) -> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 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) -> Result<(), WaveError> {
|
||||||
|
let now = Instant::now();
|
||||||
|
self.display(DisplayMode::Full);
|
||||||
|
let mut n_start_cells: usize = 0;
|
||||||
for row in &self.grid {
|
for row in &self.grid {
|
||||||
for cell in row {
|
for cell in row {
|
||||||
if !cell.value.is_none() {
|
if !cell.is_none() {
|
||||||
filled_cells_number += 1;
|
n_start_cells += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut propagation_counter: usize = 0;
|
||||||
let initial_grid = self.grid.clone();
|
let mut collapse_counter: usize = 0;
|
||||||
|
|
||||||
println!("# started");
|
println!("# started");
|
||||||
|
if self.debug_display {
|
||||||
self.update_possibilities()?;
|
println!("--------");
|
||||||
|
|
||||||
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 step_counter%(2*self.size*self.size) == 0 {
|
|
||||||
self.grid = initial_grid.clone();
|
|
||||||
self.update_possibilities()?;
|
|
||||||
self.history = vec![];
|
|
||||||
reset_counter += 1;
|
|
||||||
}
|
}
|
||||||
|
self.update_possibilities()?;
|
||||||
|
|
||||||
if !debug_display {
|
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 !self.debug_display {
|
||||||
let bar_size = (self.size + self.square_size - 1)*2 + 1;
|
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;
|
let to_do = bar_size - progress;
|
||||||
print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do));
|
print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
if self.debug_display {
|
||||||
|
|
||||||
if debug_display {
|
|
||||||
println!("--------");
|
println!("--------");
|
||||||
}
|
}
|
||||||
println!("# finished in {} steps, {:.2?} ({:.2?}/step)", step_counter, elapsed, elapsed/(step_counter as u32));
|
let elapsed = now.elapsed();
|
||||||
if reset_counter > 0 {
|
println!("# finished in {} propagations ({} forced collapse), {:.2?} ({:.2?}/propagation)", propagation_counter, collapse_counter, elapsed, elapsed/(propagation_counter as u32));
|
||||||
println!("# {} resets", reset_counter);
|
|
||||||
}
|
|
||||||
self.display(DisplayMode::Full);
|
self.display(DisplayMode::Full);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ask(&mut self) {
|
fn random_mode(&mut self, collapse_random: bool) {
|
||||||
for row_index in 0..self.size {
|
if collapse_random {
|
||||||
self.display(DisplayMode::Focus(row_index));
|
self.collapse_option = CollapseOption::Random;
|
||||||
print!("> ");
|
} else {
|
||||||
let line: String = read!("{}\n");
|
self.collapse_option = CollapseOption::First
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,13 +479,24 @@ fn main() {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut sudoku = Sudoku::new(min(size, 5));
|
let mut sudoku = Sudoku::new(size);
|
||||||
|
|
||||||
if args.contains(&"--ask".to_string()) {
|
if args.contains(&"--ask".to_string()) {
|
||||||
sudoku.ask();
|
if let Err(reason) = sudoku.ask() {
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(reason) = sudoku.solve(args.contains(&"--debug".to_string())) {
|
|
||||||
println!("{}",reason);
|
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() {
|
||||||
|
println!("{}",reason);
|
||||||
|
sudoku.display(DisplayMode::Full);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
90
src/ui.rs
Normal file
90
src/ui.rs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue