diff --git a/src/board.rs b/src/board.rs index 35ab234..bb3e661 100644 --- a/src/board.rs +++ b/src/board.rs @@ -5,7 +5,7 @@ use crate::square::Square; #[derive(Clone)] pub struct Board { - squares: [Square; 81], + pub squares: [Square; 81], } impl Board { @@ -63,16 +63,28 @@ impl Board { SquareIter::from_row(&self.squares, index / 9) } + pub fn row(&self, row: usize) -> SquareIter { + SquareIter::from_row(&self.squares, row) + } + pub fn col_from_index(&self, index: usize) -> SquareIter { SquareIter::from_col(&self.squares, index % 9) } + pub fn col(&self, col: usize) -> SquareIter { + SquareIter::from_col(&self.squares, col) + } + pub fn box_from_index(&self, index: usize) -> SquareIter { let row = index / 27; let col = (index % 9) / 3; SquareIter::from_box(&self.squares, (row * 3) + col) } + pub fn boxn(&self, boxn: usize) -> SquareIter { + SquareIter::from_box(&self.squares, boxn) + } + fn url_puzzle_str(&self) -> String { self.squares .iter() diff --git a/src/iterators.rs b/src/iterators.rs index df6e06a..234c5a3 100644 --- a/src/iterators.rs +++ b/src/iterators.rs @@ -28,7 +28,7 @@ fn to_col(squares: &[Square; 81], col: usize) -> [Square; 9] { ] } -static BOX_ROOTS: [usize; 9] = [0, 3, 6, 27, 30, 36, 54, 57, 60]; +static BOX_ROOTS: [usize; 9] = [0, 3, 6, 27, 30, 33, 54, 57, 60]; fn to_box(squares: &[Square; 81], box_num: usize) -> [Square; 9] { let root = BOX_ROOTS[box_num]; diff --git a/src/main.rs b/src/main.rs index 9669041..d8a3adb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,13 @@ mod board; mod iterators; mod square; +mod strategy; +mod validator; + +use strategy::Strategy; fn main() { + /* let nyt_hard = "3...184.9\ 8.47.....\ .....61..\ @@ -12,7 +17,40 @@ fn main() { ...6.1...\ 17.2.....\ 5........"; - let sudoku_board = board::Board::from_string(nyt_hard); + */ + let nyt_easy = "154..627.\ + ..817..45\ + ..649..1.\ + ..9683...\ + 321...86.\ + 6....193.\ + 5..9....3\ + .97...1.2\ + 8...54..."; + let mut sudoku_board = board::Board::from_string(nyt_easy); + + if let validator::BoardState::Broken(ind) = validator::validate_board(&sudoku_board) { + println!("{}", sudoku_board.to_url()); + panic!( + "Error invalid number at row {} col {}", + (ind / 9) + 1, + (ind % 9) + 1 + ); + } + + while let Some(ns) = strategy::naked_single::NakedSingle::find_instance(&sudoku_board) { + println!("Applying naked single."); + sudoku_board = ns.apply(sudoku_board); + + if let validator::BoardState::Broken(ind) = validator::validate_board(&sudoku_board) { + println!("{}", sudoku_board.to_url()); + panic!( + "Error invalid number at row {} col {}", + (ind / 9) + 1, + (ind % 9) + 1 + ); + } + } println!("{:#?}", sudoku_board); println!("{}", sudoku_board.to_url()); } diff --git a/src/strategy/base.rs b/src/strategy/base.rs new file mode 100644 index 0000000..c642395 --- /dev/null +++ b/src/strategy/base.rs @@ -0,0 +1,9 @@ +use crate::board::Board; + +pub trait Strategy { + fn find_instance(board: &Board) -> Option + where + Self: Sized; + + fn apply(&self, board: Board) -> Board; +} diff --git a/src/strategy/mod.rs b/src/strategy/mod.rs new file mode 100644 index 0000000..3f03a6f --- /dev/null +++ b/src/strategy/mod.rs @@ -0,0 +1,5 @@ +pub mod base; + +pub mod naked_single; + +pub use base::Strategy; diff --git a/src/strategy/naked_single.rs b/src/strategy/naked_single.rs new file mode 100644 index 0000000..43c960a --- /dev/null +++ b/src/strategy/naked_single.rs @@ -0,0 +1,33 @@ +use crate::{board::Board, square::Square, strategy::Strategy}; + +pub struct NakedSingle { + index: usize, + value: u8, +} + +impl Strategy for NakedSingle { + fn find_instance(board: &crate::board::Board) -> Option + where + Self: Sized, + { + board + .squares + .iter() + .enumerate() + .filter_map(|(index, square)| match square { + Square::Options(marks) => Some((index, marks)), + _ => None, + }) + .find(|(_index, marks)| marks.len() == 1) + .map(|(index, marks)| NakedSingle { + index, + value: marks.first_index().unwrap() as u8 + 1, + }) + } + + fn apply(&self, mut board: Board) -> Board { + board.squares[self.index] = Square::Value(self.value); + board.restrict_marks(); + board + } +} diff --git a/src/validator.rs b/src/validator.rs new file mode 100644 index 0000000..42ef7f4 --- /dev/null +++ b/src/validator.rs @@ -0,0 +1,63 @@ +use crate::{board::Board, square::Square}; + +pub enum BoardState { + Finished, + Broken(u8), + InProgress, +} + +fn are_unique(iter: T) -> bool +where + T: Iterator, +{ + let mut bitmap: bitmaps::Bitmap<10> = bitmaps::Bitmap::new(); + + for item in iter { + if bitmap.get(item as usize) { + return false; + } + bitmap.set(item as usize, true); + } + true +} + +pub fn validate_board(board: &Board) -> BoardState { + for r in 0..9 { + if !are_unique(board.row(r).filter_map(|sq| match sq { + Square::Value(x) => Some(x), + _ => None, + })) { + // TODO: Add the index. + return BoardState::Broken(0); + } + } + + for c in 0..9 { + if !are_unique(board.col(c).filter_map(|sq| match sq { + Square::Value(x) => Some(x), + _ => None, + })) { + // TODO: Add the index. + return BoardState::Broken(0); + } + } + + for b in 0..9 { + if !are_unique(board.boxn(b).filter_map(|sq| match sq { + Square::Value(x) => Some(x), + _ => None, + })) { + // TODO: Add the index. + return BoardState::Broken(0); + } + } + + if board.squares.iter().any(|sq| match sq { + Square::Options(_) => true, + _ => false, + }) { + BoardState::InProgress + } else { + BoardState::Finished + } +}