rustoku_lib/core/techniques/mod.rs
1use crate::core::{SolvePath, SolveStep};
2
3use super::board::Board;
4use super::candidates::Candidates;
5use super::masks::Masks;
6
7pub mod flags;
8mod hidden_pairs;
9mod hidden_singles;
10mod locked_candidates;
11mod naked_pairs;
12mod naked_singles;
13mod x_wing;
14
15use flags::TechniqueFlags;
16use hidden_pairs::HiddenPairs;
17use hidden_singles::HiddenSingles;
18use locked_candidates::LockedCandidates;
19use naked_pairs::NakedPairs;
20use naked_singles::NakedSingles;
21use x_wing::XWing;
22
23/// Propagates constraints via zero or more techniques.
24///
25/// The techniques are toggled via bitflags. Most of the data in struct comes
26/// from the Rustoku instance, which has a longer lifetime than this struct - since
27/// it is only used at the start, before any backtracking occurs.
28///
29/// Some examples of techniques employed including Naked Singles and X-Wings.
30/// If we want to add more techniques, extend the existing logic and bitflags
31/// in this module.
32///
33/// This class acts as the Mediator object between `Rustoku` and the `TechniqueRule`
34/// implementations out there. To learn about the Mediator design pattern, please
35/// consult [this link](https://refactoring.guru/design-patterns/mediator)
36/// for more details.
37pub struct TechniquePropagator<'a> {
38 board: &'a mut Board,
39 masks: &'a mut Masks,
40 candidates: &'a mut Candidates,
41 techniques_enabled: TechniqueFlags,
42}
43
44impl<'a> TechniquePropagator<'a> {
45 pub fn new(
46 board: &'a mut Board,
47 masks: &'a mut Masks,
48 candidates: &'a mut Candidates,
49 techniques_enabled: TechniqueFlags,
50 ) -> Self {
51 Self {
52 board,
53 masks,
54 candidates,
55 techniques_enabled,
56 }
57 }
58
59 /// Helper to place a number and update caches.
60 fn place_and_update(
61 &mut self,
62 r: usize,
63 c: usize,
64 num: u8,
65 flags: TechniqueFlags,
66 path: &mut SolvePath,
67 ) {
68 self.board.set(r, c, num);
69 self.masks.add_number(r, c, num);
70 self.candidates
71 .update_affected_cells(r, c, self.masks, self.board);
72 path.steps.push(SolveStep::Placement {
73 row: r,
74 col: c,
75 value: num,
76 flags,
77 });
78 }
79
80 /// Helper to remove a number and update caches.
81 fn remove_and_update(&mut self, r: usize, c: usize, num: u8) {
82 self.board.set(r, c, 0);
83 self.masks.remove_number(r, c, num);
84 self.candidates
85 .update_affected_cells(r, c, self.masks, self.board);
86 // Note: For propagation, `remove_number` is mostly for backtracking, not direct technique application.
87 // The `update_affected_cells` on removal will recalculate candidates for the now-empty cell.
88 }
89
90 /// Helper to eliminate a candidate and update caches.
91 fn eliminate_candidate(
92 &mut self,
93 r: usize,
94 c: usize,
95 candidate_bit: u16, // Assume only one candidate is being eliminated
96 flags: TechniqueFlags,
97 path: &mut SolvePath,
98 ) -> bool {
99 let initial_mask = self.candidates.get(r, c);
100 let refined_mask = initial_mask & !candidate_bit;
101 self.candidates.set(r, c, refined_mask);
102
103 let num = candidate_bit.trailing_zeros() as u8 + 1; // Convert bit to number
104 path.steps.push(SolveStep::CandidateElimination {
105 row: r,
106 col: c,
107 value: num,
108 flags,
109 });
110
111 initial_mask != refined_mask // Return true if a candidate was eliminated
112 }
113
114 /// Helper to eliminate multiple candidates and update caches
115 fn eliminate_multiple_candidates(
116 &mut self,
117 r: usize,
118 c: usize,
119 elimination_mask: u16, // bits to eliminate
120 flags: TechniqueFlags,
121 path: &mut SolvePath,
122 ) -> bool {
123 let initial_mask = self.candidates.get(r, c);
124 let refined_mask = initial_mask & !elimination_mask;
125 self.candidates.set(r, c, refined_mask);
126
127 // Log each eliminated candidate
128 let eliminated_mask = initial_mask & elimination_mask; // what was actually eliminated
129 for candidate in 1..=9 {
130 let candidate_bit = 1 << (candidate - 1);
131 if (eliminated_mask & candidate_bit) != 0 {
132 path.steps.push(SolveStep::CandidateElimination {
133 row: r,
134 col: c,
135 value: candidate,
136 flags,
137 });
138 }
139 }
140
141 initial_mask != refined_mask // Return true if a candidate was eliminated
142 }
143
144 /// Applies deterministic constraint propagation techniques iteratively.
145 pub fn propagate_constraints(&mut self, path: &mut SolvePath, initial_path_len: usize) -> bool {
146 let techniques: Vec<&dyn TechniqueRule> = vec![
147 &NakedSingles,
148 &HiddenSingles,
149 &NakedPairs,
150 &HiddenPairs,
151 &LockedCandidates,
152 &XWing,
153 ];
154
155 loop {
156 let mut changed_this_iter = false;
157
158 for technique in &techniques {
159 if self.techniques_enabled.contains(technique.flags()) {
160 // Pass the propagator itself and the current path to the technique. This
161 // is an example of the Mediator pattern, where the propagator
162 // mediates the interaction between the techniques and the board
163 changed_this_iter |= technique.apply(self, path);
164 if changed_this_iter {
165 break;
166 }
167 }
168 }
169
170 if (0..9).any(|r| {
171 (0..9).any(|c| self.board.is_empty(r, c) && self.candidates.get(r, c) == 0)
172 }) {
173 while path.steps.len() > initial_path_len {
174 if let Some(step) = path.steps.pop() {
175 match step {
176 SolveStep::Placement {
177 row,
178 col,
179 value,
180 flags: _,
181 } => {
182 self.remove_and_update(row, col, value);
183 }
184 SolveStep::CandidateElimination {
185 row,
186 col,
187 value,
188 flags: _,
189 } => {
190 // This is a candidate elimination step, we need to restore the candidate
191 // in the candidates cache
192 let initial_mask = self.candidates.get(row, col);
193 let refined_mask = initial_mask | (1 << (value - 1));
194 self.candidates.set(row, col, refined_mask);
195 }
196 }
197 }
198 }
199 return false;
200 }
201
202 if !changed_this_iter {
203 break;
204 }
205 }
206 true
207 }
208}
209
210/// This is the contract for all human techniques.
211///
212/// All techniques are expected to have a way to apply themselves to a board
213/// and modify the solve path with placements and eliminations. In addition, they
214/// are expected to return one flag that helps with technique attribution when
215/// people want to visualize the solve path.
216pub trait TechniqueRule {
217 /// Applies the technique to the given propagator.
218 fn apply(&self, prop: &mut TechniquePropagator, path: &mut SolvePath) -> bool;
219
220 /// Returns the flags associated with this technique.
221 fn flags(&self) -> TechniqueFlags;
222}