use crate::core::SolvePath;
use super::{TechniquePropagator, TechniqueRule, units};
pub struct HiddenSingles;
impl TechniqueRule for HiddenSingles {
fn apply(&self, prop: &mut TechniquePropagator, path: &mut SolvePath) -> bool {
let mut overall_placements_made = false;
let check_unit_hidden_singles =
|unit_cells: &[(usize, usize)],
prop: &mut TechniquePropagator,
path: &mut SolvePath| {
let mut unit_placement_made = false;
for cand_val in 1..=9 {
let cand_bit = 1 << (cand_val - 1);
let mut potential_cell: Option<(usize, usize)> = None;
let mut cand_occurrences = 0;
for &(r, c) in unit_cells.iter() {
if prop.board.is_empty(r, c) {
let cell_cand_mask = prop.candidates.get(r, c);
if (cell_cand_mask & cand_bit) != 0 {
cand_occurrences += 1;
potential_cell = Some((r, c));
}
}
}
if cand_occurrences == 1 {
if let Some((r, c)) = potential_cell {
if prop.board.is_empty(r, c) {
prop.place_and_update(r, c, cand_val, self.flags(), path);
unit_placement_made = true;
}
}
}
}
unit_placement_made
};
for r in 0..9 {
let cells = units::row_cells(r);
if check_unit_hidden_singles(&cells, prop, path) {
overall_placements_made = true;
}
}
for c in 0..9 {
let cells = units::col_cells(c);
if check_unit_hidden_singles(&cells, prop, path) {
overall_placements_made = true;
}
}
for box_idx in 0..9 {
let cells = units::box_cells(box_idx);
if check_unit_hidden_singles(&cells, prop, path) {
overall_placements_made = true;
}
}
overall_placements_made
}
fn flags(&self) -> crate::core::TechniqueFlags {
crate::core::TechniqueFlags::HIDDEN_SINGLES
}
}
#[cfg(test)]
mod tests {
use crate::core::{Rustoku, SolvePath, TechniqueFlags};
#[test]
fn test_hidden_singles_places_in_correct_cell() {
let s = "008007000016083000000000051107290000000000000000046307290000000000860140000300700";
let mut rustoku = Rustoku::new_from_str(s)
.unwrap()
.with_techniques(TechniqueFlags::HIDDEN_SINGLES);
let mut path = SolvePath::default();
rustoku.techniques_make_valid_changes(&mut path);
let placements: Vec<_> = path
.steps
.iter()
.filter_map(|step| match step {
crate::core::SolveStep::Placement {
row,
col,
value,
flags,
..
} if flags.contains(TechniqueFlags::HIDDEN_SINGLES) => Some((*row, *col, *value)),
_ => None,
})
.collect();
assert!(
!placements.is_empty(),
"Hidden singles should produce at least one placement"
);
for &(r, c, v) in &placements {
assert!((1..=9).contains(&v), "Placed value must be 1-9, got {v}");
assert_eq!(
rustoku.board.get(r, c),
v,
"Board cell ({r},{c}) should be {v} after hidden single"
);
}
let original = crate::core::Board::try_from(s).unwrap();
for r in 0..9 {
for c in 0..9 {
let orig_val = original.get(r, c);
if orig_val != 0 {
assert_eq!(
rustoku.board.get(r, c),
orig_val,
"Clue at ({r},{c}) was overwritten"
);
}
}
}
}
}