1pub mod fish;
2pub mod single_digit_patterns;
3use crate::sudoku::{CellIndex, CellValue, Step, StepKind, StepRule, Sudoku};
4use crate::utils::{CellSet, NamedCellSet};
5
6use std::cell::OnceCell;
7use std::collections::HashSet;
8
9use itertools::Itertools;
10use wasm_bindgen::prelude::*;
11
12#[wasm_bindgen]
13pub struct SudokuSolver {
14 sudoku: Sudoku,
15
16 all_constraints: Vec<NamedCellSet>,
17 constraints_of_cell: Vec<Vec<NamedCellSet>>,
18 house_union_of_cell: Vec<CellSet>,
19 block_id_of_cell: Vec<usize>,
20 row_id_of_cell: Vec<usize>,
21 column_id_of_cell: Vec<usize>,
22
23 cells_in_rows: Vec<NamedCellSet>,
24 cells_in_columns: Vec<NamedCellSet>,
25 cells_in_blocks: Vec<NamedCellSet>,
26 candidate_cells_in_rows: OnceCell<Vec<Vec<NamedCellSet>>>,
27 candidate_cells_in_columns: OnceCell<Vec<Vec<NamedCellSet>>>,
28 candidate_cells_in_blocks: OnceCell<Vec<Vec<NamedCellSet>>>,
29
30 possible_positions_for_house_and_value: Vec<OnceCell<NamedCellSet>>,
31}
32
33macro_rules! return_if_some {
34 ($x:expr) => {
35 if let Some(x) = $x {
36 return Some(x);
37 }
38 };
39}
40
41pub(crate) use return_if_some;
42
43impl SudokuSolver {
44 pub fn sudoku(&self) -> &Sudoku {
45 &self.sudoku
46 }
47
48 pub(crate) fn cell_index(&self, row: usize, col: usize) -> CellIndex {
49 self.sudoku.get_cell_position(row, col)
50 }
51
52 pub(crate) fn cell_value(&self, idx: CellIndex) -> Option<CellValue> {
53 self.sudoku.get_cell_value(idx)
54 }
55
56 pub(crate) fn candidates(&self, idx: CellIndex) -> &Vec<CellValue> {
57 self.sudoku.get_candidates(idx)
58 }
59
60 pub(crate) fn possible_cells(&self, value: CellValue) -> &CellSet {
61 self.sudoku.get_possible_cells(value)
62 }
63
64 pub(crate) fn can_fill(&self, idx: CellIndex, value: CellValue) -> bool {
65 self.sudoku.can_fill(idx, value)
66 }
67
68 pub(crate) fn all_constraints(&self) -> &[NamedCellSet] {
69 &self.all_constraints
70 }
71
72 pub(crate) fn constraints_of_cell(&self, idx: CellIndex) -> &[NamedCellSet] {
73 &self.constraints_of_cell[idx as usize]
74 }
75
76 pub(crate) fn house_union_of_cell(&self, idx: CellIndex) -> &CellSet {
77 &self.house_union_of_cell[idx as usize]
78 }
79
80 pub(crate) fn block_id_of_cell(&self, idx: CellIndex) -> usize {
81 self.block_id_of_cell[idx as usize]
82 }
83
84 pub(crate) fn cell_of_intersection(
85 &self,
86 house_1: &NamedCellSet,
87 house_2: &NamedCellSet,
88 ) -> CellIndex {
89 let (row_idx, col_idx) = if house_1.idx() < 18 {
90 debug_assert!(house_1.idx() >= 9);
91 debug_assert!(house_2.idx() >= 18);
92 debug_assert!(house_2.idx() < 27);
93 (house_1.idx() - 9, house_2.idx() - 18)
94 } else {
95 debug_assert!(house_2.idx() >= 9);
96 debug_assert!(house_1.idx() >= 18);
97 debug_assert!(house_1.idx() < 27);
98 (house_2.idx() - 9, house_1.idx() - 18)
99 };
100 return (row_idx * 9 + col_idx) as CellIndex;
101 }
102
103 pub(crate) fn row_id_of_cell(&self, idx: CellIndex) -> usize {
104 self.row_id_of_cell[idx as usize]
105 }
106
107 pub(crate) fn column_id_of_cell(&self, idx: CellIndex) -> usize {
108 self.column_id_of_cell[idx as usize]
109 }
110
111 pub(crate) fn cells_in_rows(&self) -> &[NamedCellSet] {
112 &self.cells_in_rows
113 }
114
115 pub(crate) fn cells_in_columns(&self) -> &[NamedCellSet] {
116 &self.cells_in_columns
117 }
118
119 pub(crate) fn cells_in_blocks(&self) -> &[NamedCellSet] {
120 &self.cells_in_blocks
121 }
122
123 pub(crate) fn candidate_cells_in_rows(&self, value: CellValue) -> &[NamedCellSet] {
124 &self.candidate_cells_in_rows.get_or_init(|| {
125 (1..=9)
126 .map(|value| {
127 self.cells_in_rows
128 .iter()
129 .map(|row| {
130 NamedCellSet::from_cellset(row, self.possible_cells(value) & row)
131 })
132 .collect()
133 })
134 .collect()
135 })[value as usize - 1]
136 }
137
138 pub(crate) fn candidate_cells_in_columns(&self, value: CellValue) -> &[NamedCellSet] {
139 &self.candidate_cells_in_columns.get_or_init(|| {
140 (1..=9)
141 .map(|value| {
142 self.cells_in_columns
143 .iter()
144 .map(|col| {
145 NamedCellSet::from_cellset(col, self.possible_cells(value) & col)
146 })
147 .collect()
148 })
149 .collect()
150 })[value as usize - 1]
151 }
152
153 pub(crate) fn candidate_cells_in_blocks(&self, value: CellValue) -> &[NamedCellSet] {
154 &self.candidate_cells_in_blocks.get_or_init(|| {
155 (1..=9)
156 .map(|value| {
157 self.cells_in_blocks
158 .iter()
159 .map(|block| {
160 NamedCellSet::from_cellset(block, self.possible_cells(value) & block)
161 })
162 .collect()
163 })
164 .collect()
165 })[value as usize - 1]
166 }
167
168 pub(crate) fn get_possible_cells_for_house_and_value(
169 &self,
170 house: &NamedCellSet,
171 value: CellValue,
172 ) -> &NamedCellSet {
173 debug_assert!(house.idx() < 27);
174 debug_assert!(value >= 1 && value <= 9);
175 let idx = house.idx() * 9 + value as usize - 1;
176 self.possible_positions_for_house_and_value[idx]
177 .get_or_init(|| NamedCellSet::from_cellset(house, self.possible_cells(value) & house))
178 }
179
180 pub(crate) fn get_cell_name(&self, idx: CellIndex) -> String {
181 format!("r{}c{}", idx / 9 + 1, idx % 9 + 1)
182 }
183
184 pub(crate) fn get_cellset_string(&self, cellset: &CellSet) -> String {
185 cellset.iter().map(|idx| self.get_cell_name(idx)).join(",")
186 }
187}
188
189#[wasm_bindgen]
190impl SudokuSolver {
191 pub fn new(sudoku: Sudoku) -> Self {
192 let mut all_constraints = vec![];
193 let mut constraints_of_cell = (0..81).map(|_| vec![]).collect::<Vec<_>>();
194 let mut house_union_of_cell = (0..81).map(|_| CellSet::new()).collect::<Vec<_>>();
195 let mut block_id_of_cell = vec![0; 81];
196 let mut row_id_of_cell = vec![0; 81];
197 let mut column_id_of_cell = vec![0; 81];
198 let mut cells_in_rows = vec![];
199 let mut cells_in_columns = vec![];
200 let mut cells_in_blocks = vec![];
201 let possible_positions_for_house_and_value = vec![OnceCell::new(); 27 * 9];
202
203 for block_x in (0..9).step_by(3) {
204 for block_y in (0..9).step_by(3) {
205 let mut block_set = NamedCellSet::new(
206 format!("b{}", block_x + block_y / 3 + 1),
207 block_x + block_y / 3,
208 );
209 for x in 0..3 {
210 for y in 0..3 {
211 let pos = sudoku.get_cell_position(block_x + x, block_y + y);
212 block_set.add(pos);
213 block_id_of_cell[pos as usize] = block_x + block_y / 3;
214 }
215 }
216 all_constraints.push(block_set.clone());
217 cells_in_blocks.push(block_set);
218 }
219 }
220
221 for row in 0..9 {
222 let mut row_set = NamedCellSet::new(format!("r{}", row + 1), 9 + row);
223 for col in 0..9 {
224 let pos = sudoku.get_cell_position(row, col);
225 row_set.add(pos);
226 row_id_of_cell[pos as usize] = row;
227 }
228 all_constraints.push(row_set.clone());
229 cells_in_rows.push(row_set);
230 }
231
232 for col in 0..9 {
233 let mut col_set = NamedCellSet::new(format!("c{}", col + 1), 18 + col);
234 for row in 0..9 {
235 let pos = sudoku.get_cell_position(row, col);
236 col_set.add(pos);
237 column_id_of_cell[pos as usize] = col;
238 }
239 all_constraints.push(col_set.clone());
240 cells_in_columns.push(col_set);
241 }
242
243 for row in 0..9 {
244 for col in 0..9 {
245 let pos = sudoku.get_cell_position(row, col) as usize;
246 let block_x = row / 3;
247 let block_y = col / 3;
248 let block_idx = block_x * 3 + block_y;
249 constraints_of_cell[pos].push(cells_in_rows[row].clone());
250 constraints_of_cell[pos].push(cells_in_columns[col].clone());
251 constraints_of_cell[pos].push(cells_in_blocks[block_idx].clone());
252 house_union_of_cell[pos] =
253 &(&cells_in_rows[row] | &cells_in_columns[col]) | &cells_in_blocks[block_idx];
254 }
255 }
256
257 SudokuSolver {
258 sudoku,
259
260 all_constraints,
261 constraints_of_cell,
262 house_union_of_cell,
263 block_id_of_cell,
264 row_id_of_cell,
265 column_id_of_cell,
266
267 cells_in_rows,
268 cells_in_columns,
269 cells_in_blocks,
270 candidate_cells_in_rows: OnceCell::new(),
271 candidate_cells_in_columns: OnceCell::new(),
272 candidate_cells_in_blocks: OnceCell::new(),
273
274 possible_positions_for_house_and_value,
275 }
276 }
277
278 pub fn get_invalid_positions(&self) -> Vec<CellIndex> {
279 let mut invalid_positions = vec![];
280 for house in self.all_constraints.iter() {
281 for (i, cell1) in house.iter().enumerate() {
282 if self.cell_value(cell1).is_none() {
283 continue;
284 }
285 for cell2 in house.iter().take(i) {
286 if cell1 == cell2 {
287 invalid_positions.push(cell1);
288 }
289 }
290 }
291 }
292 invalid_positions
293 }
294
295 pub fn initialize_candidates(&mut self) {
296 for cell in 0..81 {
297 if self.cell_value(cell).is_none() {
298 let mut candidates: HashSet<_> = (1..=9).collect();
299
300 for constraint in self.constraints_of_cell(cell).iter() {
301 for other_cell in constraint.iter() {
302 if cell == other_cell {
303 continue;
304 }
305 if let Some(other_value) = self.cell_value(other_cell) {
306 candidates.remove(&other_value);
307 }
308 }
309 }
310
311 for &candidate in candidates.iter().sorted() {
312 self.sudoku.add_candidate(cell, candidate);
313 }
314 }
315 }
316 }
317
318 pub fn apply_step(&mut self, step: &Step) {
319 self.possible_positions_for_house_and_value = vec![OnceCell::new(); 27 * 9];
320 self.candidate_cells_in_rows.take();
321 self.candidate_cells_in_columns.take();
322 self.candidate_cells_in_blocks.take();
323 match step.kind {
324 StepKind::ValueSet => {
325 for position in step.positions.iter() {
326 self.sudoku.fill(position.cell_index, position.value);
327 for cell in self.house_union_of_cell[position.cell_index as usize].iter() {
328 if cell == position.cell_index {
329 continue;
330 }
331 self.sudoku.remove_candidate(cell, position.value);
332 }
333 }
334 }
335 StepKind::CandidateEliminated => {
336 for position in step.positions.iter() {
337 self.sudoku
338 .remove_candidate(position.cell_index, position.value);
339 }
340 }
341 }
342 }
343
344 pub fn is_completed(&self) -> bool {
345 for cell in 0..81 {
346 if self.cell_value(cell).is_none() {
347 return false;
348 }
349 }
350 true
351 }
352
353 pub fn solve_full_house(&self) -> Option<Step> {
354 for house in self.all_constraints.iter() {
355 let unfilled_cells_count = house
356 .iter()
357 .filter(|&cell| self.cell_value(cell).is_none())
358 .count();
359 if unfilled_cells_count == 1 {
360 let unfilled_cell = house
361 .iter()
362 .filter(|&cell| self.cell_value(cell).is_none())
363 .next()
364 .unwrap();
365 let missing_value = self
366 .candidates(unfilled_cell)
367 .iter()
368 .cloned()
369 .next()
370 .unwrap();
371 return Some(Step::new_value_set(
372 StepRule::FullHouse,
373 format!(
374 "{} is the only missing cell in {}",
375 self.get_cell_name(unfilled_cell),
376 house.name()
377 ),
378 unfilled_cell,
379 missing_value,
380 ));
381 }
382 }
383 None
384 }
385
386 pub fn solve_naked_single(&self) -> Option<Step> {
387 for house in self.all_constraints.iter() {
388 for cell in house.iter() {
389 if self.candidates(cell).len() == 1 {
390 let &value = self.candidates(cell).iter().next().unwrap();
391 return Some(Step::new_value_set(
392 StepRule::NakedSingle,
393 format!(
394 "{} is the only possible value to fill {}",
395 value,
396 self.get_cell_name(cell)
397 ),
398 cell,
399 value,
400 ));
401 }
402 }
403 }
404 None
405 }
406
407 pub fn solve_hidden_single(&self) -> Option<Step> {
408 for house in self.all_constraints.iter() {
409 for value in 1..=9 {
410 let possible_cells = self.get_possible_cells_for_house_and_value(house, value);
411 if possible_cells.size() == 1 {
412 let target_cell = possible_cells.iter().next().unwrap();
413 return Some(Step::new_value_set(
414 StepRule::HiddenSingle,
415 format!(
416 "in {}, {} is the only possible cell that can be {}",
417 house.name(),
418 self.get_cell_name(target_cell),
419 value,
420 ),
421 target_cell,
422 value,
423 ));
424 }
425 }
426 }
427 None
428 }
429
430 pub fn solve_locked_candidates(&self) -> Option<Step> {
432 let check = |house_a: &NamedCellSet, house_b: &NamedCellSet| -> Option<Step> {
433 let intersection = house_a & house_b;
434 if intersection.is_empty() {
435 return None;
436 }
437
438 let mut step = Step::new(StepKind::CandidateEliminated, StepRule::LockedCandidates);
439
440 for value in 1..=9 {
441 let possible_cells_in_a =
442 self.get_possible_cells_for_house_and_value(house_a, value);
443 if possible_cells_in_a.is_empty()
444 || !possible_cells_in_a.is_subset_of(&intersection)
445 {
446 continue;
447 }
448 for cell in house_b.iter() {
449 if intersection.has(cell) {
450 continue;
451 }
452 if self.can_fill(cell, value) {
453 step.add(
454 format!(
455 "in {}, {} can only be in {} & {}",
456 house_a.name(),
457 value,
458 house_a.name(),
459 house_b.name(),
460 ),
461 cell,
462 value,
463 );
464 }
465 }
466 if !step.is_empty() {
467 return Some(step);
468 }
469 }
470 None
471 };
472
473 for block in &self.cells_in_blocks {
474 for row in &self.cells_in_rows {
475 let step = check(block, row);
476 if step.is_some() {
477 return step;
478 }
479 let step = check(row, block);
480 if step.is_some() {
481 return step;
482 }
483 }
484 for column in &self.cells_in_columns {
485 let step = check(block, column);
486 if step.is_some() {
487 return step;
488 }
489 let step = check(column, block);
490 if step.is_some() {
491 return step;
492 }
493 }
494 }
495 None
496 }
497
498 pub fn solve_hidden_subset(&self) -> Option<Step> {
500 let mut step = Step::new(StepKind::CandidateEliminated, StepRule::HiddenSubset);
501
502 for house in self.all_constraints.iter() {
503 let mut possible_cells_in_houses = vec![];
504 for value in 1..=9 {
505 let possible_cells_in_house =
506 self.get_possible_cells_for_house_and_value(house, value);
507 if !possible_cells_in_house.is_empty() {
508 possible_cells_in_houses.push((value, possible_cells_in_house));
509 }
510 }
511
512 for size in 2..=4 {
513 let possible_house_cells_for_candidate_in_size: Vec<_> = possible_cells_in_houses
514 .iter()
515 .filter(|(_, cells)| cells.size() <= size)
516 .collect();
517
518 if possible_house_cells_for_candidate_in_size.len() < size {
519 continue;
520 }
521
522 for subset in possible_house_cells_for_candidate_in_size
523 .iter()
524 .combinations(size)
525 {
526 let cell_union =
527 CellSet::union_multiple(subset.iter().map(|(_, cells)| &***cells));
528 let values_in_subset: HashSet<_> =
529 subset.iter().map(|(value, _)| *value).collect();
530
531 if cell_union.size() <= size {
532 for cell in cell_union.iter() {
533 for value in 1..=9 {
534 if !values_in_subset.contains(&value) && self.can_fill(cell, value)
535 {
536 step.add(
537 format!(
538 "in {}, {} only appears in {}",
539 house.name(),
540 values_in_subset.iter().sorted().join(","),
541 self.get_cellset_string(&cell_union),
542 ),
543 cell,
544 value,
545 );
546 }
547 }
548 }
549 if !step.is_empty() {
550 return Some(step);
551 }
552 }
553 }
554 }
555 }
556 None
557 }
558
559 pub fn solve_naked_subset(&self) -> Option<Step> {
561 for house in self.all_constraints.iter() {
562 for size in 2..=4 {
563 let mut step = Step::new(StepKind::CandidateEliminated, StepRule::NakedSubset);
564
565 for subset in house
566 .iter()
567 .filter(|&cell| {
568 !self.candidates(cell).is_empty() && self.candidates(cell).len() <= size
569 })
570 .combinations(size)
571 {
572 let value_union: HashSet<_> = subset
573 .iter()
574 .flat_map(|&cell| self.candidates(cell).iter().copied())
575 .collect();
576 let cells_in_subset = CellSet::from_cells(subset);
577
578 if value_union.len() > size {
579 continue;
580 }
581
582 for cell in house.iter() {
583 if cells_in_subset.has(cell) {
584 continue;
585 }
586 for &value in value_union.iter().sorted() {
587 if self.can_fill(cell, value) {
588 step.add(
589 format!(
590 "in {}, {} only contains {}",
591 house.name(),
592 self.get_cellset_string(&cells_in_subset),
593 value_union.iter().sorted().join(","),
594 ),
595 cell,
596 value,
597 );
598 }
599 }
600 }
601
602 if !step.is_empty() {
603 return Some(step);
604 }
605 }
606 }
607 }
608 None
609 }
610
611 pub fn solve_one_step(&self) -> Option<Step> {
612 let solving_techniques = [
613 SudokuSolver::solve_naked_single,
615 SudokuSolver::solve_hidden_single,
616 SudokuSolver::solve_locked_candidates,
617 SudokuSolver::solve_hidden_subset,
618 SudokuSolver::solve_naked_subset,
619 fish::solve_basic_fish,
620 fish::solve_finned_fish,
621 fish::solve_franken_fish,
622 ];
624 for technique in solving_techniques.iter() {
625 if let Some(step) = technique(self) {
626 return Some(step);
627 }
628 }
629 None
630 }
631}