sudoku_solver/
sudoku.rs

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    // cell position -> possible values at that cell
14    candidates: Vec<Vec<CellValue>>,
15    // value -> possible cell positions for that value
16    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}