1use crate::sudoku::Sudoku;
2
3pub trait SolvingStrategy {
4 fn apply(&self, sudoku: &mut Sudoku) -> bool;
5 fn name(&self) -> &'static str;
6}
7
8pub struct NakedSingles;
10
11impl SolvingStrategy for NakedSingles {
12 fn apply(&self, sudoku: &mut Sudoku) -> bool {
13 let mut progress = false;
14
15 for row in 0..sudoku.size {
16 for col in 0..sudoku.size {
17 if sudoku.grid[row][col].is_empty() {
18 let candidates = sudoku.get_candidates(row, col);
19 if candidates.len() == 1 {
20 let value = *candidates.iter().next().unwrap();
21 sudoku.set(row, col, value).unwrap();
22 progress = true;
23 }
24 }
25 }
26 }
27
28 progress
29 }
30
31 fn name(&self) -> &'static str {
32 "Naked Singles"
33 }
34}
35
36pub struct HiddenSingles;
38
39impl SolvingStrategy for HiddenSingles {
40 fn apply(&self, sudoku: &mut Sudoku) -> bool {
41 let mut progress = false;
42
43 for row in 0..sudoku.size {
45 progress |= self.apply_to_row(sudoku, row);
46 }
47
48 for col in 0..sudoku.size {
50 progress |= self.apply_to_col(sudoku, col);
51 }
52
53 for box_row in 0..sudoku.box_size {
55 for box_col in 0..sudoku.box_size {
56 progress |= self.apply_to_box(sudoku, box_row, box_col);
57 }
58 }
59
60 progress
61 }
62
63 fn name(&self) -> &'static str {
64 "Hidden Singles"
65 }
66}
67
68impl HiddenSingles {
69 fn apply_to_row(&self, sudoku: &mut Sudoku, row: usize) -> bool {
70 let mut progress = false;
71
72 for value in 1..=sudoku.size as u8 {
73 let mut possible_cells = Vec::new();
74
75 for col in 0..sudoku.size {
76 if sudoku.grid[row][col].is_empty() {
77 let candidates = sudoku.get_candidates(row, col);
78 if candidates.contains(&value) {
79 possible_cells.push(col);
80 }
81 }
82 }
83
84 if possible_cells.len() == 1 {
85 let col = possible_cells[0];
86 sudoku.set(row, col, value).unwrap();
87 progress = true;
88 }
89 }
90
91 progress
92 }
93
94 fn apply_to_col(&self, sudoku: &mut Sudoku, col: usize) -> bool {
95 let mut progress = false;
96
97 for value in 1..=sudoku.size as u8 {
98 let mut possible_cells = Vec::new();
99
100 for row in 0..sudoku.size {
101 if sudoku.grid[row][col].is_empty() {
102 let candidates = sudoku.get_candidates(row, col);
103 if candidates.contains(&value) {
104 possible_cells.push(row);
105 }
106 }
107 }
108
109 if possible_cells.len() == 1 {
110 let row = possible_cells[0];
111 sudoku.set(row, col, value).unwrap();
112 progress = true;
113 }
114 }
115
116 progress
117 }
118
119 fn apply_to_box(&self, sudoku: &mut Sudoku, box_row: usize, box_col: usize) -> bool {
120 let mut progress = false;
121
122 for value in 1..=sudoku.size as u8 {
123 let mut possible_cells = Vec::new();
124
125 for row in box_row * sudoku.box_size..(box_row + 1) * sudoku.box_size {
126 for col in box_col * sudoku.box_size..(box_col + 1) * sudoku.box_size {
127 if sudoku.grid[row][col].is_empty() {
128 let candidates = sudoku.get_candidates(row, col);
129 if candidates.contains(&value) {
130 possible_cells.push((row, col));
131 }
132 }
133 }
134 }
135
136 if possible_cells.len() == 1 {
137 let (row, col) = possible_cells[0];
138 sudoku.set(row, col, value).unwrap();
139 progress = true;
140 }
141 }
142
143 progress
144 }
145}
146
147pub struct NakedPairs;
149
150impl SolvingStrategy for NakedPairs {
151 fn apply(&self, _sudoku: &mut Sudoku) -> bool {
152 false
155 }
156
157 fn name(&self) -> &'static str {
158 "Naked Pairs"
159 }
160}
161
162pub struct PointingPairs;
164
165impl SolvingStrategy for PointingPairs {
166 fn apply(&self, _sudoku: &mut Sudoku) -> bool {
167 false
170 }
171
172 fn name(&self) -> &'static str {
173 "Pointing Pairs"
174 }
175}
176
177pub struct BoxLineReduction;
179
180impl SolvingStrategy for BoxLineReduction {
181 fn apply(&self, _sudoku: &mut Sudoku) -> bool {
182 false
184 }
185
186 fn name(&self) -> &'static str {
187 "Box/Line Reduction"
188 }
189}
190
191pub struct XWing;
193
194impl SolvingStrategy for XWing {
195 fn apply(&self, _sudoku: &mut Sudoku) -> bool {
196 false
198 }
199
200 fn name(&self) -> &'static str {
201 "X-Wing"
202 }
203}
204
205pub struct Swordfish;
207
208impl SolvingStrategy for Swordfish {
209 fn apply(&self, _sudoku: &mut Sudoku) -> bool {
210 false
212 }
213
214 fn name(&self) -> &'static str {
215 "Swordfish"
216 }
217}
218
219pub struct YWing;
221
222impl YWing {
223 fn cells_see_each_other(&self, row1: usize, col1: usize, row2: usize, col2: usize, box_size: usize) -> bool {
224 row1 == row2 ||
226 col1 == col2 ||
227 (row1 / box_size == row2 / box_size && col1 / box_size == col2 / box_size)
228 }
229
230 fn apply_y_wing_elimination(&self, sudoku: &mut Sudoku, pivot: (usize, usize), wing1: (usize, usize), wing2: (usize, usize), value1: u8, value2: u8) -> bool {
231 let mut progress = false;
232
233 for row in 0..sudoku.size {
235 for col in 0..sudoku.size {
236 if sudoku.grid[row][col].is_empty() &&
237 (row, col) != pivot && (row, col) != wing1 && (row, col) != wing2 {
238
239 if self.cells_see_each_other(row, col, wing1.0, wing1.1, sudoku.box_size) &&
240 self.cells_see_each_other(row, col, wing2.0, wing2.1, sudoku.box_size) {
241
242 let mut candidates = sudoku.get_candidates(row, col);
244 let original_size = candidates.len();
245
246 candidates.remove(&value1);
247 candidates.remove(&value2);
248
249 if candidates.len() < original_size {
250 progress = true;
251 }
254 }
255 }
256 }
257 }
258
259 progress
260 }
261}
262
263impl SolvingStrategy for YWing {
264 fn apply(&self, sudoku: &mut Sudoku) -> bool {
265 let mut progress = false;
266
267 let mut bi_value_cells = Vec::new();
269 for row in 0..sudoku.size {
270 for col in 0..sudoku.size {
271 if sudoku.grid[row][col].is_empty() {
272 let candidates = sudoku.get_candidates(row, col);
273 if candidates.len() == 2 {
274 let values: Vec<u8> = candidates.into_iter().collect();
275 bi_value_cells.push((row, col, values[0], values[1]));
276 }
277 }
278 }
279 }
280
281 for i in 0..bi_value_cells.len() {
283 let (pivot_row, pivot_col, pivot_a, pivot_b) = bi_value_cells[i];
284
285 for j in i + 1..bi_value_cells.len() {
286 let (wing1_row, wing1_col, wing1_a, wing1_b) = bi_value_cells[j];
287
288 let shared_value = if pivot_a == wing1_a || pivot_a == wing1_b {
290 Some(pivot_a)
291 } else if pivot_b == wing1_a || pivot_b == wing1_b {
292 Some(pivot_b)
293 } else {
294 None
295 };
296
297 if let Some(shared) = shared_value {
298 let pivot_other = if pivot_a == shared { pivot_b } else { pivot_a };
299 let wing1_other = if wing1_a == shared { wing1_b } else { wing1_a };
300
301 for k in j + 1..bi_value_cells.len() {
303 let (wing2_row, wing2_col, wing2_a, wing2_b) = bi_value_cells[k];
304
305 if (wing2_a == pivot_other && wing2_b == wing1_other) ||
307 (wing2_a == wing1_other && wing2_b == pivot_other) {
308
309 if self.cells_see_each_other(pivot_row, pivot_col, wing1_row, wing1_col, sudoku.box_size) &&
311 self.cells_see_each_other(pivot_row, pivot_col, wing2_row, wing2_col, sudoku.box_size) &&
312 !self.cells_see_each_other(wing1_row, wing1_col, wing2_row, wing2_col, sudoku.box_size) {
313
314 progress |= self.apply_y_wing_elimination(
316 sudoku,
317 (pivot_row, pivot_col),
318 (wing1_row, wing1_col),
319 (wing2_row, wing2_col),
320 pivot_other,
321 wing1_other
322 );
323 }
324 }
325 }
326 }
327 }
328 }
329
330 progress
331 }
332
333 fn name(&self) -> &'static str {
334 "Y-Wing"
335 }
336}
337
338pub struct XYWing;
340
341impl XYWing {
342 fn cells_see_each_other(&self, row1: usize, col1: usize, row2: usize, col2: usize, box_size: usize) -> bool {
343 row1 == row2 ||
345 col1 == col2 ||
346 (row1 / box_size == row2 / box_size && col1 / box_size == col2 / box_size)
347 }
348
349 fn apply_xy_wing_elimination(&self, sudoku: &mut Sudoku, _pivot: (usize, usize), xz_wing: (usize, usize), yz_wing: (usize, usize), z_value: u8) -> bool {
350 let mut progress = false;
351
352 for row in 0..sudoku.size {
354 for col in 0..sudoku.size {
355 if sudoku.grid[row][col].is_empty() &&
356 (row, col) != xz_wing && (row, col) != yz_wing {
357
358 if self.cells_see_each_other(row, col, xz_wing.0, xz_wing.1, sudoku.box_size) &&
359 self.cells_see_each_other(row, col, yz_wing.0, yz_wing.1, sudoku.box_size) {
360
361 let mut candidates = sudoku.get_candidates(row, col);
363 if candidates.contains(&z_value) {
364 candidates.remove(&z_value);
365 progress = true;
366 }
368 }
369 }
370 }
371 }
372
373 progress
374 }
375}
376
377impl SolvingStrategy for XYWing {
378 fn apply(&self, sudoku: &mut Sudoku) -> bool {
379 let mut progress = false;
380
381 let mut bi_value_cells = Vec::new();
383 for row in 0..sudoku.size {
384 for col in 0..sudoku.size {
385 if sudoku.grid[row][col].is_empty() {
386 let candidates = sudoku.get_candidates(row, col);
387 if candidates.len() == 2 {
388 let values: Vec<u8> = candidates.into_iter().collect();
389 bi_value_cells.push((row, col, values[0], values[1]));
390 }
391 }
392 }
393 }
394
395 for i in 0..bi_value_cells.len() {
397 let (pivot_row, pivot_col, x, y) = bi_value_cells[i];
398
399 for j in 0..bi_value_cells.len() {
401 if i == j { continue; }
402 let (xz_row, xz_col, xz_a, xz_b) = bi_value_cells[j];
403
404 let z_value = if xz_a == x {
406 Some(xz_b)
407 } else if xz_b == x {
408 Some(xz_a)
409 } else {
410 None
411 };
412
413 if let Some(z) = z_value {
414 if z == y { continue; } for k in 0..bi_value_cells.len() {
418 if k == i || k == j { continue; }
419 let (yz_row, yz_col, yz_a, yz_b) = bi_value_cells[k];
420
421 if (yz_a == y && yz_b == z) || (yz_a == z && yz_b == y) {
423 if self.cells_see_each_other(pivot_row, pivot_col, xz_row, xz_col, sudoku.box_size) &&
425 self.cells_see_each_other(pivot_row, pivot_col, yz_row, yz_col, sudoku.box_size) {
426
427 progress |= self.apply_xy_wing_elimination(
429 sudoku,
430 (pivot_row, pivot_col),
431 (xz_row, xz_col),
432 (yz_row, yz_col),
433 z
434 );
435 }
436 }
437 }
438 }
439 }
440 }
441
442 progress
443 }
444
445 fn name(&self) -> &'static str {
446 "XY-Wing"
447 }
448}
449
450pub fn get_all_strategies() -> Vec<Box<dyn SolvingStrategy>> {
451 vec![
452 Box::new(NakedSingles),
453 Box::new(HiddenSingles),
454 Box::new(NakedPairs),
455 Box::new(PointingPairs),
456 Box::new(BoxLineReduction),
457 Box::new(XWing),
458 Box::new(YWing),
459 Box::new(XYWing),
460 Box::new(Swordfish),
461 ]
462}