better ui, size adaptative
This commit is contained in:
parent
e06c9db089
commit
29aa1b9e00
280
src/main.rs
280
src/main.rs
|
@ -1,16 +1,33 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
//use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::env;
|
||||||
|
use std::process;
|
||||||
|
use text_io::read;
|
||||||
|
use std::cmp::min;
|
||||||
|
|
||||||
|
enum LineType {
|
||||||
|
Top,
|
||||||
|
Middle,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DisplayMode {
|
||||||
|
Full,
|
||||||
|
Focus(usize),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Cell {
|
struct Cell {
|
||||||
value: Option<u8>,
|
value: Option<usize>,
|
||||||
possibilities: Vec<u8>
|
max: usize,
|
||||||
|
possibilities: Vec<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
fn set(&mut self, value: u8) {
|
fn set(&mut self, value: usize) {
|
||||||
self.value = if value > 0 && value < 10 {
|
self.value = if value > 0 && value <= self.max {
|
||||||
Some(value)
|
Some(value)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -18,108 +35,140 @@ impl Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_possibilities(&mut self) {
|
fn reset_possibilities(&mut self) {
|
||||||
self.possibilities = (1..=9).collect();
|
self.possibilities = (1..=self.max).collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_possibilities(&mut self, possibilities: &Vec<u8>) {
|
fn remove_possibilities(&mut self, possibilities: &HashSet<usize>) {
|
||||||
self.possibilities.retain(|&x| !possibilities.contains(&x))
|
self.possibilities.retain(|&x| !possibilities.contains(&x))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Cell {
|
fn new(max: usize) -> Self {
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
value: None,
|
value: None,
|
||||||
possibilities: (1..=9).collect(),
|
max,
|
||||||
|
possibilities: (1..=max).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Cell {
|
impl fmt::Display for Cell {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(v) = self.value {
|
if let Some(value) = self.value {
|
||||||
write!(f, "{}", v)
|
write!(f, "{}", to_base35(value))
|
||||||
} else {
|
} else {
|
||||||
write!(f, " ")
|
write!(f, " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Choice {
|
struct Choice {
|
||||||
cell_selected: [usize; 2],
|
cell_selected: [usize; 2],
|
||||||
possibilities: Vec<u8>,
|
possibilities: Vec<usize>,
|
||||||
selected_value: u8,
|
selected_value: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Sudoku {
|
struct Sudoku {
|
||||||
grid: [[Cell; 9]; 9],
|
grid: Vec<Vec<Cell>>,
|
||||||
rng: rand::rngs::ThreadRng,
|
rng: rand::rngs::ThreadRng,
|
||||||
history: Vec<Choice>,
|
history: Vec<Choice>,
|
||||||
|
size: usize,
|
||||||
|
square_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sudoku {
|
impl Sudoku {
|
||||||
fn new(sudoku_set: [[u8; 9]; 9]) -> Self {
|
fn new(order: usize) -> Self {
|
||||||
let mut sudoku_grid: [[Cell; 9]; 9] = [[(); 9]; 9].map(|_| [(); 9].map(|_| Cell::default()));
|
let size = order*order;
|
||||||
|
let sudoku_grid: Vec<Vec<Cell>> = vec![vec![Cell::new(size); size]; size];
|
||||||
for (row_index, row) in sudoku_set.iter().enumerate(){
|
|
||||||
for (column_index, value) in row.iter().enumerate() {
|
|
||||||
sudoku_grid[row_index][column_index].set(*value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
grid: sudoku_grid,
|
grid: sudoku_grid,
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
history: vec![],
|
history: vec![],
|
||||||
|
size,
|
||||||
|
square_size: order,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display(&self) {
|
fn display(&self, display_mode: DisplayMode) {
|
||||||
println!("# sudoku grid : ");
|
self.draw_line(LineType::Top);
|
||||||
for row in &self.grid {
|
for (row_index, row) in self.grid.iter().enumerate() {
|
||||||
let mut line: String = "[".to_string();
|
if row_index % self.square_size == 0 && row_index != 0 {
|
||||||
for cell in row {
|
self.draw_line(LineType::Middle);
|
||||||
line += &format!("{}|",cell);
|
|
||||||
}
|
}
|
||||||
line.pop();
|
|
||||||
println!("{}]",line);
|
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) {
|
fn update_possibilities(&mut self) {
|
||||||
let mut row_used_values: [Vec<u8>;9] = [vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![]];
|
let mut row_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size];
|
||||||
let mut column_used_values: [Vec<u8>;9] = [vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![],vec![]];
|
let mut column_used_values: Vec<HashSet<usize>> = vec![HashSet::new(); self.size];
|
||||||
let mut square_used_values: [[Vec<u8>;3];3] = [[vec![],vec![],vec![]],[vec![],vec![],vec![]],[vec![],vec![],vec![]]];
|
let mut square_used_values: Vec<Vec<HashSet<usize>>> = vec![vec![HashSet::new(); self.square_size]; self.square_size];
|
||||||
|
|
||||||
for row_index in 0..9 {
|
for row_index in 0..self.size {
|
||||||
for column_index in 0..9 {
|
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].value else {
|
||||||
continue
|
continue
|
||||||
};
|
};
|
||||||
row_used_values[row_index].push(value);
|
row_used_values[row_index].insert(value);
|
||||||
column_used_values[column_index].push(value);
|
column_used_values[column_index].insert(value);
|
||||||
square_used_values[row_index/3][column_index/3].push(value);
|
square_used_values[row_index/self.square_size][column_index/self.square_size].insert(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for row_index in 0..9 {
|
for row_index in 0..self.size {
|
||||||
for column_index in 0..9 {
|
for column_index in 0..self.size {
|
||||||
self.grid[row_index][column_index].remove_possibilities(&row_used_values[row_index]);
|
let mut used_values = row_used_values[row_index].clone();
|
||||||
self.grid[row_index][column_index].remove_possibilities(&column_used_values[column_index]);
|
used_values.extend(&column_used_values[column_index]);
|
||||||
self.grid[row_index][column_index].remove_possibilities(&square_used_values[row_index/3][column_index/3]);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collapse(&mut self) -> bool {
|
fn collapse(&mut self, debug_display: bool) -> bool {
|
||||||
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 = 9;
|
let mut min_len: usize = self.size;
|
||||||
|
|
||||||
let mut grid_has_empty_cell: bool = false;
|
let mut grid_has_empty_cell: bool = false;
|
||||||
|
|
||||||
for row_index in 0..9 {
|
for row_index in 0..self.size {
|
||||||
for column_index in 0..9 {
|
for column_index in 0..self.size {
|
||||||
if !self.grid[row_index][column_index].value.is_none() {
|
if !self.grid[row_index][column_index].value.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -134,18 +183,22 @@ impl Sudoku {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !grid_has_empty_cell {
|
if !grid_has_empty_cell {
|
||||||
println!("x no empty cells");
|
if debug_display {
|
||||||
|
println!("x no empty cells");
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.collapse_cell(min_row_index, min_column_index) {
|
if self.collapse_cell(min_row_index, min_column_index, debug_display) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fork: Option<Choice> = None;
|
let mut fork: Option<Choice> = None;
|
||||||
|
|
||||||
while let Some(choice) = self.history.pop() {
|
while let Some(choice) = self.history.pop() {
|
||||||
println!("* backtracking");
|
if debug_display {
|
||||||
|
println!("* backtracking");
|
||||||
|
}
|
||||||
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None;
|
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None;
|
||||||
if choice.possibilities.len() > 1 {
|
if choice.possibilities.len() > 1 {
|
||||||
fork = Some(choice);
|
fork = Some(choice);
|
||||||
|
@ -154,13 +207,19 @@ impl Sudoku {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(choice) = fork else {
|
let Some(choice) = fork else {
|
||||||
println!("x backtracked to start");
|
if debug_display {
|
||||||
|
println!("x backtracked to start");
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
};
|
};
|
||||||
|
|
||||||
self.reset_possibilities();
|
self.reset_possibilities();
|
||||||
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&vec![choice.selected_value]);
|
|
||||||
return self.collapse_cell(choice.cell_selected[0], choice.cell_selected[1])
|
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) {
|
fn reset_possibilities(&mut self) {
|
||||||
|
@ -173,9 +232,11 @@ impl Sudoku {
|
||||||
self.update_possibilities();
|
self.update_possibilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collapse_cell(&mut self, row_index: usize, column_index: usize) -> bool {
|
fn collapse_cell(&mut self, row_index: usize, column_index: usize, debug_display: bool) -> bool {
|
||||||
let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) else {
|
let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) else {
|
||||||
println!("x no possibilities for [{}][{}]", row_index, column_index);
|
if debug_display {
|
||||||
|
println!("x no possibilities for [{}][{}]", row_index, column_index);
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -186,60 +247,93 @@ impl Sudoku {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.grid[row_index][column_index].set(selected_value);
|
self.grid[row_index][column_index].set(selected_value);
|
||||||
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value);
|
if debug_display {
|
||||||
|
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value);
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve(&mut self, display: bool) {
|
fn solve(&mut self, debug_display: bool) {
|
||||||
if display {
|
let now = Instant::now();
|
||||||
self.display();
|
|
||||||
println!("--------");
|
self.display(DisplayMode::Full);
|
||||||
}
|
if debug_display {
|
||||||
|
println!("--------");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("# started");
|
||||||
|
|
||||||
self.update_possibilities();
|
self.update_possibilities();
|
||||||
|
|
||||||
let mut counter: usize = 0;
|
let mut counter: usize = 0;
|
||||||
|
|
||||||
while self.collapse() {
|
while self.collapse(debug_display) {
|
||||||
self.update_possibilities();
|
self.update_possibilities();
|
||||||
counter +=1;
|
counter +=1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if display {
|
let elapsed = now.elapsed();
|
||||||
println!("--------");
|
|
||||||
|
|
||||||
println!("finished with {} steps", counter);
|
if debug_display {
|
||||||
|
println!("--------");
|
||||||
|
}
|
||||||
|
println!("# finished in {} steps, {:.2?} ({:.2?}/step)", counter, elapsed, elapsed/(counter as u32));
|
||||||
|
self.display(DisplayMode::Full);
|
||||||
|
}
|
||||||
|
|
||||||
self.display();
|
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 "0123456789abcdefghijklmnopqrstuvwxyz".find(c) {
|
||||||
|
Some(value) => {
|
||||||
|
if value <= self.size {
|
||||||
|
self.grid[row_index][column_index].set(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [6,0,0,0,0,2,1,0,0], // has to backtrack
|
fn to_base35(mut n: usize) -> String {
|
||||||
// [0,3,2,9,0,0,0,7,0],
|
let chars = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
// [0,0,0,0,0,8,0,0,0],
|
if n == 0 {
|
||||||
// [2,0,0,0,0,0,4,0,0],
|
return chars[0..1].to_string(); // Special case for 0
|
||||||
// [7,0,3,0,0,0,9,1,0],
|
}
|
||||||
// [0,8,0,0,9,4,0,0,0],
|
|
||||||
// [0,0,4,0,0,0,6,0,0],
|
|
||||||
// [1,2,0,7,0,0,0,0,5],
|
|
||||||
// [0,0,0,0,0,0,0,9,4],
|
|
||||||
|
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
while n > 0 {
|
||||||
|
let remainder = n % 35;
|
||||||
|
result.push(chars.chars().nth(remainder).unwrap());
|
||||||
|
n /= 35;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result will be in reverse order, so we need to reverse it
|
||||||
|
result.chars().rev().collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let sudoku_set: [[u8; 9]; 9] = [
|
let args: Vec<String> = env::args().collect();
|
||||||
[0,0,0,0,0,0,0,0,0],
|
if args.len() < 2 {
|
||||||
[0,3,2,9,0,0,0,7,0],
|
eprintln!("Usage: {} <order> [--debug] [--ask]", args[0]);
|
||||||
[0,0,0,0,0,8,0,0,0],
|
process::exit(1);
|
||||||
[2,0,0,0,0,0,4,0,0],
|
}
|
||||||
[7,0,3,0,0,0,9,1,0],
|
|
||||||
[0,8,0,0,9,4,0,0,0],
|
|
||||||
[0,0,4,0,0,0,6,0,0],
|
|
||||||
[1,2,0,7,0,0,0,0,5],
|
|
||||||
[0,0,0,0,0,0,0,9,4],
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut sudoku = Sudoku::new(sudoku_set);
|
let size: usize = args[1].parse().unwrap_or_else(|_| {
|
||||||
|
eprintln!("Invalid size argument");
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
sudoku.solve(true);
|
let mut sudoku = Sudoku::new(min(size, 5));
|
||||||
|
|
||||||
|
if args.contains(&"--ask".to_string()) {
|
||||||
|
sudoku.ask();
|
||||||
|
}
|
||||||
|
|
||||||
|
sudoku.solve(args.contains(&"--debug".to_string()));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue