Compare commits
10 commits
a9aa010af7
...
28cd9c2b8e
Author | SHA1 | Date | |
---|---|---|---|
28cd9c2b8e | |||
684ab1565f | |||
4be954b9d0 | |||
78fc4bc205 | |||
9dd739ffcc | |||
2f3555b303 | |||
855472e42a | |||
f9dda499e2 | |||
3e3245a977 | |||
2678a5d957 |
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Jiro
|
||||||
|
(from the wind rises)
|
||||||
|
|
||||||
|
A sudoku solver (can take blank input) for size n
|
346
src/main.rs
346
src/main.rs
|
@ -1,326 +1,7 @@
|
||||||
use std::fmt;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::time::Instant;
|
|
||||||
use clap::{Parser};
|
use clap::{Parser};
|
||||||
|
|
||||||
mod cell;
|
|
||||||
mod ui;
|
|
||||||
mod solver;
|
mod solver;
|
||||||
|
|
||||||
enum WaveError {
|
|
||||||
Contradiction,
|
|
||||||
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::NoHistory => write!(f, "Error: Tried to backtrack but the History is empty."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct Step {
|
|
||||||
position: (usize, usize),
|
|
||||||
state_set: usize,
|
|
||||||
num_allowed_states: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Step {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "({},{}) to {}, out of {}", self.position.0, self.position.1, self.state_set, self.num_allowed_states)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Solver {
|
|
||||||
grid: Vec<Vec<cell::Cell>>,
|
|
||||||
history: Vec<Step>,
|
|
||||||
last_move_index: usize,
|
|
||||||
size: usize,
|
|
||||||
square_size: usize,
|
|
||||||
debug_display: bool,
|
|
||||||
grid_display: bool,
|
|
||||||
collapse_option: cell::CollapseOption,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Solver {
|
|
||||||
fn new(order: usize) -> Self {
|
|
||||||
let size = order*order;
|
|
||||||
let states = (1..=size).collect();
|
|
||||||
let sudoku_grid: Vec<Vec<cell::Cell>> = vec![vec![cell::Cell::new(states); size]; size];
|
|
||||||
Self {
|
|
||||||
grid: sudoku_grid,
|
|
||||||
history: vec![],
|
|
||||||
last_move_index: 0,
|
|
||||||
size,
|
|
||||||
square_size: order,
|
|
||||||
debug_display: false,
|
|
||||||
grid_display: false,
|
|
||||||
collapse_option: cell::CollapseOption::Random,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_possibilities(&mut self) -> Result<(), WaveError> {
|
|
||||||
let mut row_used_states: Vec<Vec<cell::BlockingCell>> = vec![vec![]; self.size];
|
|
||||||
let mut column_used_states: Vec<Vec<cell::BlockingCell>> = vec![vec![]; self.size];
|
|
||||||
let mut square_used_states: Vec<Vec<Vec<cell::BlockingCell>>> = vec![vec![vec![]; self.square_size]; self.square_size];
|
|
||||||
|
|
||||||
for row_index in 0..self.size {
|
|
||||||
for column_index in 0..self.size {
|
|
||||||
let Some(state) = self.grid[row_index][column_index].get_state() else {
|
|
||||||
continue
|
|
||||||
};
|
|
||||||
let blocking_cell = cell::BlockingCell {
|
|
||||||
state,
|
|
||||||
position: (row_index, column_index),
|
|
||||||
};
|
|
||||||
row_used_states[row_index].push(blocking_cell.clone());
|
|
||||||
column_used_states[column_index].push(blocking_cell.clone());
|
|
||||||
square_used_states[row_index/self.square_size][column_index/self.square_size].push(blocking_cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for row_index in 0..self.size {
|
|
||||||
for column_index in 0..self.size {
|
|
||||||
self.remove_allowed((row_index, column_index), &row_used_states[row_index]
|
|
||||||
.iter()
|
|
||||||
.chain(&column_used_states[column_index])
|
|
||||||
.chain(&square_used_states[row_index / self.square_size][column_index / self.square_size])
|
|
||||||
.cloned()
|
|
||||||
.collect())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn propagate_collapse(&mut self) -> Result<(), WaveError> {
|
|
||||||
if self.last_move_index >= self.history.len() {
|
|
||||||
self.debug(&format!("x nothing to propagate"));
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let last_move = self.history[self.last_move_index].clone();
|
|
||||||
self.last_move_index += 1;
|
|
||||||
|
|
||||||
self.debug(&format!("- propagating {}", last_move));
|
|
||||||
|
|
||||||
let collapsed_possibility = vec![cell::BlockingCell {
|
|
||||||
state: last_move.state_set,
|
|
||||||
position: last_move.position,
|
|
||||||
}];
|
|
||||||
|
|
||||||
for index in 0..self.size {
|
|
||||||
if index != last_move.position.1 {
|
|
||||||
self.remove_allowed((last_move.position.0, index), &collapsed_possibility)?;
|
|
||||||
}
|
|
||||||
if index != last_move.position.0 {
|
|
||||||
self.remove_allowed((index, last_move.position.1), &collapsed_possibility)?;
|
|
||||||
}
|
|
||||||
let square_position = ((last_move.position.0/self.square_size)*self.square_size + index/self.square_size,
|
|
||||||
(last_move.position.1/self.square_size)*self.square_size + index%self.square_size);
|
|
||||||
if square_position.0 != last_move.position.0 || square_position.1 != last_move.position.1 {
|
|
||||||
self.remove_allowed(square_position, &collapsed_possibility)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.check_impossible(last_move.position)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_impossible(&self, center_position: (usize, usize)) -> Result<(),WaveError> {
|
|
||||||
let mut missing_states: HashSet<usize> = (1..=self.size).collect();
|
|
||||||
for column in 0..self.size {
|
|
||||||
let existing_states = self.grid[center_position.0][column].get_existing();
|
|
||||||
missing_states.retain(|state| !existing_states.contains(state));
|
|
||||||
}
|
|
||||||
if !missing_states.is_empty() {
|
|
||||||
self.debug(&format!("x missing row state : {:?}", missing_states));
|
|
||||||
return Err(WaveError::Contradiction)
|
|
||||||
}
|
|
||||||
|
|
||||||
missing_states = (1..=self.size).collect();
|
|
||||||
for row in 0..self.size {
|
|
||||||
let existing_states = self.grid[row][center_position.1].get_existing();
|
|
||||||
missing_states.retain(|state| !existing_states.contains(state));
|
|
||||||
}
|
|
||||||
if !missing_states.is_empty() {
|
|
||||||
self.debug(&format!("x missing column state : {:?}", missing_states));
|
|
||||||
return Err(WaveError::Contradiction)
|
|
||||||
}
|
|
||||||
missing_states = (1..=self.size).collect();
|
|
||||||
for square_index in 0..self.size {
|
|
||||||
let row = (center_position.0/self.square_size)*self.square_size + square_index/self.square_size;
|
|
||||||
let column = (center_position.1/self.square_size)*self.square_size + square_index%self.square_size;
|
|
||||||
let existing_states = self.grid[row][column].get_existing();
|
|
||||||
missing_states.retain(|state| !existing_states.contains(state));
|
|
||||||
}
|
|
||||||
if !missing_states.is_empty() {
|
|
||||||
self.debug(&format!("x missing square state : {:?}", missing_states));
|
|
||||||
return Err(WaveError::Contradiction)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collapse(&mut self) -> Result<(), WaveError> {
|
|
||||||
let mut min_allowed_position: (usize, usize) = (0, 0);
|
|
||||||
let mut min_allowed_number: usize = self.size;
|
|
||||||
|
|
||||||
let mut grid_has_empty_cell: bool = false;
|
|
||||||
|
|
||||||
for row_index in 0..self.size {
|
|
||||||
for column_index in 0..self.size {
|
|
||||||
if !self.grid[row_index][column_index].is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
grid_has_empty_cell = true;
|
|
||||||
let possibilities_len = self.grid[row_index][column_index].get_num_allowed();
|
|
||||||
if possibilities_len < min_allowed_number {
|
|
||||||
min_allowed_position = (row_index, column_index);
|
|
||||||
min_allowed_number = possibilities_len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !grid_has_empty_cell {
|
|
||||||
self.debug("x no empty cells");
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.collapse_cell(min_allowed_position)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backtrack(&mut self) -> Result<(), WaveError> {
|
|
||||||
let mut fork: Option<Step> = None;
|
|
||||||
while let Some(step) = self.history.pop() {
|
|
||||||
self.last_move_index -= 1;
|
|
||||||
self.grid[step.position.0][step.position.1].reset_state();
|
|
||||||
|
|
||||||
let blocking_cell = cell::BlockingCell {
|
|
||||||
state: step.state_set,
|
|
||||||
position: step.position,
|
|
||||||
};
|
|
||||||
self.propagate_backtrack(step.position, blocking_cell);
|
|
||||||
if step.num_allowed_states > 1 {
|
|
||||||
fork = Some(step);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.debug(&format!("* backtracking [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(step) = fork else {
|
|
||||||
self.debug("x backtracked to start");
|
|
||||||
return Err(WaveError::NoHistory)
|
|
||||||
};
|
|
||||||
self.debug(&format!("* fork [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
|
|
||||||
|
|
||||||
let mut state_selected_set = HashSet::new();
|
|
||||||
state_selected_set.insert(step.state_set);
|
|
||||||
|
|
||||||
let blocking_cell = cell::BlockingCell {
|
|
||||||
state: step.state_set,
|
|
||||||
position: step.position,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.remove_allowed(step.position, &vec![blocking_cell])?;
|
|
||||||
self.debug(&format!("- removed : {}, available : {:?}", step.state_set, self.grid[step.position.0][step.position.1].get_allowed()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn propagate_backtrack(&mut self, cell_pos: (usize, usize), removed_cell: cell::BlockingCell) {
|
|
||||||
for index in 0..self.size {
|
|
||||||
if index != cell_pos.0 {
|
|
||||||
self.grid[index][cell_pos.1].add_allowed(&removed_cell);
|
|
||||||
}
|
|
||||||
if index != cell_pos.1 {
|
|
||||||
self.grid[cell_pos.0][index].add_allowed(&removed_cell);
|
|
||||||
}
|
|
||||||
let square_row = (cell_pos.0/self.square_size)*self.square_size + index/self.square_size;
|
|
||||||
let square_column = (cell_pos.1/self.square_size)*self.square_size + index%self.square_size;
|
|
||||||
if square_row != cell_pos.0 || square_column != cell_pos.1 {
|
|
||||||
self.grid[square_row][square_column].add_allowed(&removed_cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn solve(&mut self, solver_limit: Option<usize>) -> Result<(), WaveError> {
|
|
||||||
let now = Instant::now();
|
|
||||||
self.display(ui::DisplayMode::Full);
|
|
||||||
let mut number_cell_init: usize = 0;
|
|
||||||
for row in &self.grid {
|
|
||||||
for cell in row {
|
|
||||||
if !cell.is_none() {
|
|
||||||
number_cell_init += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut propagation_counter: usize = 0;
|
|
||||||
let mut collapse_counter: usize = 0;
|
|
||||||
println!("# started");
|
|
||||||
self.debug("--------");
|
|
||||||
self.update_possibilities()?;
|
|
||||||
|
|
||||||
while number_cell_init + self.history.len() < self.size * self.size {
|
|
||||||
self.debug(&format!("\n## while, h={}/{}", self.last_move_index, self.history.len()));
|
|
||||||
while self.last_move_index < self.history.len() && number_cell_init + 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()?;
|
|
||||||
}
|
|
||||||
propagation_counter += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.grid_display {
|
|
||||||
if !self.debug_display {
|
|
||||||
self.display(ui::DisplayMode::Erase);
|
|
||||||
} else {
|
|
||||||
self.display(ui::DisplayMode::Full);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.collapse()?;
|
|
||||||
collapse_counter += 1;
|
|
||||||
|
|
||||||
if !self.debug_display && !self.grid_display {
|
|
||||||
self.progress_bar(number_cell_init);
|
|
||||||
}
|
|
||||||
if let Some(limit) = solver_limit {
|
|
||||||
if collapse_counter >= limit {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.debug("--------");
|
|
||||||
let elapsed = now.elapsed();
|
|
||||||
println!("\n# finished in {} propagations ({} forced collapse), {:.2?} ({:.2?}/propagation)", propagation_counter, collapse_counter, elapsed, elapsed/(propagation_counter as u32));
|
|
||||||
if !self.grid_display || self.debug_display {
|
|
||||||
self.display(ui::DisplayMode::Full);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_mode(&mut self, collapse_random: bool) {
|
|
||||||
if collapse_random {
|
|
||||||
self.collapse_option = cell::CollapseOption::Random;
|
|
||||||
} else {
|
|
||||||
self.collapse_option = cell::CollapseOption::First
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -340,6 +21,10 @@ struct Args {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
limit: Option<usize>, // Optional value
|
limit: Option<usize>, // Optional value
|
||||||
|
|
||||||
|
/// Solve n times and averages the results
|
||||||
|
#[arg(long)]
|
||||||
|
benchmark: Option<usize>, // Optional value
|
||||||
|
|
||||||
/// Disable randomization of the collapse
|
/// Disable randomization of the collapse
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
norand: bool,
|
norand: bool,
|
||||||
|
@ -347,13 +32,17 @@ struct Args {
|
||||||
/// Display grid when solving
|
/// Display grid when solving
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
grid: bool,
|
grid: bool,
|
||||||
|
|
||||||
|
/// Create puzzle
|
||||||
|
#[arg(long)]
|
||||||
|
puzzle: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let mut solver = Solver::new(args.size);
|
let mut solver = solver::Solver::new(args.size);
|
||||||
|
|
||||||
if args.ask {
|
if args.ask {
|
||||||
if let Err(reason) = solver.ask() {
|
if let Err(reason) = solver.ask() {
|
||||||
|
@ -363,9 +52,22 @@ fn main() {
|
||||||
solver.debug_mode(args.debug);
|
solver.debug_mode(args.debug);
|
||||||
solver.random_mode(!args.norand);
|
solver.random_mode(!args.norand);
|
||||||
solver.grid_display_mode(args.grid);
|
solver.grid_display_mode(args.grid);
|
||||||
|
|
||||||
|
if let Some(number) = args.benchmark {
|
||||||
|
solver.benchmark(number);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if let Some(difficulty) = args.puzzle {
|
||||||
|
solver.make_puzzle(difficulty).map(|_| ())
|
||||||
|
} else {
|
||||||
|
solver.solve(args.limit).map(|_| ())
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(reason) = solver.solve(args.limit) {
|
if let Err(reason) = result {
|
||||||
println!("{}", reason);
|
println!("{}", reason);
|
||||||
solver.display(ui::DisplayMode::Full);
|
if args.debug {
|
||||||
|
solver.display(solver::ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
use crate::Solver;
|
|
||||||
use crate::WaveError;
|
|
||||||
use crate::Step;
|
|
||||||
use crate::cell;
|
|
||||||
|
|
||||||
impl Solver {
|
|
||||||
pub fn remove_allowed(&mut self, position: (usize, usize), blocking_cells: &Vec<cell::BlockingCell>) -> Result<(), WaveError> {
|
|
||||||
match self.grid[position.0][position.1].remove_allowed(blocking_cells) {
|
|
||||||
Ok(result) => {
|
|
||||||
let cell::RemoveResult::Collapsed(state_set) = result else {
|
|
||||||
return Ok(())
|
|
||||||
};
|
|
||||||
self.debug(&format!("* collapsed by removal ({},{}) to {}", position.0, position.1, state_set));
|
|
||||||
self.history.push(Step {
|
|
||||||
position,
|
|
||||||
state_set,
|
|
||||||
num_allowed_states: 1,
|
|
||||||
});
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
self.debug(&format!("x failed to update states allowed of ({},{}) : {}", position.0, position.1, reason));
|
|
||||||
return Err(WaveError::Contradiction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collapse_cell(&mut self, position: (usize, usize)) -> Result<(), WaveError> {
|
|
||||||
match self.grid[position.0][position.1].collapse(&self.collapse_option) {
|
|
||||||
Ok(state_set) => {
|
|
||||||
let num_allowed_states = self.grid[position.0][position.1].get_num_allowed();
|
|
||||||
self.debug(&format!("# collapsing ({},{}) ({}) to {}", position.0, position.1, num_allowed_states, state_set));
|
|
||||||
self.history.push(Step {position, state_set, num_allowed_states});
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
self.debug(&format!("x could not collapse [{}][{}] : {}", position.0, position.1, reason));
|
|
||||||
return Err(WaveError::Contradiction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -57,10 +57,6 @@ impl Cell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state(&self) -> Option<usize> {
|
|
||||||
self.state
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_allowed(&self) -> Vec<usize> {
|
pub fn get_allowed(&self) -> Vec<usize> {
|
||||||
self.blocking_states
|
self.blocking_states
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -68,6 +64,10 @@ impl Cell {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_state(&self) -> Option<usize> {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_existing(&self) -> Vec<usize> {
|
pub fn get_existing(&self) -> Vec<usize> {
|
||||||
if let Some(state) = self.state {
|
if let Some(state) = self.state {
|
||||||
vec![state]
|
vec![state]
|
||||||
|
@ -111,15 +111,18 @@ impl Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_allowed(&mut self, blocking_cells: &Vec<BlockingCell>) -> Result<RemoveResult, CellError> {
|
pub fn remove_allowed(&mut self, blocking_cells: &Vec<BlockingCell>) -> Result<RemoveResult, CellError> {
|
||||||
if !self.state.is_none() {
|
|
||||||
return Ok(RemoveResult::Filled)
|
|
||||||
}
|
|
||||||
|
|
||||||
for blocking_cell in blocking_cells {
|
for blocking_cell in blocking_cells {
|
||||||
if let Some(blocking) = self.blocking_states.get_mut(&blocking_cell.state) {
|
if let Some(blocking) = self.blocking_states.get_mut(&blocking_cell.state) {
|
||||||
blocking.push(blocking_cell.position);
|
blocking.push(blocking_cell.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(state) = self.state {
|
||||||
|
if self.blocking_states[&state].len() > 0 {
|
||||||
|
return Err(CellError::StateNotAllowed)
|
||||||
|
}
|
||||||
|
return Ok(RemoveResult::Filled)
|
||||||
|
}
|
||||||
|
|
||||||
let allowed_states = self.get_allowed();
|
let allowed_states = self.get_allowed();
|
||||||
|
|
434
src/solver/mod.rs
Normal file
434
src/solver/mod.rs
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::ops::MulAssign;
|
||||||
|
use std::fmt;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub mod sudoku;
|
||||||
|
pub mod ui;
|
||||||
|
mod cell;
|
||||||
|
|
||||||
|
pub enum WaveError {
|
||||||
|
Contradiction,
|
||||||
|
NoHistory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WaveError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
WaveError::Contradiction => write!(f, "Error: There is a contradiction in the puzzle."),
|
||||||
|
WaveError::NoHistory => write!(f, "Error: The puzzle is impossible (backtracked to start)."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Step {
|
||||||
|
position: (usize, usize),
|
||||||
|
state_set: usize,
|
||||||
|
num_allowed_states: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Step {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "({},{}) to {}, out of {}", self.position.0, self.position.1, self.state_set, self.num_allowed_states)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SolveResult {
|
||||||
|
total_time: Duration,
|
||||||
|
propagation: (usize, Duration),
|
||||||
|
collapse: (usize, Duration),
|
||||||
|
backtrack: (usize, Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SolveResult {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let propagation_percentage = (self.propagation.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
|
||||||
|
let collapse_percentage = (self.collapse.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
|
||||||
|
let backtrack_percentage = (self.backtrack.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
|
||||||
|
|
||||||
|
write!(f, "# total time : {:.2?} ({:.2?}% propagation, {:.2?}% forced collapse, {:.2?}% backtrack)\n- {} propagations ({:.2?}), {} forced collapse ({:.2?}), {} backtrack ({:.2?})",
|
||||||
|
self.total_time, propagation_percentage, collapse_percentage, backtrack_percentage,
|
||||||
|
self.propagation.0, (self.propagation.1.checked_div(self.propagation.0 as u32)).unwrap_or(std::time::Duration::ZERO),
|
||||||
|
self.collapse.0, (self.collapse.1.checked_div(self.collapse.0 as u32)).unwrap_or(std::time::Duration::ZERO),
|
||||||
|
self.backtrack.0, (self.backtrack.1.checked_div(self.backtrack.0 as u32)).unwrap_or(std::time::Duration::ZERO)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::AddAssign for SolveResult {
|
||||||
|
fn add_assign(&mut self, other: Self) {
|
||||||
|
self.total_time += other.total_time;
|
||||||
|
self.propagation.0 += other.propagation.0;
|
||||||
|
self.propagation.1 += other.propagation.1;
|
||||||
|
self.collapse.0 += other.collapse.0;
|
||||||
|
self.collapse.1 += other.collapse.1;
|
||||||
|
self.backtrack.0 += other.backtrack.0;
|
||||||
|
self.backtrack.1 += other.backtrack.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulAssign<f32> for SolveResult {
|
||||||
|
fn mul_assign(&mut self, factor: f32) {
|
||||||
|
// Scale the durations
|
||||||
|
self.total_time = scale_duration(self.total_time, factor);
|
||||||
|
self.propagation.1 = scale_duration(self.propagation.1, factor);
|
||||||
|
self.collapse.1 = scale_duration(self.collapse.1, factor);
|
||||||
|
self.backtrack.1 = scale_duration(self.backtrack.1, factor);
|
||||||
|
|
||||||
|
// Scale the usize counts (rounded to nearest integer)
|
||||||
|
self.propagation.0 = scale_usize(self.propagation.0, factor);
|
||||||
|
self.collapse.0 = scale_usize(self.collapse.0, factor);
|
||||||
|
self.backtrack.0 = scale_usize(self.backtrack.0, factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SolveResult {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
total_time: Duration::ZERO,
|
||||||
|
propagation: (0, Duration::ZERO),
|
||||||
|
collapse: (0, Duration::ZERO),
|
||||||
|
backtrack: (0, Duration::ZERO),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to scale `Duration` by `f32`
|
||||||
|
fn scale_duration(duration: Duration, factor: f32) -> Duration {
|
||||||
|
let nanos = duration.as_secs_f64() * (factor as f64);
|
||||||
|
Duration::from_secs_f64(nanos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to scale `usize` by `f32`
|
||||||
|
fn scale_usize(value: usize, factor: f32) -> usize {
|
||||||
|
((value as f64) * (factor as f64)).round() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Solver {
|
||||||
|
grid: Vec<Vec<cell::Cell>>,
|
||||||
|
history: Vec<Step>,
|
||||||
|
last_move_index: usize,
|
||||||
|
size: usize,
|
||||||
|
square_size: usize,
|
||||||
|
debug_display: bool,
|
||||||
|
grid_display: bool,
|
||||||
|
solve_display: bool,
|
||||||
|
solve_progress_display: bool,
|
||||||
|
collapse_option: cell::CollapseOption,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Solver {
|
||||||
|
pub fn new(order: usize) -> Self {
|
||||||
|
let size = order*order;
|
||||||
|
let states = (1..=size).collect();
|
||||||
|
let sudoku_grid: Vec<Vec<cell::Cell>> = vec![vec![cell::Cell::new(states); size]; size];
|
||||||
|
Self {
|
||||||
|
grid: sudoku_grid,
|
||||||
|
history: vec![],
|
||||||
|
last_move_index: 0,
|
||||||
|
size,
|
||||||
|
square_size: order,
|
||||||
|
debug_display: false,
|
||||||
|
grid_display: false,
|
||||||
|
solve_display: true,
|
||||||
|
solve_progress_display: true,
|
||||||
|
collapse_option: cell::CollapseOption::Random,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_puzzle(&mut self, difficulty: usize) -> Result<(), WaveError> {
|
||||||
|
self.solve_display = false;
|
||||||
|
println!("\n# generating full...");
|
||||||
|
let _ = self.solve(None)?;
|
||||||
|
|
||||||
|
println!("\n\n# erasing cells...");
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut min_num_allowed = 1;
|
||||||
|
|
||||||
|
while min_num_allowed <= difficulty {
|
||||||
|
let erase_cell = loop {
|
||||||
|
let pos = (rng.gen_range(0..self.size), rng.gen_range(0..self.size));
|
||||||
|
if let Some(state) = self.grid[pos.0][pos.1].get_state() {
|
||||||
|
self.grid[pos.0][pos.1].reset_state();
|
||||||
|
break (pos, state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let blocking_cell = cell::BlockingCell {
|
||||||
|
state: erase_cell.1,
|
||||||
|
position: erase_cell.0,
|
||||||
|
};
|
||||||
|
min_num_allowed = self.propagate_backtrack(erase_cell.0, blocking_cell);
|
||||||
|
if min_num_allowed > difficulty {
|
||||||
|
let _ = self.grid[erase_cell.0.0][erase_cell.0.1].collapse(&cell::CollapseOption::Set(erase_cell.1));
|
||||||
|
//println!("put back ({},{}) to {}", erase_cell.0.0, erase_cell.0.1, erase_cell.1);
|
||||||
|
//self.propagate_collapse(erase_cell.0, blocking_cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
self.solve_display = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_grid(&mut self) {
|
||||||
|
let states = (1..=self.size).collect();
|
||||||
|
let empty_grid = vec![vec![cell::Cell::new(states); self.size]; self.size];
|
||||||
|
self.grid = empty_grid.clone();
|
||||||
|
self.history = vec![];
|
||||||
|
self.last_move_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_mode(&mut self, collapse_random: bool) {
|
||||||
|
if collapse_random {
|
||||||
|
self.collapse_option = cell::CollapseOption::Random;
|
||||||
|
} else {
|
||||||
|
self.collapse_option = cell::CollapseOption::First
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn benchmark(&mut self, solve_number: usize) {
|
||||||
|
self.solve_display = false;
|
||||||
|
self.solve_progress_display = false;
|
||||||
|
println!();
|
||||||
|
if self.grid_display {
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
|
let mut results_sum: SolveResult = SolveResult::default();
|
||||||
|
let mut error_number: usize = 0;
|
||||||
|
for index in 0..solve_number {
|
||||||
|
self.reset_grid();
|
||||||
|
match self.solve(None) {
|
||||||
|
Ok(result) => {
|
||||||
|
if self.debug_display {
|
||||||
|
println!("\n{}", result);
|
||||||
|
}
|
||||||
|
results_sum += result;
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
if self.debug_display {
|
||||||
|
println!("{}", reason);
|
||||||
|
}
|
||||||
|
error_number += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.debug_display && !self.grid_display {
|
||||||
|
let progress = (index + 1) as f32 / solve_number as f32;
|
||||||
|
self.progress_bar(progress);
|
||||||
|
}
|
||||||
|
if self.grid_display {
|
||||||
|
if !self.debug_display {
|
||||||
|
self.display(ui::DisplayMode::Erase);
|
||||||
|
} else {
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.debug_display {
|
||||||
|
println!("\n---------");
|
||||||
|
} else {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
results_sum *= 1.0 / ((solve_number - error_number) as f32);
|
||||||
|
println!("\nbenchmark {} (n = {})\n{}\n({} errors)", self.square_size, solve_number, results_sum, error_number);
|
||||||
|
|
||||||
|
self.solve_display = true;
|
||||||
|
self.solve_progress_display = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve(&mut self, solver_limit: Option<usize>) -> Result<SolveResult, WaveError> {
|
||||||
|
let start_time = Instant::now();
|
||||||
|
if self.solve_display {
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut propagation_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
|
||||||
|
let mut collapse_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
|
||||||
|
let mut backtrack_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
|
||||||
|
self.debug("--------");
|
||||||
|
|
||||||
|
while self.history.len() < self.size * self.size {
|
||||||
|
self.debug(&format!("\n## while, h={}/{}", self.last_move_index, self.history.len()));
|
||||||
|
while self.last_move_index < self.history.len() && self.history.len() < self.size * self.size {
|
||||||
|
let mut need_backtrack = false;
|
||||||
|
|
||||||
|
let propagation_start = Instant::now();
|
||||||
|
match self.propagate_collapse() {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(reason) => {
|
||||||
|
if let WaveError::Contradiction = reason {
|
||||||
|
need_backtrack = true;
|
||||||
|
} else {
|
||||||
|
return Err(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
propagation_counter.0 += 1;
|
||||||
|
propagation_counter.1 += propagation_start.elapsed();
|
||||||
|
|
||||||
|
if need_backtrack {
|
||||||
|
let backtrack_start = Instant::now();
|
||||||
|
self.backtrack()?;
|
||||||
|
backtrack_counter.0 += 1;
|
||||||
|
backtrack_counter.1 += backtrack_start.elapsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.grid_display && self.solve_display {
|
||||||
|
if !self.debug_display {
|
||||||
|
self.display(ui::DisplayMode::Erase);
|
||||||
|
} else {
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let collapse_start = Instant::now();
|
||||||
|
self.collapse()?;
|
||||||
|
collapse_counter.0 += 1;
|
||||||
|
collapse_counter.1 += collapse_start.elapsed();
|
||||||
|
|
||||||
|
if !self.debug_display && !self.grid_display && self.solve_progress_display {
|
||||||
|
self.progress_bar(self.history.len() as f32/((self.size*self.size) as f32));
|
||||||
|
}
|
||||||
|
if let Some(limit) = solver_limit {
|
||||||
|
if collapse_counter.0 >= limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.debug("--------");
|
||||||
|
|
||||||
|
if (!self.grid_display || self.debug_display) && self.solve_display {
|
||||||
|
println!();
|
||||||
|
self.display(ui::DisplayMode::Full);
|
||||||
|
}
|
||||||
|
let total_time = start_time.elapsed();
|
||||||
|
let result = SolveResult {
|
||||||
|
total_time,
|
||||||
|
propagation : propagation_counter,
|
||||||
|
collapse : collapse_counter,
|
||||||
|
backtrack : backtrack_counter,
|
||||||
|
};
|
||||||
|
if self.solve_display {
|
||||||
|
println!("\n{}", result);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collapse(&mut self) -> Result<(), WaveError> {
|
||||||
|
let mut min_allowed_position: (usize, usize) = (0, 0);
|
||||||
|
let mut min_allowed_number: usize = self.size;
|
||||||
|
|
||||||
|
let mut grid_has_empty_cell: bool = false;
|
||||||
|
|
||||||
|
for row_index in 0..self.size {
|
||||||
|
for column_index in 0..self.size {
|
||||||
|
if !self.grid[row_index][column_index].is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
grid_has_empty_cell = true;
|
||||||
|
let possibilities_len = self.grid[row_index][column_index].get_num_allowed();
|
||||||
|
if possibilities_len < min_allowed_number {
|
||||||
|
min_allowed_position = (row_index, column_index);
|
||||||
|
min_allowed_number = possibilities_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !grid_has_empty_cell {
|
||||||
|
self.debug("x no empty cells");
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.collapse_cell(min_allowed_position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collapse_cell(&mut self, position: (usize, usize)) -> Result<(), WaveError> {
|
||||||
|
match self.grid[position.0][position.1].collapse(&self.collapse_option) {
|
||||||
|
Ok(state_set) => {
|
||||||
|
let num_allowed_states = self.grid[position.0][position.1].get_num_allowed();
|
||||||
|
self.debug(&format!("# collapsing ({},{}) ({}) to {}", position.0, position.1, num_allowed_states, state_set));
|
||||||
|
self.history.push(Step {position, state_set, num_allowed_states});
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
self.debug(&format!("x could not collapse [{}][{}] : {}", position.0, position.1, reason));
|
||||||
|
return Err(WaveError::Contradiction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_last_fork(&mut self) -> Result<Step, WaveError> {
|
||||||
|
let mut fork: Option<Step> = None;
|
||||||
|
while let Some(step) = self.history.pop() {
|
||||||
|
if self.last_move_index == 0 {
|
||||||
|
self.debug("there is no last move");
|
||||||
|
return Err(WaveError::NoHistory)
|
||||||
|
}
|
||||||
|
self.last_move_index -= 1;
|
||||||
|
self.grid[step.position.0][step.position.1].reset_state();
|
||||||
|
|
||||||
|
let blocking_cell = cell::BlockingCell {
|
||||||
|
state: step.state_set,
|
||||||
|
position: step.position,
|
||||||
|
};
|
||||||
|
self.propagate_backtrack(step.position, blocking_cell);
|
||||||
|
if step.num_allowed_states > 1 {
|
||||||
|
fork = Some(step);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.debug(&format!("* backtracking [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
|
||||||
|
}
|
||||||
|
match fork {
|
||||||
|
Some(step) => {
|
||||||
|
self.debug(&format!("* fork [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
|
||||||
|
Ok(step)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.debug("x backtracked to start");
|
||||||
|
Err(WaveError::NoHistory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backtrack(&mut self) -> Result<(), WaveError> {
|
||||||
|
let step = self.get_last_fork()?;
|
||||||
|
|
||||||
|
let mut state_selected_set = HashSet::new();
|
||||||
|
state_selected_set.insert(step.state_set);
|
||||||
|
|
||||||
|
let blocking_cell = cell::BlockingCell {
|
||||||
|
state: step.state_set,
|
||||||
|
position: step.position,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.remove_allowed(step.position, &vec![blocking_cell])?;
|
||||||
|
self.debug(&format!("- removed : {}, available : {:?}", step.state_set, self.grid[step.position.0][step.position.1].get_allowed()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_allowed(&mut self, position: (usize, usize), blocking_cells: &Vec<cell::BlockingCell>) -> Result<(), WaveError> {
|
||||||
|
match self.grid[position.0][position.1].remove_allowed(blocking_cells) {
|
||||||
|
Ok(result) => {
|
||||||
|
let cell::RemoveResult::Collapsed(state_set) = result else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
self.debug(&format!("* collapsed by removal ({},{}) to {}", position.0, position.1, state_set));
|
||||||
|
self.history.push(Step {
|
||||||
|
position,
|
||||||
|
state_set,
|
||||||
|
num_allowed_states: 1,
|
||||||
|
});
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
self.debug(&format!("x failed to update states allowed of ({},{}) : {}", position.0, position.1, reason));
|
||||||
|
return Err(WaveError::Contradiction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/solver/sudoku.rs
Normal file
100
src/solver/sudoku.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use super::Solver;
|
||||||
|
use super::WaveError;
|
||||||
|
use super::cell;
|
||||||
|
|
||||||
|
impl Solver {
|
||||||
|
pub fn propagate_collapse(&mut self) -> Result<(), WaveError> {
|
||||||
|
if self.last_move_index >= self.history.len() {
|
||||||
|
self.debug(&format!("x nothing to propagate"));
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_move = self.history[self.last_move_index].clone();
|
||||||
|
self.last_move_index += 1;
|
||||||
|
|
||||||
|
self.debug(&format!("- propagating {}", last_move));
|
||||||
|
|
||||||
|
let collapsed_possibility = vec![cell::BlockingCell {
|
||||||
|
state: last_move.state_set,
|
||||||
|
position: last_move.position,
|
||||||
|
}];
|
||||||
|
|
||||||
|
for index in 0..self.size {
|
||||||
|
if index != last_move.position.1 {
|
||||||
|
self.remove_allowed((last_move.position.0, index), &collapsed_possibility)?;
|
||||||
|
}
|
||||||
|
if index != last_move.position.0 {
|
||||||
|
self.remove_allowed((index, last_move.position.1), &collapsed_possibility)?;
|
||||||
|
}
|
||||||
|
let square_position = ((last_move.position.0/self.square_size)*self.square_size + index/self.square_size,
|
||||||
|
(last_move.position.1/self.square_size)*self.square_size + index%self.square_size);
|
||||||
|
if square_position.0 != last_move.position.0 || square_position.1 != last_move.position.1 {
|
||||||
|
self.remove_allowed(square_position, &collapsed_possibility)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_impossible(last_move.position)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_impossible(&self, center_position: (usize, usize)) -> Result<(),WaveError> {
|
||||||
|
let mut missing_states: HashSet<usize> = (1..=self.size).collect();
|
||||||
|
for column in 0..self.size {
|
||||||
|
let existing_states = self.grid[center_position.0][column].get_existing();
|
||||||
|
missing_states.retain(|state| !existing_states.contains(state));
|
||||||
|
}
|
||||||
|
if !missing_states.is_empty() {
|
||||||
|
self.debug(&format!("x missing row state : {:?}", missing_states));
|
||||||
|
return Err(WaveError::Contradiction)
|
||||||
|
}
|
||||||
|
|
||||||
|
missing_states = (1..=self.size).collect();
|
||||||
|
for row in 0..self.size {
|
||||||
|
let existing_states = self.grid[row][center_position.1].get_existing();
|
||||||
|
missing_states.retain(|state| !existing_states.contains(state));
|
||||||
|
}
|
||||||
|
if !missing_states.is_empty() {
|
||||||
|
self.debug(&format!("x missing column state : {:?}", missing_states));
|
||||||
|
return Err(WaveError::Contradiction)
|
||||||
|
}
|
||||||
|
missing_states = (1..=self.size).collect();
|
||||||
|
for square_index in 0..self.size {
|
||||||
|
let row = (center_position.0/self.square_size)*self.square_size + square_index/self.square_size;
|
||||||
|
let column = (center_position.1/self.square_size)*self.square_size + square_index%self.square_size;
|
||||||
|
let existing_states = self.grid[row][column].get_existing();
|
||||||
|
missing_states.retain(|state| !existing_states.contains(state));
|
||||||
|
}
|
||||||
|
if !missing_states.is_empty() {
|
||||||
|
self.debug(&format!("x missing square state : {:?}", missing_states));
|
||||||
|
return Err(WaveError::Contradiction)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn propagate_backtrack(&mut self, cell_pos: (usize, usize), removed_cell: cell::BlockingCell) -> usize {
|
||||||
|
let mut min_allowed_number = self.size;
|
||||||
|
|
||||||
|
for index in 0..self.size {
|
||||||
|
if index != cell_pos.0 {
|
||||||
|
self.grid[index][cell_pos.1].add_allowed(&removed_cell);
|
||||||
|
}
|
||||||
|
if index != cell_pos.1 {
|
||||||
|
self.grid[cell_pos.0][index].add_allowed(&removed_cell);
|
||||||
|
}
|
||||||
|
let square_row = (cell_pos.0/self.square_size)*self.square_size + index/self.square_size;
|
||||||
|
let square_column = (cell_pos.1/self.square_size)*self.square_size + index%self.square_size;
|
||||||
|
if square_row != cell_pos.0 || square_column != cell_pos.1 {
|
||||||
|
self.grid[square_row][square_column].add_allowed(&removed_cell);
|
||||||
|
}
|
||||||
|
let num_allowed = self.grid[square_row][square_column].get_num_allowed();
|
||||||
|
if num_allowed < min_allowed_number {
|
||||||
|
min_allowed_number = num_allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
min_allowed_number
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
use text_io::read;
|
use text_io::read;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use super::Solver;
|
||||||
|
use super::cell;
|
||||||
|
use super::Step;
|
||||||
|
|
||||||
use crate::Solver;
|
|
||||||
use crate::cell;
|
|
||||||
|
|
||||||
const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
@ -79,6 +82,11 @@ impl Solver {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
if value <= self.size {
|
if value <= self.size {
|
||||||
self.grid[row_index][column_index].collapse(&cell::CollapseOption::Set(value))?;
|
self.grid[row_index][column_index].collapse(&cell::CollapseOption::Set(value))?;
|
||||||
|
self.history.push(Step {
|
||||||
|
position: (row_index, column_index),
|
||||||
|
state_set: value,
|
||||||
|
num_allowed_states: 1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -94,11 +102,15 @@ impl Solver {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn progress_bar(&self, number_cell_init: usize) {
|
pub fn progress_bar(&self, progress: f32) {
|
||||||
let bar_size = (self.size + self.square_size - 1)*2 + 1;
|
let progress = progress.clamp(0.0, 1.0);
|
||||||
let progress = self.history.len()*bar_size/(self.size*self.size - number_cell_init);
|
let bar_size = ((self.size + self.square_size - 1)*2 + 1).clamp(10, 100);
|
||||||
let to_do = bar_size - progress;
|
let done = (bar_size as f32 * progress).round() as usize;
|
||||||
print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do));
|
let to_do = bar_size - done;
|
||||||
|
print!("\r[{}{}] ({:.2?}%)", "#".repeat(done), "-".repeat(to_do), progress * 100.0);
|
||||||
|
if let Err(e) = io::stdout().flush() {
|
||||||
|
eprintln!("Error flushing stdout: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_mode(&mut self, debug_display: bool) {
|
pub fn debug_mode(&mut self, debug_display: bool) {
|
||||||
|
@ -110,7 +122,7 @@ impl Solver {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug(&self, text: &str) {
|
pub fn debug(&self, text: &str) {
|
||||||
if self.debug_display {
|
if self.debug_display && self.solve_display {
|
||||||
println!("{}", text);
|
println!("{}", text);
|
||||||
}
|
}
|
||||||
}
|
}
|
20
todo
20
todo
|
@ -1,9 +1,17 @@
|
||||||
more check for impossible
|
banchmark 2 (n = 80000)
|
||||||
|
# total time : 451.66µs (80.53% propagation, 18.73% forced collapse, 0.00% backtrack)
|
||||||
|
- 13 propagations (27.98µs), 8 forced collapse (10.58µs), 0 backtrack (0.00ns)
|
||||||
|
(0 errors)
|
||||||
|
|
||||||
|
benchmark 3 (n = 8000)
|
||||||
|
# total time : 9.94ms (62.15% propagation, 37.50% forced collapse, 0.12% backtrack)
|
||||||
|
- 78 propagations (79.17µs), 48 forced collapse (77.63µs), 0 backtrack (0.00ns)
|
||||||
|
(0 errors)
|
||||||
|
|
||||||
cargo run 4 --grid --debug --ask --norand --limit 220
|
benchmark 4 (n = 300)
|
||||||
--d-fc-g
|
# total time : 116.36ms (43.53% propagation, 56.03% forced collapse, 0.23% backtrack)
|
||||||
|
- 265 propagations (191.13µs), 183 forced collapse (356.23µs), 4 backtrack (66.91µs)
|
||||||
|
|
||||||
error :
|
benchmark 5 (n = 100)
|
||||||
when backtracking, always adding the state back is wrong
|
# total time : 886.94ms (31.81% propagation, 67.66% forced collapse, 0.43% backtrack)
|
||||||
(maybe it was not there)
|
- 743 propagations (379.77µs), 506 forced collapse (1.19ms), 27 backtrack (140.68µs)
|
||||||
|
|
Loading…
Reference in a new issue