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}