rustoku_lib/core/
candidates.rs

1use super::board::Board;
2use super::masks::Masks;
3
4/// Represents the candidates cache for a Rustoku puzzle.
5///
6/// This struct holds a 9x9 grid of candidate masks for each cell in the Rustoku board.
7/// Each cell's candidates are represented as a bitmask, where each bit corresponds to a number
8/// from 1 to 9. A bit set to 1 indicates that the corresponding number is a candidate for that cell.
9/// This struct provides methods to get and set candidate masks for specific cells, as well as to
10/// update the candidates based on the current state of the board and masks.
11#[derive(Debug, Copy, Clone, PartialEq, Eq)]
12pub struct Candidates {
13    cache: [[u16; 9]; 9],
14}
15
16impl Candidates {
17    pub(super) fn new() -> Self {
18        Candidates { cache: [[0; 9]; 9] }
19    }
20
21    /// Returns the candidate mask for a specific cell in the cache.
22    pub(super) fn get(&self, r: usize, c: usize) -> u16 {
23        self.cache[r][c]
24    }
25
26    /// Returns the actual candidate numbers (1-9) for a specific cell.
27    pub fn get_candidates(&self, r: usize, c: usize) -> Vec<u8> {
28        let mask = self.get(r, c);
29        let mut candidates = Vec::new();
30        for i in 0..9 {
31            // Check if the i-th bit is set (representing number i+1)
32            if (mask >> i) & 1 == 1 {
33                candidates.push((i + 1) as u8);
34            }
35        }
36        candidates
37    }
38
39    /// Sets the candidate mask for a specific cell in the cache.
40    pub(super) fn set(&mut self, r: usize, c: usize, mask: u16) {
41        self.cache[r][c] = mask;
42    }
43
44    /// Update affected cells in the cache based on the current state of the board and masks.
45    pub(super) fn update_affected_cells(
46        &mut self,
47        r: usize,
48        c: usize,
49        masks: &Masks,
50        board: &Board,
51    ) {
52        // Invalidate/update cache for the placed cell
53        self.cache[r][c] = 0; // No candidates for a filled cell
54
55        // Update cache for affected row, column, and box
56        for i in 0..9 {
57            if board.is_empty(r, i) {
58                self.cache[r][i] = masks.compute_candidates_mask_for_cell(r, i);
59            }
60            if board.is_empty(i, c) {
61                self.cache[i][c] = masks.compute_candidates_mask_for_cell(i, c);
62            }
63        }
64
65        // Update box cells
66        let box_idx = Masks::get_box_idx(r, c);
67        let start_row = (box_idx / 3) * 3;
68        let start_col = (box_idx % 3) * 3;
69        for r_offset in 0..3 {
70            for c_offset in 0..3 {
71                let cur_r = start_row + r_offset;
72                let cur_c = start_col + c_offset;
73                if board.is_empty(cur_r, cur_c) {
74                    self.cache[cur_r][cur_c] = masks.compute_candidates_mask_for_cell(cur_r, cur_c);
75                }
76            }
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_get_candidates_empty_mask() {
87        let candidates = Candidates::new(); // Starts with all 0s
88        let r = 0;
89        let c = 0;
90        let cands = candidates.get_candidates(r, c);
91        assert_eq!(cands, vec![]);
92    }
93
94    #[test]
95    fn test_get_candidates_full_mask() {
96        let mut candidates = Candidates::new();
97        let r = 0;
98        let c = 0;
99        // All bits from 0 to 8 set (representing numbers 1 to 9)
100        // 0b00000001_11111111 = 511 (binary)
101        let full_mask = (1 << 9) - 1; // Or 0b111111111
102        candidates.set(r, c, full_mask);
103        let cands = candidates.get_candidates(r, c);
104        assert_eq!(cands, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
105    }
106
107    #[test]
108    fn test_get_candidates_single_candidate() {
109        let mut candidates = Candidates::new();
110        let r = 1;
111        let c = 2;
112
113        // Test for candidate 1 (bit 0)
114        candidates.set(r, c, 1 << 0); // Mask: 0b000000001
115        let cands_1 = candidates.get_candidates(r, c);
116        assert_eq!(cands_1, vec![1]);
117
118        // Test for candidate 5 (bit 4)
119        candidates.set(r, c, 1 << 4); // Mask: 0b000010000
120        let cands_5 = candidates.get_candidates(r, c);
121        assert_eq!(cands_5, vec![5]);
122
123        // Test for candidate 9 (bit 8)
124        candidates.set(r, c, 1 << 8); // Mask: 0b100000000
125        let cands_9 = candidates.get_candidates(r, c);
126        assert_eq!(cands_9, vec![9]);
127    }
128
129    #[test]
130    fn test_get_candidates_multiple_candidates() {
131        let mut candidates = Candidates::new();
132        let r = 3;
133        let c = 4;
134
135        // Candidates: 2, 4, 7
136        // Bit positions: 1, 3, 6
137        // Mask: (1 << 1) | (1 << 3) | (1 << 6)
138        // Mask: 0b01001010 = 2 + 8 + 64 = 74
139        let mask = (1 << 1) | (1 << 3) | (1 << 6); // 0b01001010
140        candidates.set(r, c, mask);
141        let cands = candidates.get_candidates(r, c);
142        assert_eq!(cands, vec![2, 4, 7]);
143
144        // Candidates: 1, 9
145        // Bit positions: 0, 8
146        let mask_1_9 = (1 << 0) | (1 << 8); // 0b100000001
147        candidates.set(r, c, mask_1_9);
148        let cands_1_9 = candidates.get_candidates(r, c);
149        assert_eq!(cands_1_9, vec![1, 9]);
150    }
151
152    #[test]
153    fn test_get_candidates_different_cells() {
154        let mut candidates = Candidates::new();
155
156        // Set candidates for (0,0)
157        candidates.set(0, 0, (1 << 0) | (1 << 2)); // Candidates 1, 3
158        // Set candidates for (8,8)
159        candidates.set(8, 8, (1 << 5) | (1 << 7)); // Candidates 6, 8
160
161        assert_eq!(candidates.get_candidates(0, 0), vec![1, 3]);
162        assert_eq!(candidates.get_candidates(8, 8), vec![6, 8]);
163        assert_eq!(candidates.get_candidates(0, 1), vec![]); // Unset cell
164    }
165}