1use std::fmt::{self, Display, Debug};
2use itertools::Itertools;
3use serde::{Serialize, Deserialize};
4
5#[derive(Serialize, Deserialize, Debug, Clone)]
6pub struct SudokuConstraints {
7 pub grid_size: usize,
8 pub fixed_numbers: Vec<FixedNumber>,
9 pub regions: Vec<Region>,
10 pub extra_regions: Vec<Region>,
11 pub killer_cages: Vec<KillerCage>,
12 pub thermos: Vec<Thermo>,
13 pub arrows: Vec<Arrow>,
14 pub primary_diagonal: bool,
15 pub secondary_diagonal: bool,
16 pub anti_knight: bool,
17 pub anti_king: bool,
18 pub kropki_dots: Vec<KropkiDot>,
19 pub kropki_negative: bool,
20 pub odd_cells: Vec<CellPosition>,
21 pub even_cells: Vec<CellPosition>,
22 pub top_bottom: bool,
23 pub renbans: Vec<Renban>,
24 pub palindromes: Vec<Palindrome>,
25}
26
27#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
28pub struct FixedNumber {
29 pub position: CellPosition,
30 pub value: u32,
31}
32
33#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct CellPosition {
35 pub row: usize,
36 pub col: usize,
37}
38
39impl CellPosition {
40 pub fn new(row: usize, col: usize) -> CellPosition {
41 CellPosition {
42 row,
43 col,
44 }
45 }
46
47 pub fn to_string(&self) -> String {
48 format!("R{}C{}", self.row + 1, self.col + 1)
49 }
50}
51
52#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
53pub struct CellDirection {
54 pub row: isize,
55 pub col: isize,
56}
57
58pub type Region = Vec<CellPosition>;
59
60pub type Grid = Vec<Vec<u32>>;
61
62#[derive(Serialize, Deserialize, Debug, Clone)]
63pub struct KillerCage {
64 pub sum: Option<u32>,
65 pub region: Region,
66}
67
68#[derive(Serialize, Deserialize, Debug, Clone)]
69pub struct KropkiDot {
70 pub dot_type: KropkiDotType,
71 pub cell_1: CellPosition,
72 pub cell_2: CellPosition,
73}
74
75#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
76pub enum KropkiDotType {
77 Consecutive,
78 Double,
79 Negative,
80}
81
82#[derive(Serialize, Deserialize, Debug)]
83pub struct SudokuGrid {
84 pub values: Grid,
85}
86
87#[derive(Serialize, Deserialize, Debug)]
88pub struct SudokulogicalSolveResult {
89 pub solution_type: SolutionType,
90 pub solution: Option<Grid>,
91 pub steps: Vec<SolutionStep>,
92 pub invalid_state_reason: Option<InvalidStateReason>,
93}
94
95#[derive(Serialize, Deserialize, Debug, PartialEq)]
96pub enum SolutionType {
97 Full,
98 Partial,
99 None,
100}
101
102#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
103pub struct InvalidStateReason {
104 pub state_type: InvalidStateType,
105 pub area: Area,
106 pub values: Vec<u32>,
107}
108
109#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
110pub enum InvalidStateType {
111 CellNoCandidates,
112 CellEmpty,
113 CellInvalidValue,
114 AreaValueConflict,
115 AreaCandidates,
116 AreaConstraint,
117}
118
119#[derive(Serialize, Deserialize, Debug)]
120pub struct SudokuBruteSolveResult {
121 pub solution_count: u32,
122 pub solution: Option<Grid>,
123}
124
125#[derive(Serialize, Deserialize, Debug, Clone)]
126pub struct SolutionStep {
127 pub rule: Rule,
128 pub cells: Vec<CellPosition>,
131 pub values: Vec<u32>,
133 pub areas: Vec<Area>,
135 pub affected_cells: Vec<CellPosition>,
138 pub candidates: Option<Vec<Vec<Vec<u32>>>>,
140 pub invalid_state_reason: Option<InvalidStateReason>,
142}
143
144#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash)]
145pub enum Rule {
146 NakedSingle, HiddenSingle, Thermo,
150 Kropki,
151 Candidates,
152 AdvancedCandidates,
153 ThermoCandidates,
154 KillerCandidates,
155 ArrowCandidates,
156 RenbanCandidates,
157 PalindromeValues,
158 PalindromeCandidates,
159 ArrowAdvancedCandidates,
161 Killer45,
162 KropkiChainCandidates,
163 KropkiAdvancedCandidates,
164 TopBottomCandidates,
165 LockedCandidatesPairs, NakedPairs, HiddenPairs,
168 CommonPeerElimination, CommonPeerEliminationKropki,
171 CommonPeerEliminationArrow,
172 LockedCandidatesTriples,
173 NakedTriples, HiddenTriples,
175 XWing,
176 XYWing,
177 Swordfish, TurbotFish,
179 EmptyRectangles,
180 AdhocNakedSet,
181 PhistomefelRing,
182 NishioForcingChains,
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
186pub enum Area {
187 Grid,
188 Adhoc(Vec<CellPosition>),
189 Cell(usize, usize),
190 Row(usize),
191 Column(usize),
192 Region(usize),
193 Thermo(usize),
194 Arrow(usize),
195 KillerCage(usize),
196 KropkiDot(usize),
197 PrimaryDiagonal,
198 SecondaryDiagonal,
199 Renban(usize),
200 Palindrome(usize),
201}
202
203pub type Thermo = Vec<CellPosition>;
204
205#[derive(Serialize, Deserialize, Debug, Clone)]
206pub struct Arrow {
207 pub circle_cells: Vec<CellPosition>,
208 pub arrow_cells: Vec<CellPosition>,
209}
210
211pub type Renban = Vec<CellPosition>;
212
213pub type Palindrome = Vec<CellPosition>;
214
215impl Display for Rule {
216 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217 Debug::fmt(self, f)
218 }
219}
220
221impl Arrow {
222 pub fn all_cells(&self) -> Vec<CellPosition> {
223 [
224 self.arrow_cells.to_vec(),
225 self.circle_cells.iter().sorted().copied().collect()
226 ].concat()
227 }
228}
229
230impl SudokuConstraints {
231 pub fn new(grid_size: usize, fixed_numbers: Vec<FixedNumber>) -> SudokuConstraints {
232 SudokuConstraints {
233 grid_size,
234 fixed_numbers,
235 regions: SudokuConstraints::default_regions(grid_size),
236 extra_regions: vec![],
237 killer_cages: vec![],
238 thermos: vec![],
239 arrows: vec![],
240 primary_diagonal: false,
241 secondary_diagonal: false,
242 anti_knight: false,
243 anti_king: false,
244 kropki_dots: vec![],
245 kropki_negative: false,
246 odd_cells: vec![],
247 even_cells: vec![],
248 top_bottom: false,
249 renbans: vec![],
250 palindromes: vec![],
251 }
252 }
253
254 #[allow(dead_code)]
255 pub fn default_regions(grid_size: usize) -> Vec<Region> {
256 let (region_height, region_width) = SudokuConstraints::compute_region_sizes(grid_size);
257
258 let mut regions: Vec<Region> = vec![];
259 for region_row_index in 0..(grid_size / region_height) {
260 for region_col_index in 0..(grid_size / region_width) {
261 let mut region: Region = vec![];
262 for row_index in 0..region_height {
263 for col_index in 0..region_width {
264 let cell = CellPosition {
265 row: region_row_index * region_height + row_index,
266 col: region_col_index * region_width + col_index,
267 };
268 region.push(cell);
269 }
270 }
271 regions.push(region);
272 }
273 }
274
275 regions
276 }
277
278 pub fn compute_region_sizes(grid_size: usize) -> (usize, usize) {
279 if grid_size == 4 {
280 (2, 2)
281 } else if grid_size == 6 {
282 (2, 3)
283 } else {
284 (3, 3)
285 }
286 }
287
288 #[cfg(test)]
289 pub fn with_top_bottom(mut self) -> Self {
290 self.top_bottom = true;
291 self
292 }
293
294 #[cfg(test)]
295 pub fn with_anti_king(mut self) -> Self {
296 self.anti_king = true;
297 self
298 }
299
300 #[cfg(test)]
301 pub fn with_anti_knight(mut self) -> Self {
302 self.anti_knight = true;
303 self
304 }
305
306 #[cfg(test)]
307 pub fn with_diagonals(mut self) -> Self {
308 self.primary_diagonal = true;
309 self.secondary_diagonal = true;
310 self
311 }
312
313 pub fn to_lz_string(&self) -> String {
314 let json = serde_json::to_string(&self).unwrap();
315 let lz_string = lz_str::compress_to_base64(&json);
316 lz_string
317 }
318
319 pub fn to_grid_string(&self) -> String {
320 SudokuGrid::from_fixed_numbers(self.grid_size, &self.fixed_numbers).to_string(Some("\n"))
321 }
322
323 pub fn to_import_string(&self) -> String {
324 self.to_grid_string().replace("\n", "")
325 }
326}
327
328impl FixedNumber {
329 pub fn new(row: usize, col: usize, value: u32) -> FixedNumber {
330 FixedNumber {
331 position: CellPosition {
332 row,
333 col,
334 },
335 value,
336 }
337 }
338}
339
340impl KropkiDot {
341 #[cfg(test)]
342 pub fn consecutive(cell_1: CellPosition, cell_2: CellPosition) -> KropkiDot {
343 KropkiDot {
344 dot_type: KropkiDotType::Consecutive,
345 cell_1,
346 cell_2,
347 }
348 }
349
350 #[cfg(test)]
351 pub fn double(cell_1: CellPosition, cell_2: CellPosition) -> KropkiDot {
352 KropkiDot {
353 dot_type: KropkiDotType::Double,
354 cell_1,
355 cell_2,
356 }
357 }
358
359 pub fn other_cell(&self, cell: &CellPosition) -> CellPosition {
360 if self.cell_1.eq(cell) {
361 self.cell_2
362 } else {
363 self.cell_1
364 }
365 }
366
367 pub fn check_values(&self, value1: u32, value2: u32) -> bool {
368 value1 == 0 ||
369 value2 == 0 ||
370 (
371 self.dot_type != KropkiDotType::Negative && (
372 self.apply_operation(value1) == value2 ||
373 self.apply_operation(value2) == value1
374 )
375 ) ||
376 (
377 self.dot_type == KropkiDotType::Negative &&
378 value1 + 1 != value2 && value2 + 1 != value1 &&
379 value1 * 2 != value2 && value2 * 2 != value1
380 )
381 }
382
383 fn apply_operation(&self, value: u32) -> u32 {
384 match self.dot_type {
385 KropkiDotType::Consecutive => value + 1,
386 KropkiDotType::Double => value * 2,
387 KropkiDotType::Negative => unimplemented!(),
388 }
389 }
390}
391
392impl SudokulogicalSolveResult {
393 pub fn no_solution(invalid_state_reason: InvalidStateReason) -> SudokulogicalSolveResult {
394 SudokulogicalSolveResult {
395 solution_type: SolutionType::None,
396 solution: None,
397 steps: vec![],
398 invalid_state_reason: Some(invalid_state_reason),
399 }
400 }
401}
402
403impl SolutionStep {
404 pub fn new(
405 rule: Rule, cells: Vec<CellPosition>, values: Vec<u32>,
406 areas: Vec<Area>, affected_cells: Vec<CellPosition>,
407 ) -> SolutionStep {
408 SolutionStep {
409 rule,
410 cells,
411 values,
412 areas,
413 affected_cells,
414 candidates: None,
415 invalid_state_reason: None,
416 }
417 }
418
419 pub fn is_grid_step(&self) -> bool {
420 [ Rule::NakedSingle, Rule::HiddenSingle, Rule::Thermo, Rule::PalindromeValues ].contains(&self.rule)
421 }
422}
423
424impl SudokuGrid {
425 pub fn new(grid: Grid) -> Self {
426 SudokuGrid {
427 values: grid,
428 }
429 }
430
431 pub fn from_string(grid_str: String) -> Self {
432 let grid_size = f32::sqrt(grid_str.len() as f32) as usize;
433 assert_eq!(grid_size * grid_size, grid_str.len(), "Invalid grid passed");
434 let grid_chars = grid_str.chars().collect_vec();
435 let grid: Grid = (0..grid_size).map(|row| {
436 (0..grid_size).map(|col| {
437 let index = row * grid_size + col;
438 grid_chars[index].to_digit(10).unwrap()
439 }).collect()
440 }).collect();
441 Self {
442 values: grid,
443 }
444 }
445
446 pub fn from_fixed_numbers(grid_size: usize, fixed_numbers: &Vec<FixedNumber>) -> Self {
447 let mut grid: Grid = vec![ vec![ 0; grid_size ]; grid_size ];
448 for fixed_number in fixed_numbers {
449 let cell = fixed_number.position;
450 grid[cell.row][cell.col] = fixed_number.value;
451 }
452 SudokuGrid::new(grid)
453 }
454
455 pub fn to_fixed_numbers(&self) -> Vec<FixedNumber> {
456 let mut fixed_numbers = vec![];
457 for (row_index, row) in self.values.iter().enumerate() {
458 for (col_index, &value) in row.iter().enumerate() {
459 if value != 0 {
460 fixed_numbers.push(FixedNumber::new(row_index, col_index, value));
461 }
462 }
463 }
464 fixed_numbers
465 }
466
467 pub fn to_string(&self, separator: Option<&str>) -> String {
468 self.values
469 .iter()
470 .map(|row| {
471 row.iter()
472 .map(|digit| digit.to_string() )
473 .collect::<Vec<String>>()
474 .join("")
475 })
476 .collect::<Vec<String>>()
477 .join(separator.unwrap_or(""))
478 }
479}
480
481impl Area {
482 pub fn to_string(&self) -> String {
483 match self {
484 Area::Row(row) => format!("row {}", row + 1),
485 Area::Column(col) => format!("column {}", col + 1),
486 Area::Region(region) => format!("box {}", region + 1),
487 Area::Grid | Area::Adhoc(_) | Area::Cell(_, _) |
488 Area::Thermo(_) | Area::Arrow(_) |
489 Area::KillerCage(_) | Area::KropkiDot(_) |
490 Area::PrimaryDiagonal | Area::SecondaryDiagonal |
491 Area::Renban(_) | Area::Palindrome(_) => unimplemented!(),
492 }
493 }
494}
495
496#[test]
498fn check_sudoku_constraints_import_string() {
499 let fixed_numbers = SudokuGrid::new(vec![
500 vec![ 2, 0, 3, 0, 0, 0, 1, 0, 7 ],
501 vec![ 9, 1, 0, 0, 4, 0, 0, 2, 6 ],
502 vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
503 vec![ 3, 0, 8, 0, 1, 0, 7, 0, 9 ],
504 vec![ 1, 0, 0, 0, 0, 0, 0, 0, 2 ],
505 vec![ 0, 0, 2, 0, 0, 0, 8, 0, 0 ],
506 vec![ 0, 0, 4, 1, 6, 7, 2, 0, 0 ],
507 vec![ 7, 0, 0, 0, 8, 0, 0, 0, 1 ],
508 vec![ 8, 0, 0, 2, 0, 9, 0, 0, 3 ],
509 ]).to_fixed_numbers();
510 let constraints = SudokuConstraints::new(9, fixed_numbers);
511 assert_eq!(constraints.to_import_string(), String::from(
512 "203000107910040026000000000308010709100000002002000800004167200700080001800209003"
513 ))
514}
515
516#[test]
517fn check_sudoku_grid_from_string() {
518 let grid_str = String::from("203000107910040026000000000308010709100000002002000800004167200700080001800209003");
519 let grid = SudokuGrid::from_string(grid_str).values;
520 let expected_grid = vec![
521 vec![ 2, 0, 3, 0, 0, 0, 1, 0, 7 ],
522 vec![ 9, 1, 0, 0, 4, 0, 0, 2, 6 ],
523 vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
524 vec![ 3, 0, 8, 0, 1, 0, 7, 0, 9 ],
525 vec![ 1, 0, 0, 0, 0, 0, 0, 0, 2 ],
526 vec![ 0, 0, 2, 0, 0, 0, 8, 0, 0 ],
527 vec![ 0, 0, 4, 1, 6, 7, 2, 0, 0 ],
528 vec![ 7, 0, 0, 0, 8, 0, 0, 0, 1 ],
529 vec![ 8, 0, 0, 2, 0, 9, 0, 0, 3 ],
530 ];
531 assert_eq!(grid, expected_grid);
532}