From 2153cdb3ab8e0a0c1dfdce12849085248c109104 Mon Sep 17 00:00:00 2001 From: Lauchmelder Date: Wed, 6 Jul 2022 21:56:40 +0200 Subject: [PATCH] initial commit --- .gitignore | 2 + .vscode/launch.json | 45 +++++++++++ Cargo.lock | 75 ++++++++++++++++++ Cargo.toml | 9 +++ README.md | 7 ++ src/main.rs | 59 ++++++++++++++ src/sudoku.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 379 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs create mode 100644 src/sudoku.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64ee209 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.vscode/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9d61d75 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'sudoku-solver'", + "cargo": { + "args": [ + "build", + "--bin=sudoku-solver", + "--package=sudoku-solver" + ], + "filter": { + "name": "sudoku-solver", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'sudoku-solver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=sudoku-solver", + "--package=sudoku-solver" + ], + "filter": { + "name": "sudoku-solver", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..190f863 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sudoku-solver" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2eeffd8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sudoku-solver" +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" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..463619d --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Sudoku Solver + +This program can find solutions to Sudoku. + +It uses an heuristic search algorithm to optimize a random initial solution. + +Also the code is very bad. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9b7ed88 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,59 @@ +mod sudoku; +use sudoku::{Sudoku, State}; + +use rand::seq::SliceRandom; + +fn main() { + let mut sudoku = Sudoku::new_random(9); + + let mut pos = 0; + let mut taboo = vec![Sudoku::new(9); 1024]; + + let mut counter = 0; + + loop + { + match sudoku.solved() + { + State::Solved => { println!("Solved!"); break; }, + State::Unsolved(err) => + { + println!("Iteration {}: {} errors", counter, err); + if counter % 15 == 0 + { + println!("{}", sudoku); + } + } + } + + let mut neighbours: Vec<(Sudoku, i32)> = vec![]; + + for neighbour in sudoku.iter_neighbours() + { + if taboo.contains(neighbour) { + continue; + } + + let errors = neighbour.errors() as i32; + neighbours.push((neighbour.clone(), errors)); + } + + neighbours.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()); + for i in 1..neighbours.len() + { + if neighbours[i].1 != neighbours[0].1 + { + neighbours = neighbours.chunks(i).next().unwrap().to_vec(); + break; + } + } + + taboo[pos] = sudoku.clone(); + pos = (pos + 1) % taboo.len(); + sudoku = neighbours.choose(&mut rand::thread_rng()).unwrap().0.clone(); + counter += 1; + } + + println!("{}", sudoku) + +} diff --git a/src/sudoku.rs b/src/sudoku.rs new file mode 100644 index 0000000..f2ee2a7 --- /dev/null +++ b/src/sudoku.rs @@ -0,0 +1,182 @@ +use std::fmt::Display; +use std::slice::Iter; + +pub enum State +{ + Solved, + Unsolved(u32) +} + +pub struct Sudoku +{ + size: usize, + board: Vec, + pub neighbours: Vec +} + +impl Sudoku +{ + pub fn new(size: u8) -> Sudoku + { + // Will result in bad behaviour if size = 0xFF but idc + Sudoku { + size: 9, + board: vec![0; (size * size).into()], + neighbours: vec![] + } + } + + pub fn fill_random(&mut self) + { + // Fill board with numbers in order + // TODO: Come up with proper randomized board + let mut crap: Vec = vec![]; + crap.append(&mut vec![1, 2, 3, 1, 2, 3, 1, 2, 3]); + crap.append(&mut vec![4, 5, 6, 4, 5, 6, 4, 5, 6]); + crap.append(&mut vec![7, 8, 9, 7, 8, 9, 7, 8, 9]); + + self.board = vec![]; + self.board.append(&mut crap.clone()); + self.board.append(&mut crap.clone()); + self.board.append(&mut crap); + } + + pub fn new_random(size: u8) -> Sudoku + { + let mut sudoku = Sudoku::new(size); + sudoku.fill_random(); + + sudoku + } + + pub fn iter_neighbours(&mut self) -> Iter + { + let indices = [0, 3, 6, 27, 30, 33, 54, 57, 60]; + let offsets = [0, 1, 2, 9, 10, 11, 18, 19, 20]; + self.neighbours.clear(); + + for s in indices + { + for i in 0..9 + { + for j in i+1..9 + { + let mut new_sudoku = self.clone(); + new_sudoku.swap(s + offsets[i], s + offsets[j]); + + self.neighbours.push(new_sudoku); + } + } + } + + self.neighbours.iter() + } + + pub fn swap(&mut self, left: usize, right: usize) + { + self.board.swap(left, right); + } + + fn check_rows(&self) -> u32 + { + let mut errors = 0u32; + + for row in self.board.chunks(self.size) + { + for i in 0..self.size + { + for j in i+1..self.size + { + errors += if row[i] == row[j] { 1 } else { 0 }; + } + } + } + + errors + } + + fn check_cols(&self) -> u32 + { + let mut errors = 0u32; + + for col in 0..self.size + { + for i in 0..self.size + { + for j in i+1..self.size + { + errors += if self.board[col + i * self.size] == self.board[col + j * self.size] { 1 } else { 0 }; + } + } + } + + errors + } + + pub fn errors(&self) -> u32 + { + let mut errors = 0u32; + + errors += self.check_rows(); + errors += self.check_cols(); + + errors + } + + pub fn solved(&self) -> State + { + let errors = self.errors(); + + match errors + { + 0 => State::Solved, + _ => State::Unsolved(errors) + } + } +} + +impl Clone for Sudoku +{ + fn clone(&self) -> Self + { + Sudoku { + size: self.size, + board: self.board.clone(), + neighbours: vec![] + } + } +} + +impl Display for Sudoku +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + for i in 0..self.size + { + for j in 0..self.size + { + write!(f, "{} ", self.board[i * self.size + j])?; + } + writeln!(f, "")?; + } + + write!(f, "") + } +} + +impl PartialEq for Sudoku +{ + fn eq(&self, other: &Self) -> bool + { + for i in 0..self.size + { + if self.board[i] != other.board[i] { + return false; + } + } + + true + } +} + +impl Eq for Sudoku {} \ No newline at end of file