diff --git a/src/board.rs b/src/board.rs index 41d926d..d1b0d17 100644 --- a/src/board.rs +++ b/src/board.rs @@ -8,6 +8,20 @@ pub struct Board { pub squares: [Square; 81], } +pub fn index_to_row(index: u8) -> u8 { + index / 9 +} + +pub fn index_to_col(index: u8) -> u8 { + index % 9 +} + +pub fn index_to_box(index: u8) -> u8 { + let row = index / 27; + let col = (index % 9) / 3; + (row * 3) + col +} + impl Board { pub fn from_string(str: &str) -> Board { if str.len() != 81 { @@ -58,9 +72,7 @@ impl Board { } pub fn box_from_index(&self, index: u8) -> SquareIter { - let row = index / 27; - let col = (index % 9) / 3; - SquareIter::from_box(&self.squares, (row * 3) + col) + SquareIter::from_box(&self.squares, index_to_box(index)) } pub fn boxn(&self, boxn: u8) -> SquareIter { diff --git a/src/solver/solver.rs b/src/solver/solver.rs index 99631a9..bbf88ed 100644 --- a/src/solver/solver.rs +++ b/src/solver/solver.rs @@ -1,5 +1,5 @@ use crate::board::Board; -use crate::strategy::{HiddenSingle, NakedSingle, Strategy, StrategyFn}; +use crate::strategy::{BoxLine, HiddenSingle, NakedSingle, Strategy, StrategyFn}; use crate::validator; fn try_strategies(strategies: &[StrategyFn], sudoku_board: &Board) -> Option> { @@ -21,10 +21,15 @@ pub fn solve_board(mut sudoku_board: Board) -> Result { ); } - let strategies: [StrategyFn; 2] = [NakedSingle::find_instance, HiddenSingle::find_instance]; + let strategies: [StrategyFn; 3] = [ + NakedSingle::find_instance, + HiddenSingle::find_instance, + BoxLine::find_instance, + ]; - while let Some(ns) = try_strategies(&strategies, &sudoku_board) { - sudoku_board = ns.apply(sudoku_board); + while let Some(strat) = try_strategies(&strategies, &sudoku_board) { + println!("{}", strat); + sudoku_board = strat.apply(sudoku_board); if let validator::BoardState::Broken(ind) = validator::validate_board(&sudoku_board) { println!("{}", sudoku_board.to_url()); diff --git a/src/strategy/base.rs b/src/strategy/base.rs index c4c291b..1494927 100644 --- a/src/strategy/base.rs +++ b/src/strategy/base.rs @@ -1,8 +1,10 @@ +use core::fmt; + use crate::board::Board; pub type StrategyFn = fn(&Board) -> Option>; -pub trait Strategy { +pub trait Strategy: fmt::Display { fn find_instance(board: &Board) -> Option> where Self: Sized; diff --git a/src/strategy/box_line.rs b/src/strategy/box_line.rs new file mode 100644 index 0000000..7b35107 --- /dev/null +++ b/src/strategy/box_line.rs @@ -0,0 +1,163 @@ +use std::fmt::Display; + +use crate::{ + board::{index_to_box, index_to_col, index_to_row, Board}, + square::Square, + strategy::Strategy, +}; + +enum Seen { + NotSeen, + SeenInBox(u8), + SeenInMultiple, +} + +pub enum RowCol { + Row(u8), + Col(u8), +} + +pub struct BoxLine { + rowcol: RowCol, + boxn: u8, + value: u8, +} + +impl Strategy for BoxLine { + fn find_instance(board: &Board) -> Option> { + for r in 0..9 { + for n in 1..=9 { + let seen = board + .row(r) + .fold(Seen::NotSeen, |curr, square| match square { + Square::Marks(i, opts) => { + if opts.get(n as usize) { + let boxn = index_to_box(i); + match curr { + Seen::NotSeen => Seen::SeenInBox(boxn), + Seen::SeenInBox(b) => { + if b == boxn { + curr + } else { + Seen::SeenInMultiple + } + } + _ => Seen::SeenInMultiple, + } + } else { + curr + } + } + _ => curr, + }); + + match seen { + Seen::SeenInBox(b) => { + if board.boxn(b).any(|sq| match sq { + Square::Marks(i, marks) => { + (index_to_row(i) != r) && marks.get(n as usize) + } + _ => false, + }) { + return Some(Box::new(BoxLine { + rowcol: RowCol::Row(r), + boxn: b, + value: n, + })); + } + } + _ => {} + } + } + } + for c in 0..9 { + for n in 1..=9 { + let seen = board + .col(c) + .fold(Seen::NotSeen, |curr, square| match square { + Square::Marks(i, opts) => { + if opts.get(n as usize) { + let boxn = index_to_box(i); + match curr { + Seen::NotSeen => Seen::SeenInBox(boxn), + Seen::SeenInBox(b) => { + if b == boxn { + curr + } else { + Seen::SeenInMultiple + } + } + _ => Seen::SeenInMultiple, + } + } else { + curr + } + } + _ => curr, + }); + + match seen { + Seen::SeenInBox(b) => { + if board.boxn(b).any(|sq| match sq { + Square::Marks(i, marks) => { + (index_to_col(i) != c) && marks.get(n as usize) + } + _ => false, + }) { + return Some(Box::new(BoxLine { + rowcol: RowCol::Col(c), + boxn: b, + value: n, + })); + } + } + _ => {} + } + } + } + None + } + + fn apply(&self, mut board: Board) -> Board { + for ref mut square in board.squares.iter_mut() { + if let Square::Marks(i, ref mut marks) = square { + if index_to_box(*i as u8) == self.boxn { + match self.rowcol { + RowCol::Row(r) => { + if index_to_row(*i) != r { + marks.set(self.value as usize, false); + } + } + RowCol::Col(c) => { + if index_to_col(*i) != c { + marks.set(self.value as usize, false); + } + } + } + } + } + } + board + } +} + +impl Display for BoxLine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.rowcol { + RowCol::Row(r) => write!( + f, + "Box Line Reduction on {} in row {} in box {}", + self.value, + r + 1, + self.boxn + 1, + ), + RowCol::Col(c) => write!( + f, + "Box Line Reduction on {} in col {} in box {}", + self.value, + c + 1, + self.boxn + 1, + ), + } + } +} diff --git a/src/strategy/hidden_single.rs b/src/strategy/hidden_single.rs index 79c3a9b..2d47a0a 100644 --- a/src/strategy/hidden_single.rs +++ b/src/strategy/hidden_single.rs @@ -1,4 +1,6 @@ -use crate::board::Board; +use std::fmt::Display; + +use crate::board::{index_to_col, index_to_row, Board}; use crate::square::Square; use crate::strategy::Strategy; @@ -99,3 +101,15 @@ impl Strategy for HiddenSingle { board } } + +impl Display for HiddenSingle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Hidden Single {} at row {} col {}", + self.value, + index_to_row(self.index) + 1, + index_to_col(self.index) + 1, + ) + } +} diff --git a/src/strategy/mod.rs b/src/strategy/mod.rs index a22f2f8..a8cbb8d 100644 --- a/src/strategy/mod.rs +++ b/src/strategy/mod.rs @@ -1,10 +1,12 @@ pub mod base; +pub mod box_line; pub mod hidden_single; pub mod naked_single; pub use base::Strategy; pub use base::StrategyFn; +pub use box_line::BoxLine; pub use hidden_single::HiddenSingle; pub use naked_single::NakedSingle; diff --git a/src/strategy/naked_single.rs b/src/strategy/naked_single.rs index 183acf2..ae0cef4 100644 --- a/src/strategy/naked_single.rs +++ b/src/strategy/naked_single.rs @@ -1,4 +1,10 @@ -use crate::{board::Board, square::Square, strategy::Strategy}; +use std::fmt::{Display, Write}; + +use crate::{ + board::{index_to_col, index_to_row, Board}, + square::Square, + strategy::Strategy, +}; pub struct NakedSingle { index: u8, @@ -32,3 +38,15 @@ impl Strategy for NakedSingle { board } } + +impl Display for NakedSingle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Naked Single {} at row {} col {}", + self.value, + index_to_row(self.index) + 1, + index_to_col(self.index) + 1, + ) + } +}