1use crate::utils::CellSet;
2
3use itertools::Itertools;
4use std::fmt::Debug;
5use wasm_bindgen::prelude::*;
6
7pub type CellIndex = u8;
8pub type CellValue = u8;
9
10#[wasm_bindgen]
11pub struct Sudoku {
12 board: Vec<Option<CellValue>>,
13 candidates: Vec<Vec<CellValue>>,
15 possible_positions: Vec<CellSet>,
17}
18
19#[wasm_bindgen]
20impl Sudoku {
21 pub(crate) fn get_candidates(&self, idx: CellIndex) -> &Vec<CellValue> {
22 &self.candidates[idx as usize]
23 }
24
25 pub(crate) fn add_candidate(&mut self, idx: CellIndex, value: CellValue) {
26 self.candidates[idx as usize].push(value);
27 self.possible_positions[value as usize].add(idx);
28 }
29
30 pub(crate) fn remove_candidate(&mut self, idx: CellIndex, value: CellValue) {
31 self.candidates[idx as usize].retain(|&x| x != value);
32 self.possible_positions[value as usize].delete(idx);
33 }
34
35 pub(crate) fn can_fill(&self, idx: CellIndex, value: CellValue) -> bool {
36 self.possible_positions[value as usize].has(idx)
37 }
38
39 pub(crate) fn fill(&mut self, idx: CellIndex, value: CellValue) {
40 self.board[idx as usize] = Some(value);
41 for &candidate in self.candidates[idx as usize].iter() {
42 self.possible_positions[candidate as usize].delete(idx);
43 }
44 self.candidates[idx as usize].clear();
45 }
46
47 pub(crate) fn get_possible_cells(&self, value: CellValue) -> &CellSet {
48 &self.possible_positions[value as usize]
49 }
50
51 pub(crate) fn get_cell_value(&self, idx: CellIndex) -> Option<CellValue> {
52 self.board[idx as usize]
53 }
54
55 pub(crate) fn get_cell_position(&self, row: usize, col: usize) -> CellIndex {
56 (row * 9 + col) as u8
57 }
58
59 pub(crate) fn get_cell_name(&self, idx: CellIndex) -> String {
60 format!("r{}c{}", idx / 9 + 1, idx % 9 + 1)
61 }
62
63 pub fn from_values(str: &str) -> Self {
64 let mut board = Vec::with_capacity(81);
65 for ch in str.chars() {
66 if ch.is_digit(10) {
67 let digit = ch.to_digit(10).unwrap() as u8;
68 board.push(Some(digit));
69 } else if ch == '.' || ch == '_' {
70 board.push(None);
71 }
72 }
73 let candidates = vec![vec![]; 81];
74 let possible_positions = vec![CellSet::new(); 10];
75 Self {
76 board,
77 candidates,
78 possible_positions,
79 }
80 }
81
82 pub fn from_candidates(str: &str) -> Self {
83 let mut board = vec![None; 81];
84 let mut candidates = vec![vec![]; 81];
85 let mut possible_positions = vec![CellSet::new(); 10];
86 let mut chars = str.chars();
87 let mut idx = 0;
88 let mut waiting_next_digit = false;
89 while let Some(ch) = chars.next() {
90 if ch.is_digit(10) {
91 waiting_next_digit = true;
92 let digit = ch.to_digit(10).unwrap() as u8;
93 candidates[idx].push(digit);
94 possible_positions[digit as usize].add(idx as u8);
95 } else if ch == '.' {
96 for digit in 1..=9 {
97 candidates[idx].push(digit);
98 possible_positions[digit as usize].add(idx as u8);
99 }
100 } else {
101 if waiting_next_digit {
102 assert!(candidates[idx].len() > 0);
103 if candidates[idx].len() == 1 {
104 board[idx] = Some(candidates[idx][0]);
105 }
106 idx += 1;
107 }
108 waiting_next_digit = false;
109 }
110 }
111 Self {
112 board,
113 candidates,
114 possible_positions,
115 }
116 }
117
118 pub fn to_value_string(&self) -> String {
119 let mut s = String::new();
120 for row in 0..9 {
121 for col in 0..9 {
122 let idx = self.get_cell_position(row, col);
123 let value = self.get_cell_value(idx);
124 if let Some(value) = value {
125 s.push_str(&value.to_string());
126 } else {
127 s.push('.');
128 }
129 }
130 }
131 s
132 }
133
134 pub fn to_candidate_string(&self) -> String {
135 let candidates = self
136 .candidates
137 .iter()
138 .enumerate()
139 .map(|(idx, candidates)| {
140 if let Some(value) = self.get_cell_value(idx as u8) {
141 return format!("{}", value);
142 }
143 return candidates.iter().map(|&x| x.to_string()).join("");
144 })
145 .collect_vec();
146
147 let mut s = String::new();
148 let col_widths = (0..9)
149 .map(|col| {
150 (0..9)
151 .map(|row| {
152 let idx = self.get_cell_position(row, col);
153 candidates[idx as usize].len()
154 })
155 .max()
156 .unwrap()
157 + 1
158 })
159 .collect_vec();
160
161 let push_horizontal_line = |s: &mut String| {
162 s.push('+');
163 for col in 0..9 {
164 for _ in 0..col_widths[col] {
165 s.push('-');
166 }
167 if col % 3 == 2 {
168 s.push_str("-+");
169 }
170 }
171 s.push('\n');
172 };
173
174 push_horizontal_line(&mut s);
175 for row in 0..9 {
176 s.push('|');
177 for col in 0..9 {
178 let idx = self.get_cell_position(row, col);
179 for _ in 0..col_widths[col] - candidates[idx as usize].len() {
180 s.push(' ');
181 }
182 s.push_str(&candidates[idx as usize]);
183 if col % 3 == 2 {
184 s.push_str(" |");
185 }
186 }
187 s.push('\n');
188 if row % 3 == 2 {
189 push_horizontal_line(&mut s);
190 }
191 }
192 s
193 }
194}
195
196#[wasm_bindgen(getter_with_clone)]
197#[derive(Clone)]
198pub struct Step {
199 pub kind: StepKind,
200 pub rule: StepRule,
201 pub positions: Vec<StepPosition>,
202}
203
204#[wasm_bindgen]
205impl Step {
206 pub(crate) fn new(kind: StepKind, rule: StepRule) -> Self {
207 Self {
208 kind,
209 rule,
210 positions: vec![],
211 }
212 }
213
214 pub(crate) fn new_value_set(
215 rule: StepRule,
216 reason: String,
217 position: CellIndex,
218 value: CellValue,
219 ) -> Self {
220 let mut step = Self::new(StepKind::ValueSet, rule);
221 step.add(reason, position, value);
222 step
223 }
224
225 pub(crate) fn add(&mut self, reason: String, cell_index: CellIndex, value: CellValue) {
226 self.positions.push(StepPosition {
227 reason,
228 cell_index,
229 value,
230 });
231 }
232
233 pub(crate) fn is_empty(&self) -> bool {
234 self.positions.is_empty()
235 }
236
237 pub fn to_string(&self, sudoku: &Sudoku) -> String {
238 let mut f = String::new();
239 use std::fmt::Write;
240 match self.kind {
241 StepKind::ValueSet => {
242 for position in self.positions.iter() {
243 write!(
244 f,
245 "[{:?}] {} => {}={}\n",
246 self.rule,
247 position.reason,
248 sudoku.get_cell_name(position.cell_index),
249 position.value,
250 )
251 .unwrap();
252 }
253 }
254 StepKind::CandidateEliminated => {
255 for position in self.positions.iter() {
256 write!(
257 f,
258 "[{:?}] {} => {}<>{}\n",
259 self.rule,
260 position.reason,
261 sudoku.get_cell_name(position.cell_index),
262 position.value,
263 )
264 .unwrap();
265 }
266 }
267 }
268 f
269 }
270}
271
272#[wasm_bindgen(getter_with_clone)]
273#[derive(Clone)]
274pub struct StepPosition {
275 pub reason: String,
276 pub cell_index: CellIndex,
277 pub value: CellValue,
278}
279
280impl StepPosition {
281 pub(crate) fn new(reason: String, cell_index: CellIndex, value: CellValue) -> Self {
282 Self {
283 reason,
284 cell_index,
285 value,
286 }
287 }
288}
289
290#[wasm_bindgen]
291#[derive(Debug, Clone)]
292pub enum StepKind {
293 ValueSet,
294 CandidateEliminated,
295}
296
297#[wasm_bindgen]
298#[derive(Debug, Clone, PartialEq)]
299pub enum StepRule {
300 FullHouse,
301 NakedSingle,
302 HiddenSingle,
303 LockedCandidates,
304 HiddenSubset,
305 NakedSubset,
306 BasicFish,
307 FinnedFish,
308 FrankenFish,
309 MutantFish,
310 TwoStringKite,
311 Skyscraper,
312 RectangleElimination,
313}