1mod board;
11mod candidates;
12mod masks;
13mod solution;
14mod techniques;
15
16pub use board::Board;
17pub use candidates::Candidates;
18pub use masks::Masks;
19pub use solution::{Solution, SolvePath, SolveStep};
20pub use techniques::flags::TechniqueFlags;
21
22use crate::error::RustokuError;
23use rand::prelude::SliceRandom;
24use rand::rng;
25use techniques::TechniquePropagator;
26
27#[derive(Debug, Copy, Clone)]
61pub struct Rustoku {
62 pub board: Board,
64 pub masks: Masks,
66 pub candidates: Candidates,
68 pub techniques: TechniqueFlags,
70}
71
72impl Rustoku {
73 pub fn new(initial_board: Board) -> Result<Self, RustokuError> {
75 let board = initial_board; let mut masks = Masks::new();
77 let mut candidates = Candidates::new();
78
79 for r in 0..9 {
81 for c in 0..9 {
82 let num = board.get(r, c);
83 if num != 0 {
84 if !masks.is_safe(r, c, num) {
85 return Err(RustokuError::DuplicateValues);
86 }
87 masks.add_number(r, c, num);
88 }
89 }
90 }
91
92 for r in 0..9 {
94 for c in 0..9 {
95 if board.is_empty(r, c) {
96 candidates.set(r, c, masks.compute_candidates_mask_for_cell(r, c));
97 }
98 }
99 }
100
101 Ok(Self {
102 board,
103 masks,
104 candidates,
105 techniques: TechniqueFlags::EASY, })
107 }
108
109 pub fn builder() -> RustokuBuilder {
111 RustokuBuilder::new()
112 }
113
114 pub fn new_from_str(s: &str) -> Result<Self, RustokuError> {
116 let board = Board::try_from(s)?;
117 Self::new(board)
118 }
119
120 pub fn with_techniques(mut self, techniques: TechniqueFlags) -> Self {
122 self.techniques = techniques;
123 self
124 }
125
126 fn candidates_from_mask(mask: u16) -> Vec<u8> {
128 let mut nums = Vec::with_capacity(mask.count_ones() as usize);
129 for v in 1..=9u8 {
130 if mask & (1 << (v - 1)) != 0 {
131 nums.push(v);
132 }
133 }
134 nums
135 }
136
137 fn find_next_empty_cell(&self) -> Option<(usize, usize)> {
139 let mut min = (10, None); for (r, c) in self.board.iter_empty_cells() {
141 let count = self.candidates.get(r, c).count_ones() as u8;
142 if count < min.0 {
143 min = (count, Some((r, c)));
144 if count == 1 {
145 return min.1;
146 }
147 }
148 }
149 min.1
150 }
151
152 fn place_number(&mut self, r: usize, c: usize, num: u8) {
154 self.board.set(r, c, num);
155 self.masks.add_number(r, c, num);
156 self.candidates
157 .update_affected_cells_for(r, c, &self.masks, &self.board, Some(num));
158 }
159
160 fn remove_number(&mut self, r: usize, c: usize, num: u8) {
162 self.board.set(r, c, 0); self.masks.remove_number(r, c, num);
164 self.candidates
165 .update_affected_cells(r, c, &self.masks, &self.board);
166 }
168
169 fn solve_until_recursive(
171 &mut self,
172 solutions: &mut Vec<Solution>,
173 path: &mut SolvePath,
174 bound: usize,
175 ) -> usize {
176 let Some((r, c)) = self.find_next_empty_cell() else {
178 solutions.push(Solution {
179 board: self.board,
180 solve_path: path.clone(),
181 });
182 return 1;
183 };
184
185 let mut count = 0;
186 let mask = self.candidates.get(r, c);
188 let mut nums = Self::candidates_from_mask(mask);
189 nums.shuffle(&mut rng());
190
191 for &num in &nums {
192 if !self.masks.is_safe(r, c, num) {
193 continue;
194 }
195
196 self.place_number(r, c, num);
197 let step_number = path.steps.len() as u32;
198 path.steps.push(SolveStep::Placement {
199 row: r,
200 col: c,
201 value: num,
202 flags: TechniqueFlags::empty(),
203 step_number,
204 candidates_eliminated: 0,
205 related_cell_count: 0,
206 difficulty_point: 0,
207 });
208
209 count += self.solve_until_recursive(solutions, path, bound);
210 path.steps.pop();
211 self.remove_number(r, c, num);
212
213 if bound > 0 && solutions.len() >= bound {
215 return count;
216 }
217 }
218
219 count
220 }
221
222 fn techniques_make_valid_changes(&mut self, path: &mut SolvePath) -> bool {
224 let mut propagator = TechniquePropagator::new(
225 &mut self.board,
226 &mut self.masks,
227 &mut self.candidates,
228 self.techniques,
229 );
230 propagator.propagate_constraints(path, 0)
231 }
232
233 pub fn solve_until(&mut self, bound: usize) -> Vec<Solution> {
235 let mut solutions = Vec::new();
236 let mut path = SolvePath::default();
237
238 if !self.techniques_make_valid_changes(&mut path) {
239 return solutions;
240 }
241
242 self.solve_until_recursive(&mut solutions, &mut path, bound);
243 solutions
244 }
245
246 pub fn solve_any(&mut self) -> Option<Solution> {
248 self.solve_until(1).into_iter().next()
249 }
250
251 pub fn solve_all(&mut self) -> Vec<Solution> {
253 use rayon::prelude::*;
254
255 let mut path = SolvePath::default();
257 if !self.techniques_make_valid_changes(&mut path) {
258 return Vec::new();
259 }
260
261 if let Some((r, c)) = self.find_next_empty_cell() {
263 let mask = self.candidates.get(r, c);
264 let nums = Self::candidates_from_mask(mask);
265
266 let initial_path = path.clone();
267
268 let chunks: Vec<Vec<Solution>> = nums
270 .par_iter()
271 .map(|&num| {
272 let mut cloned = *self; let mut local_solutions: Vec<Solution> = Vec::new();
274 let mut local_path = initial_path.clone();
275
276 cloned.place_number(r, c, num);
278 let step_number = local_path.steps.len() as u32;
279 local_path.steps.push(SolveStep::Placement {
280 row: r,
281 col: c,
282 value: num,
283 flags: TechniqueFlags::empty(),
284 step_number,
285 candidates_eliminated: 0,
286 related_cell_count: 0,
287 difficulty_point: 0,
288 });
289
290 cloned.solve_until_recursive(&mut local_solutions, &mut local_path, 0);
292 local_solutions
293 })
294 .collect();
295
296 let mut solutions = Vec::new();
298 for mut s in chunks {
299 solutions.append(&mut s);
300 }
301 solutions
302 } else {
303 vec![Solution {
305 board: self.board,
306 solve_path: path,
307 }]
308 }
309 }
310
311 pub fn is_solved(&self) -> bool {
313 self.board.cells.iter().flatten().all(|&val| val != 0) && Rustoku::new(self.board).is_ok()
314 }
315}
316
317pub struct RustokuBuilder {
319 board: Option<Board>,
320 techniques: TechniqueFlags,
321 max_solutions: Option<usize>,
322}
323
324impl RustokuBuilder {
325 pub fn new() -> Self {
327 RustokuBuilder {
328 board: None,
329 techniques: TechniqueFlags::EASY,
330 max_solutions: None,
331 }
332 }
333}
334
335impl Default for RustokuBuilder {
336 fn default() -> Self {
337 Self::new()
338 }
339}
340
341impl RustokuBuilder {
342 pub fn board(mut self, board: Board) -> Self {
344 self.board = Some(board);
345 self
346 }
347
348 pub fn board_from_str(mut self, s: &str) -> Result<Self, RustokuError> {
350 let board = Board::try_from(s)?;
351 self.board = Some(board);
352 Ok(self)
353 }
354
355 pub fn techniques(mut self, techniques: TechniqueFlags) -> Self {
357 self.techniques = techniques;
358 self
359 }
360
361 pub fn max_solutions(mut self, max: usize) -> Self {
363 self.max_solutions = Some(max);
364 self
365 }
366
367 pub fn build(self) -> Result<Rustoku, RustokuError> {
369 let board = self.board.unwrap_or_default();
370 let mut r = Rustoku::new(board)?;
371 r.techniques = self.techniques;
372 Ok(r)
375 }
376}
377
378#[derive(Debug)]
381pub struct Solutions {
382 solver: Rustoku,
383 path: SolvePath,
384 stack: Vec<Frame>,
385 finished: bool,
386}
387
388#[derive(Debug)]
389struct Frame {
390 r: usize,
391 c: usize,
392 nums: Vec<u8>,
393 idx: usize,
394 placed: Option<u8>,
395}
396
397impl Solutions {
398 pub fn from_solver(mut solver: Rustoku) -> Self {
401 let mut path = SolvePath::default();
402 let mut finished = false;
403
404 if !solver.techniques_make_valid_changes(&mut path) {
405 finished = true;
406 }
407
408 let mut stack = Vec::new();
409 if !finished {
410 if let Some((r, c)) = solver.find_next_empty_cell() {
411 let mask = solver.candidates.get(r, c);
412 let mut nums = Rustoku::candidates_from_mask(mask);
413 nums.shuffle(&mut rng());
414 stack.push(Frame {
415 r,
416 c,
417 nums,
418 idx: 0,
419 placed: None,
420 });
421 } else {
422 }
424 }
425
426 Solutions {
427 solver,
428 path,
429 stack,
430 finished,
431 }
432 }
433}
434
435impl Iterator for Solutions {
436 type Item = Solution;
437
438 fn next(&mut self) -> Option<Self::Item> {
439 if self.finished {
440 return None;
441 }
442
443 loop {
444 if self.stack.is_empty() {
446 if let Some((r, c)) = self.solver.find_next_empty_cell() {
447 let mask = self.solver.candidates.get(r, c);
448 let mut nums = Rustoku::candidates_from_mask(mask);
449 nums.shuffle(&mut rng());
450 self.stack.push(Frame {
451 r,
452 c,
453 nums,
454 idx: 0,
455 placed: None,
456 });
457 continue;
458 } else {
459 let sol = Solution {
461 board: self.solver.board,
462 solve_path: self.path.clone(),
463 };
464 self.finished = true;
465 return Some(sol);
466 }
467 }
468
469 let last_idx = self.stack.len() - 1;
470 let frame = &mut self.stack[last_idx];
471
472 if frame.idx >= frame.nums.len() {
474 if let Some(num) = frame.placed {
475 self.solver.remove_number(frame.r, frame.c, num);
477 self.path.steps.pop();
478 frame.placed = None;
479 } else {
480 self.stack.pop();
482 }
483 continue;
484 }
485
486 let num = frame.nums[frame.idx];
487 frame.idx += 1;
488
489 if self.solver.masks.is_safe(frame.r, frame.c, num) {
490 self.solver.place_number(frame.r, frame.c, num);
492 let step_number = self.path.steps.len() as u32;
493 self.path.steps.push(SolveStep::Placement {
494 row: frame.r,
495 col: frame.c,
496 value: num,
497 flags: TechniqueFlags::empty(),
498 step_number,
499 candidates_eliminated: 0,
500 related_cell_count: 0,
501 difficulty_point: 0,
502 });
503 frame.placed = Some(num);
504
505 if let Some((nr, nc)) = self.solver.find_next_empty_cell() {
507 let mask = self.solver.candidates.get(nr, nc);
508 let mut nums2 = Rustoku::candidates_from_mask(mask);
509 nums2.shuffle(&mut rng());
510 self.stack.push(Frame {
511 r: nr,
512 c: nc,
513 nums: nums2,
514 idx: 0,
515 placed: None,
516 });
517 continue;
518 } else {
519 let solution = Solution {
521 board: self.solver.board,
522 solve_path: self.path.clone(),
523 };
524 if let Some(pnum) = frame.placed {
526 self.solver.remove_number(frame.r, frame.c, pnum);
527 self.path.steps.pop();
528 frame.placed = None;
529 }
530 return Some(solution);
531 }
532 }
533 }
535 }
536}
537
538pub fn generate_board(num_clues: usize) -> Result<Board, RustokuError> {
555 if !(17..=81).contains(&num_clues) {
556 return Err(RustokuError::InvalidClueCount);
557 }
558
559 let mut rustoku = Rustoku::new(Board::default())?;
561 let solution = rustoku.solve_any().ok_or(RustokuError::DuplicateValues)?;
562 let mut board = solution.board;
563
564 let mut cells: Vec<(usize, usize)> = board.iter_cells().collect();
566 cells.shuffle(&mut rng());
567
568 let mut clues = 81;
569
570 for &(r, c) in &cells {
572 if clues <= num_clues {
573 break;
574 }
575
576 let original = board.cells[r][c];
577 board.cells[r][c] = 0;
578
579 if Rustoku::new(board)?.solve_until(2).len() != 1 {
580 board.cells[r][c] = original; } else {
582 clues -= 1;
583 }
584 }
585
586 if Rustoku::new(board)?.solve_until(2).len() != 1 {
588 return Err(RustokuError::GenerateFailure);
590 }
591
592 Ok(board)
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use crate::core::board::Board;
599 use crate::error::RustokuError;
600 use crate::format::format_line;
601
602 const UNIQUE_PUZZLE: &str =
603 "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
604 const UNIQUE_SOLUTION: &str =
605 "534678912672195348198342567859761423426853791713924856961537284287419635345286179";
606 const TWO_PUZZLE: &str =
607 "295743861431865900876192543387459216612387495549216738763504189928671354154938600";
608 const SIX_PUZZLE: &str =
609 "295743001431865900876192543387459216612387495549216738763500000000000000000000000";
610
611 #[test]
612 fn test_builder_and_iterator() {
613 let board = Board::try_from(UNIQUE_PUZZLE).expect("valid puzzle");
614 let solver = Rustoku::builder()
615 .board(board)
616 .techniques(TechniqueFlags::all())
617 .build()
618 .expect("builder build");
619
620 let mut sols = Solutions::from_solver(solver);
622 let first = sols.next();
623 assert!(first.is_some());
624 assert!(sols.next().is_none());
626 }
627
628 #[test]
629 fn test_try_from_with_duplicate_initial_values() {
630 let s = "530070000600195000098000060800060003400803001700020006060000280000419005500080079";
631 let board = Board::try_from(s).expect("Board parsing failed before duplicate check");
632 let rustoku = Rustoku::new(board);
633 assert!(matches!(rustoku, Err(RustokuError::DuplicateValues)));
634 }
635
636 #[test]
637 fn test_solve_any_with_solvable_sudoku() {
638 let s = UNIQUE_PUZZLE;
639 let mut rustoku =
640 Rustoku::new_from_str(s).expect("Rustoku creation failed from puzzle string");
641 let solution = rustoku.solve_any().expect("Solving solvable puzzle failed");
642
643 assert_eq!(
644 UNIQUE_SOLUTION,
645 format_line(&solution.board),
646 "Solution does not match the expected result"
647 );
648 }
649
650 #[test]
651 fn test_solve_any_with_unsolvable_sudoku() {
652 let s = "078002609030008020002000083000000040043090000007300090200001036001840902050003007";
653 let mut rustoku = Rustoku::new_from_str(s).expect("Rustoku creation failed");
654 let solution = rustoku.solve_any();
655 assert!(
656 solution.is_none(),
657 "Expected no solution for this unsolvable puzzle"
658 );
659 }
660
661 #[test]
662 fn test_solve_until_with_bound() {
663 let s = UNIQUE_PUZZLE;
664 let mut rustoku =
665 Rustoku::new_from_str(s).expect("Rustoku creation failed from puzzle string");
666
667 let solutions = rustoku.solve_until(1);
668 assert_eq!(
669 1,
670 solutions.len(),
671 "Expected exactly one solution with bound = 1"
672 );
673
674 let all_solutions = rustoku.solve_until(0);
675 assert_eq!(
676 1,
677 all_solutions.len(),
678 "Expected exactly one solution for this board with bound = 0"
679 );
680
681 assert_eq!(
682 solutions[0].board, all_solutions[0].board,
683 "Solution with bound = 1 does not match the solution with bound = 0"
684 );
685 }
686
687 #[test]
688 fn test_solve_all_with_unique_puzzle() {
689 let s = UNIQUE_PUZZLE;
690 let mut rustoku =
691 Rustoku::new_from_str(s).expect("Rustoku creation failed from unique puzzle string");
692 let solutions = rustoku.solve_all();
693 assert_eq!(
694 1,
695 solutions.len(),
696 "Expected a unique solution for the board"
697 );
698 }
699
700 #[test]
701 fn test_solve_all_with_two_puzzle() {
702 let s = TWO_PUZZLE;
703 let mut rustoku =
704 Rustoku::new_from_str(s).expect("Rustoku creation failed from two puzzle string");
705 let solutions = rustoku.solve_all();
706 assert_eq!(
707 2,
708 solutions.len(),
709 "Expected two solutions for the given board"
710 );
711 }
712
713 #[test]
714 fn test_solve_all_with_six_puzzle() {
715 let s = SIX_PUZZLE;
716 let mut rustoku =
717 Rustoku::new_from_str(s).expect("Rustoku creation failed from six puzzle string");
718 let solutions = rustoku.solve_all();
719 assert_eq!(
720 6,
721 solutions.len(),
722 "Expected one solution for the six puzzle"
723 );
724 }
725
726 #[test]
727 fn test_solve_any_with_all_techniques() {
728 let s = UNIQUE_PUZZLE;
729 let rustoku = Rustoku::new_from_str(s).expect("Rustoku creation failed for technique test");
730 let solution = rustoku
731 .with_techniques(TechniqueFlags::all())
732 .solve_any()
733 .expect("Solving with all techniques failed");
734
735 assert_eq!(
736 UNIQUE_SOLUTION,
737 format_line(&solution.board),
738 "Solution does not match the expected result with all techniques"
739 );
740 }
741
742 #[test]
743 fn test_solve_all_with_all_techniques() {
744 let s = TWO_PUZZLE;
745 let rustoku = Rustoku::new_from_str(s)
746 .expect("Rustoku creation failed for multi-solution technique test");
747 let solutions = rustoku.with_techniques(TechniqueFlags::all()).solve_all();
748
749 assert_eq!(
750 2,
751 solutions.len(),
752 "Expected two solutions for the given board with all techniques"
753 );
754 }
755
756 #[test]
757 fn test_generate_with_enough_clues() {
758 (20..=80).step_by(20).for_each(|num_clues| {
759 let board = generate_board(num_clues)
760 .unwrap_or_else(|_| panic!("Board generation failed for {num_clues} clues"));
761 let mut rustoku =
762 Rustoku::new(board).expect("Rustoku creation failed from generated board");
763 let clues_count = board
764 .cells
765 .iter()
766 .flatten()
767 .filter(|&&cell| cell != 0)
768 .count();
769 assert!(
770 clues_count >= num_clues,
771 "Expected at least {num_clues} clues, but found {clues_count} clues"
772 );
773
774 let solutions = rustoku.solve_all();
775 assert_eq!(
776 1,
777 solutions.len(),
778 "Generated puzzle with {num_clues} clues should have a unique solution"
779 );
780 })
781 }
782
783 #[test]
784 fn test_generate_with_too_few_clues() {
785 let num_clues = 16;
786 let result = generate_board(num_clues);
787 assert!(matches!(result, Err(RustokuError::InvalidClueCount)));
788 }
789
790 #[test]
791 fn test_generate_with_too_many_clues() {
792 let num_clues = 82;
793 let result = generate_board(num_clues);
794 assert!(matches!(result, Err(RustokuError::InvalidClueCount)));
795 }
796
797 #[test]
798 fn test_is_solved_with_valid_solution() {
799 let s = UNIQUE_SOLUTION;
800 let rustoku = Rustoku::new_from_str(s).expect("Rustoku creation failed for solved check");
801 assert!(rustoku.is_solved(), "The Sudoku puzzle should be solved");
802 }
803
804 #[test]
805 fn test_is_solved_with_unsolved_board() {
806 let s = UNIQUE_PUZZLE;
807 let rustoku = Rustoku::new_from_str(s).expect("Rustoku creation failed for unsolved check");
808 assert!(!rustoku.is_solved(), "The board should not be valid");
809 }
810}