sudoku with simple backtracking

This commit is contained in:
WanderingPenwing 2024-11-28 12:02:18 +01:00
commit 9a0a9afe37
5 changed files with 435 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

147
Cargo.lock generated Normal file
View file

@ -0,0 +1,147 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.166"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "sudoku"
version = "0.1.0"
dependencies = [
"rand",
"text_io",
]
[[package]]
name = "syn"
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "text_io"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f0c8eb2ad70c12a6a69508f499b3051c924f4b1cfeae85bfad96e6bc5bba46"
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "sudoku"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
text_io = "0.1.12"

18
shell.nix Normal file
View file

@ -0,0 +1,18 @@
{ pkgs ? import <nixpkgs> { overlays = [ (import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz)) ]; },}:
with pkgs;
mkShell {
nativeBuildInputs = with xorg; [
pkg-config
] ++ [
cargo
rustc
];
buildInputs = [
latest.rustChannels.stable.rust
xorg.libX11
xorg.libXi
xorg.libXtst
libevdev
];
}

259
src/main.rs Normal file
View file

@ -0,0 +1,259 @@
use std::fmt;
use std::collections::HashSet;
use rand::seq::SliceRandom;
#[derive(Clone)]
struct Cell {
value: Option<u8>,
possibilities: Vec<u8>
}
impl Cell {
fn set(&mut self, value: u8) {
self.value = if value > 0 && value < 10 {
Some(value)
} else {
None
};
}
fn reset_possibilities(&mut self) {
self.possibilities = (1..=9).collect();
}
fn remove_possibilities(&mut self, possibilities: &HashSet<u8>) {
self.possibilities.retain(|&x| !possibilities.contains(&x))
}
}
impl Default for Cell {
fn default() -> Self {
Self {
value: None,
possibilities: (1..=9).collect(),
}
}
}
impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(v) = self.value {
write!(f, "{}", v)
} else {
write!(f, " ")
}
}
}
struct Choice {
cell_selected: [usize; 2],
possibilities: Vec<u8>,
selected_value: u8,
}
struct Sudoku {
grid: [[Cell; 9]; 9],
rng: rand::rngs::ThreadRng,
history: Vec<Choice>,
}
impl Sudoku {
fn new(sudoku_set: [[u8; 9]; 9]) -> Self {
let mut sudoku_grid: [[Cell; 9]; 9] = [[(); 9]; 9].map(|_| [(); 9].map(|_| Cell::default()));
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 {
grid: sudoku_grid,
rng: rand::thread_rng(),
history: vec![],
}
}
fn display(&self) {
println!("# sudoku grid : ");
for row in &self.grid {
let mut line: String = "[".to_string();
for cell in row {
line += &format!("{}|",cell);
}
line.pop();
println!("{}]",line);
}
}
fn update_possibilities(&mut self) {
self.update_rows();
self.update_columns();
self.update_squares();
}
fn update_rows(&mut self) {
for row_index in 0..9 {
let mut used_values: HashSet<u8> = HashSet::new();
for column_index in 0..9 {
if let Some(value) = self.grid[row_index][column_index].value {
used_values.insert(value);
}
}
for column_index in 0..9 {
self.grid[row_index][column_index].remove_possibilities(&used_values);
}
}
}
fn update_columns(&mut self) {
for column_index in 0..9 {
let mut used_values: HashSet<u8> = HashSet::new();
for row_index in 0..9 {
if let Some(value) = self.grid[row_index][column_index].value {
used_values.insert(value);
}
}
for row_index in 0..9 {
self.grid[row_index][column_index].remove_possibilities(&used_values);
}
}
}
fn update_squares(&mut self) {
for square_row_index in 0..3 {
for square_column_index in 0..3 {
let mut used_values: HashSet<u8> = HashSet::new();
for row_offset_index in 0..3 {
for column_offset_index in 0..3 {
if let Some(value) = self.grid[square_row_index * 3 + row_offset_index][square_column_index * 3 + column_offset_index].value {
used_values.insert(value);
}
}
}
for row_offset_index in 0..3 {
for column_offset_index in 0..3 {
self.grid[square_row_index * 3 + row_offset_index][square_column_index * 3 + column_offset_index].remove_possibilities(&used_values.clone());
}
}
}
}
}
fn collapse(&mut self) -> bool {
let mut min_row_index: usize = 0;
let mut min_column_index: usize = 0;
let mut min_len: usize = 9;
let mut grid_has_empty_cell: bool = false;
for row_index in 0..9 {
for column_index in 0..9 {
if !self.grid[row_index][column_index].value.is_none() {
continue;
}
grid_has_empty_cell = true;
let possibilities_len = self.grid[row_index][column_index].possibilities.len();
if possibilities_len < min_len {
min_row_index = row_index;
min_column_index = column_index;
min_len = possibilities_len;
}
}
}
if !grid_has_empty_cell {
println!("x no empty cells");
return false
}
if self.collapse_cell(min_row_index, min_column_index) {
return true
}
let mut fork: Option<Choice> = None;
while let Some(choice) = self.history.pop() {
println!("* backtracking");
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None;
if choice.possibilities.len() > 1 {
fork = Some(choice);
break;
}
}
if let Some(choice) = fork {
self.reset_possibilities();
let mut choice_value = HashSet::new();
choice_value.insert(choice.selected_value);
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&choice_value);
return self.collapse_cell(choice.cell_selected[0], choice.cell_selected[1])
} else {
println!("x backtracked to start");
return false
}
}
fn reset_possibilities(&mut self) {
for row in &mut self.grid {
for cell in row {
cell.reset_possibilities();
}
}
self.update_possibilities();
}
fn collapse_cell(&mut self, row_index: usize, column_index: usize) -> bool {
if let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) {
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);
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value);
return true
} else {
println!("x no possibilities for [{}][{}]", row_index, column_index);
return false
}
}
}
fn main() {
let sudoku_set: [[u8; 9]; 9] = [
[6,0,0,0,0,2,1,0,0],
[0,3,2,9,0,0,0,7,0],
[0,0,0,0,0,8,0,0,0],
[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);
sudoku.display();
println!("--------");
sudoku.update_possibilities();
while sudoku.collapse() {
sudoku.update_possibilities();
}
println!("--------");
sudoku.display();
}