better ui, size adaptative

This commit is contained in:
WanderingPenwing 2024-11-29 15:04:07 +01:00
parent e06c9db089
commit 29aa1b9e00

View file

@ -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,27 +35,26 @@ 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, " ")
} }
@ -47,79 +63,112 @@ impl fmt::Display for Cell {
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 {
if debug_display {
println!("x no empty cells"); 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() {
if debug_display {
println!("* backtracking"); 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 {
if debug_display {
println!("x backtracked to start"); 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 {
if debug_display {
println!("x no possibilities for [{}][{}]", row_index, column_index); 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);
if debug_display {
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value); println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value);
}
return true return true
} }
fn solve(&mut self, display: bool) { fn solve(&mut self, debug_display: bool) {
if display { let now = Instant::now();
self.display();
self.display(DisplayMode::Full);
if debug_display {
println!("--------"); println!("--------");
} }
self.update_possibilities(); println!("# started");
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();
if debug_display {
println!("--------"); println!("--------");
}
println!("# finished in {} steps, {:.2?} ({:.2?}/step)", counter, elapsed, elapsed/(counter as u32));
self.display(DisplayMode::Full);
}
println!("finished with {} steps", counter); fn ask(&mut self) {
for row_index in 0..self.size {
self.display(); 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()));
} }