1use std::ops::Range;
2use std::{char, fmt::Display, ops::{Index, IndexMut}};
3
4use solver::SudokuSolver;
5use unchecked_array::UncheckedArray;
6
7pub mod solver;
8use solver::BacktrackingSudokuSolver;
9
10#[cfg(feature = "builder")]
11mod builder;
12#[cfg(feature = "builder")]
13pub use builder::build_sudoku;
14
15fn box_iter(n: usize) -> Range<usize> {
16 let base = n - n % 3;
17 base..base+3
18}
19
20#[derive(Clone)]
21pub struct Sudoku(UncheckedArray<UncheckedArray<u8,9>,9>);
22
23impl Sudoku {
24 const UNASSIGNED: u8 = 0;
25
26 pub fn empty() -> Self {
27 [Self::UNASSIGNED; 81].into()
28 }
29
30 pub fn new(a: impl Into<Self>) -> Self {
31 a.into()
32 }
33
34 pub fn from_str_pattern(l: &str) -> Result<Self,String> {
35 let mut chars = l.chars();
36 let mut arr = [0_u8; 9 * 9];
37 arr.iter_mut().try_for_each(|e| {
38 let c = chars.next().unwrap_or('.');
39 *e = match c {
40 '0'..='9' => c as u8 - b'0',
41 '.' => Self::UNASSIGNED,
42 _ => return Err(format!("Unknown character {c}")),
43 };
44 Ok(())
45 })?;
46 Ok(arr.into())
47 }
48
49 fn find_unsassigned(&self) -> Option<(usize,usize)> {
50 for row in 0..9 {
51 for col in 0..9 {
52 if self.0[row][col] == Self::UNASSIGNED {
53 return Some((row,col));
54 }
55 }
56 }
57 None
58 }
59
60 pub fn is_solved(&mut self) -> bool {
61 (0..9).all(|i|
62 (0..9).all(|j|
63 self.is_unique(i, j)))
64 }
65
66 fn used_in_row(&self, row: usize, n: u8) -> bool {
67 (0..9).any(|col| self[row][col] == n)
68 }
69
70 fn used_in_col(&self, col: usize, n: u8) -> bool {
71 (0..9).any(|row| self[row][col] == n)
72 }
73
74 fn used_in_box(&self, row: usize, col: usize, n: u8) -> bool {
75 box_iter(row).any(|row|
76 box_iter(col).any(|col|
77 self[row][col] == n))
78 }
79
80 fn is_unique(&mut self, row: usize, col: usize) -> bool {
81 let n = self[row][col];
82 self[row][col] = Self::UNASSIGNED;
83 let res =
84 n != Self::UNASSIGNED
85 && !self.used_in_row(row, n)
86 && !self.used_in_col(col, n)
87 && !self.used_in_box(row, col, n);
88 self[row][col] = n;
89 res
90 }
91
92 fn is_safe(&self, row: usize, col: usize, n: u8) -> bool {
93 !self.used_in_row(row, n)
94 && !self.used_in_col(col, n)
95 && !self.used_in_box(row, col, n)
96 && self.0[row][col] == Self::UNASSIGNED
97 }
98
99 fn check_well_formatted(&mut self) -> Option<()> {
100 for row in 0..9 {
101 for col in 0..9 {
102 if self[row][col] != Self::UNASSIGNED && !self.is_unique(row, col) {
103 return None;
104 }
105 }
106 }
107 Some(())
108 }
109
110 pub fn solve(&self) -> Option<Sudoku> {
111 self.solve_with(BacktrackingSudokuSolver)
112 }
113
114 pub fn solve_with(&self, mut solver: impl SudokuSolver) -> Option<Sudoku> {
115 let mut s = self.clone();
116 s.check_well_formatted()?;
117 solver.solve(&mut s);
118 Some(s)
119 }
120
121 #[cfg(feature = "random")]
122 pub fn remove_digits(&mut self, mut n: usize) {
123 use rand::Rng;
124
125 let mut rng = rand::thread_rng();
126 while n > 0 {
127 let id = rng.gen_range(0..81);
128 let row = id / 9;
129 let col = id % 9;
130
131 let cell = &mut self[row][col];
132 if *cell != Sudoku::UNASSIGNED {
133 *cell = Sudoku::UNASSIGNED;
134 n -= 1;
135 }
136 }
137 }
138}
139
140impl Index<usize> for Sudoku {
141 type Output = [u8; 9];
142
143 fn index(&self, index: usize) -> &Self::Output {
144 &self.0[index]
145 }
146}
147
148impl IndexMut<usize> for Sudoku {
149 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
150 &mut self.0[index]
151 }
152}
153
154impl From<[[u8; 9]; 9]> for Sudoku {
155 fn from(value: [[u8; 9]; 9]) -> Self {
156 let mut dst: UncheckedArray<UncheckedArray<u8,9>,9> = UncheckedArray::default();
157 for (i,e) in (0..9).zip(value.into_iter()) {
158 dst[i] = UncheckedArray::from(e);
159 }
160 Sudoku(dst)
161 }
162}
163
164impl From<[u8; 81]> for Sudoku {
165 fn from(value: [u8; 81]) -> Self {
166 let mut src = 0;
167 let mut dst = [[0_u8; 9]; 9];
168 dst.iter_mut().for_each(|it| {
169 it.iter_mut().for_each(|n| {
170 *n = value[src];
171 src += 1;
172 });
173 });
174 Sudoku::new(dst)
175 }
176}
177
178impl Display for Sudoku {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 for row in 0..9 {
181 for col in 0..9 {
182 if col == 0 {
183 write!(f, "|")?;
184 }
185 let n = self.0[row][col];
186 let n = if n != Self::UNASSIGNED {
187 (b'0' + n) as char
188 } else {
189 '?'
190 };
191 write!(f, "{n}|")?;
192 }
193 if row < 8 {
194 writeln!(f)?;
195 }
196 }
197 Ok(())
198 }
199}
200
201impl PartialEq for Sudoku {
202 fn eq(&self, other: &Self) -> bool {
203 self.0 == other.0
204 }
205}