use crate::core::{SolvePath, SolveStep};
use super::board::Board;
use super::candidates::Candidates;
use super::masks::Masks;
pub mod flags;
mod hidden_pairs;
mod hidden_quads;
mod hidden_singles;
mod hidden_triples;
mod jellyfish;
mod locked_candidates;
mod naked_pairs;
mod naked_quads;
mod naked_singles;
mod naked_triples;
mod skyscraper;
mod swordfish;
pub mod units;
mod w_wing;
mod x_wing;
mod xy_wing;
mod xyz_wing;
use flags::TechniqueFlags;
use hidden_pairs::HiddenPairs;
use hidden_quads::HiddenQuads;
use hidden_singles::HiddenSingles;
use hidden_triples::HiddenTriples;
use jellyfish::Jellyfish;
use locked_candidates::LockedCandidates;
use naked_pairs::NakedPairs;
use naked_quads::NakedQuads;
use naked_singles::NakedSingles;
use naked_triples::NakedTriples;
use skyscraper::Skyscraper;
use swordfish::Swordfish;
use w_wing::WWing;
use x_wing::XWing;
use xy_wing::XYWing;
use xyz_wing::XyzWing;
pub struct TechniquePropagator<'a> {
board: &'a mut Board,
masks: &'a mut Masks,
candidates: &'a mut Candidates,
techniques_enabled: TechniqueFlags,
}
impl<'a> TechniquePropagator<'a> {
pub fn new(
board: &'a mut Board,
masks: &'a mut Masks,
candidates: &'a mut Candidates,
techniques_enabled: TechniqueFlags,
) -> Self {
Self {
board,
masks,
candidates,
techniques_enabled,
}
}
fn place_and_update(
&mut self,
r: usize,
c: usize,
num: u8,
flags: TechniqueFlags,
path: &mut SolvePath,
) {
self.board.set(r, c, num);
self.masks.add_number(r, c, num);
let affected_cells_count = self.count_affected_cells(r, c, num);
let candidates_eliminated_count = self.count_candidates_eliminated(r, c, num);
self.candidates
.update_affected_cells(r, c, self.masks, self.board);
let step_number = path.steps.len() as u32;
let difficulty_point = Self::difficulty_for_technique(flags);
path.steps.push(SolveStep::Placement {
row: r,
col: c,
value: num,
flags,
step_number,
candidates_eliminated: candidates_eliminated_count,
related_cell_count: affected_cells_count.min(255) as u8,
difficulty_point,
});
}
fn remove_and_update(&mut self, r: usize, c: usize, num: u8) {
self.board.set(r, c, 0);
self.masks.remove_number(r, c, num);
self.candidates
.update_affected_cells(r, c, self.masks, self.board);
}
fn eliminate_candidate(
&mut self,
r: usize,
c: usize,
candidate_bit: u16, flags: TechniqueFlags,
path: &mut SolvePath,
) -> bool {
let initial_mask = self.candidates.get(r, c);
let refined_mask = initial_mask & !candidate_bit;
self.candidates.set(r, c, refined_mask);
let num = candidate_bit.trailing_zeros() as u8 + 1; let step_number = path.steps.len() as u32;
let difficulty_point = Self::difficulty_for_technique(flags);
path.steps.push(SolveStep::CandidateElimination {
row: r,
col: c,
value: num,
flags,
step_number,
candidates_eliminated: 1, related_cell_count: 1, difficulty_point,
});
initial_mask != refined_mask }
fn eliminate_multiple_candidates(
&mut self,
r: usize,
c: usize,
elimination_mask: u16, flags: TechniqueFlags,
path: &mut SolvePath,
) -> bool {
let initial_mask = self.candidates.get(r, c);
let refined_mask = initial_mask & !elimination_mask;
self.candidates.set(r, c, refined_mask);
let eliminated_mask = initial_mask & elimination_mask; let eliminated_count = eliminated_mask.count_ones();
let difficulty_point = Self::difficulty_for_technique(flags);
for candidate in 1..=9 {
let candidate_bit = 1 << (candidate - 1);
if (eliminated_mask & candidate_bit) != 0 {
let step_number = path.steps.len() as u32;
path.steps.push(SolveStep::CandidateElimination {
row: r,
col: c,
value: candidate,
flags,
step_number,
candidates_eliminated: eliminated_count,
related_cell_count: 1,
difficulty_point,
});
}
}
initial_mask != refined_mask }
fn count_affected_cells(&self, r: usize, c: usize, _num: u8) -> u32 {
let mut count = 0u32;
let box_r = (r / 3) * 3;
let box_c = (c / 3) * 3;
for col in 0..9 {
if col != c && self.board.is_empty(r, col) {
count += 1;
}
}
for row in 0..9 {
if row != r && self.board.is_empty(row, c) {
count += 1;
}
}
for br in box_r..box_r + 3 {
for bc in box_c..box_c + 3 {
if br != r && bc != c && self.board.is_empty(br, bc) {
count += 1;
}
}
}
count
}
fn count_candidates_eliminated(&self, r: usize, c: usize, num: u8) -> u32 {
let mut count = 0u32;
let box_r = (r / 3) * 3;
let box_c = (c / 3) * 3;
let candidate_bit = 1u16 << (num - 1);
for col in 0..9 {
if col != c && (self.candidates.get(r, col) & candidate_bit) != 0 {
count += 1;
}
}
for row in 0..9 {
if row != r && (self.candidates.get(row, c) & candidate_bit) != 0 {
count += 1;
}
}
for br in box_r..box_r + 3 {
for bc in box_c..box_c + 3 {
if br != r && bc != c && (self.candidates.get(br, bc) & candidate_bit) != 0 {
count += 1;
}
}
}
count
}
fn difficulty_for_technique(flags: TechniqueFlags) -> u8 {
if flags.is_empty() {
0
} else {
flags.bits().trailing_zeros() as u8 + 1
}
}
pub fn propagate_constraints(&mut self, path: &mut SolvePath, initial_path_len: usize) -> bool {
let techniques: Vec<&dyn TechniqueRule> = vec![
&NakedSingles,
&HiddenSingles,
&NakedPairs,
&HiddenPairs,
&LockedCandidates,
&NakedTriples,
&HiddenTriples,
&XWing,
&NakedQuads,
&HiddenQuads,
&Swordfish,
&Jellyfish,
&Skyscraper,
&WWing,
&XYWing,
&XyzWing,
];
loop {
let mut changed_this_iter = false;
for technique in &techniques {
if self.techniques_enabled.contains(technique.flags()) {
changed_this_iter |= technique.apply(self, path);
if changed_this_iter {
break;
}
}
}
if (0..9).any(|r| {
(0..9).any(|c| self.board.is_empty(r, c) && self.candidates.get(r, c) == 0)
}) {
while path.steps.len() > initial_path_len {
if let Some(step) = path.steps.pop() {
match step {
SolveStep::Placement {
row,
col,
value,
flags: _,
..
} => {
self.remove_and_update(row, col, value);
}
SolveStep::CandidateElimination {
row,
col,
value,
flags: _,
..
} => {
let initial_mask = self.candidates.get(row, col);
let refined_mask = initial_mask | (1 << (value - 1));
self.candidates.set(row, col, refined_mask);
}
}
}
}
return false;
}
if !changed_this_iter {
break;
}
}
true
}
}
pub trait TechniqueRule {
fn apply(&self, prop: &mut TechniquePropagator, path: &mut SolvePath) -> bool;
fn flags(&self) -> TechniqueFlags;
}