lisudoku_solver/solver/
checker.rs

1use crate::solver::Solver;
2use crate::types::{Area, CellPosition, InvalidStateReason, InvalidStateType, KropkiDot, KropkiDotType};
3use itertools::Itertools;
4use std::collections::{HashMap, HashSet};
5use std::mem::swap;
6use super::logical_solver::{technique::Technique, top_bottom_candidates::TopBottomCandidates};
7
8impl Solver {
9  pub fn check_solved(&self) -> (bool, Option<InvalidStateReason>) {
10    self.check_grid_valid(false)
11  }
12
13  pub fn check_partially_solved(&self) -> (bool, Option<InvalidStateReason>) {
14    self.check_grid_valid(true)
15  }
16
17  fn check_grid_valid(&self, allow_empty: bool) -> (bool, Option<InvalidStateReason>) {
18    for cell in self.get_area_cells(&Area::Grid) {
19      let value = self.grid[cell.row][cell.col];
20      if value == 0 {
21        if !allow_empty {
22          return (
23            false,
24            Some(InvalidStateReason {
25              state_type: InvalidStateType::CellEmpty,
26              area: Area::Cell(cell.row, cell.col),
27              values: vec![],
28            }),
29          )
30        }
31
32        let cell_candidates = self.compute_cell_candidates(&cell);
33        if cell_candidates.is_empty() {
34          return (
35            false,
36            Some(InvalidStateReason {
37              state_type: InvalidStateType::CellNoCandidates,
38              area: Area::Cell(cell.row, cell.col),
39              values: vec![],
40            }),
41          )
42        }
43      } else if value < 1 || value > self.constraints.grid_size as u32 {
44        return (
45          false,
46          Some(InvalidStateReason {
47            state_type: InvalidStateType::CellInvalidValue,
48            area: Area::Cell(cell.row, cell.col),
49            values: vec![],
50          }),
51        )
52      }
53    }
54
55    for area in self.get_all_areas(true, true, true, true, true) {
56      let check = self.check_area_valid(&area);
57      if !check.0 {
58        return check
59      }
60    }
61
62    for arrow_index in 0..self.constraints.arrows.len() {
63      let check = self.check_arrow_valid(arrow_index);
64      if !check.0 {
65        return check
66      }
67    }
68
69    if self.constraints.anti_knight {
70      let check = self.check_anti_knight_valid();
71      if !check.0 {
72        return check
73      }
74    }
75
76    if self.constraints.anti_king {
77      let check = self.check_anti_king_valid();
78      if !check.0 {
79        return check
80      }
81    }
82
83    let check = self.check_odd_cells();
84    if !check.0 {
85      return check
86    }
87
88    let check = self.check_even_cells();
89    if !check.0 {
90      return check
91    }
92
93    if self.constraints.top_bottom {
94      let check = self.check_top_bottom_valid();
95      if !check.0 {
96        return check
97      }
98    }
99
100    (true, None)
101  }
102
103  fn check_area_valid(&self, area: &Area) -> (bool, Option<InvalidStateReason>) {
104    match area {
105      &Area::Row(_) | &Area::Column(_) | &Area::Region(_) |
106        &Area::PrimaryDiagonal | &Area::SecondaryDiagonal => self.check_area_region_valid(area),
107      &Area::KillerCage(killer_cage_index) => self.check_killer_area_valid(area, killer_cage_index),
108      &Area::Thermo(_) => self.check_thermo_area_valid(area),
109      &Area::KropkiDot(kropki_dot_index) => self.check_kropki_dot_valid(kropki_dot_index),
110      &Area::Renban(_) => self.check_renban_valid(area),
111      &Area::Palindrome(_) => self.check_palindrome_valid(area),
112      &Area::Grid | &Area::Adhoc(_) | &Area::Cell(_, _) | &Area::Arrow(_) => unimplemented!(),
113    }
114  }
115
116  fn check_area_region_valid(&self, area: &Area) -> (bool, Option<InvalidStateReason>) {
117    let mut values = HashSet::new();
118    let mut candidates = HashSet::new();
119
120    let area_cells = self.get_area_cells(area);
121    for cell in &area_cells {
122      let value = self.grid[cell.row][cell.col];
123      if value == 0 {
124        candidates.extend(self.compute_cell_candidates(cell));
125        continue
126      }
127      if values.contains(&value) {
128        return (
129          false,
130          Some(InvalidStateReason {
131            state_type: InvalidStateType::AreaValueConflict,
132            area: area.clone(),
133            values: vec![value],
134          }),
135        )
136      }
137      values.insert(value);
138    }
139
140    candidates.extend(values);
141
142    // There's less candidate values than area cells so there is no solution
143    if candidates.len() < area_cells.len() {
144      let values = if area_cells.len() == self.constraints.grid_size {
145        self.compute_all_candidates().difference(&candidates).copied().sorted().collect()
146      } else {
147        vec![]
148      };
149
150      return (
151        false,
152        Some(InvalidStateReason {
153          state_type: InvalidStateType::AreaCandidates,
154          area: area.clone(),
155          values,
156        }),
157      )
158    }
159
160    if area_cells.len() == self.constraints.grid_size {
161      // It's a full area, so we need to place all values
162      // Check if all remaining candidate subsets fit into the remaining cells
163
164      let mut value_cells: HashMap<u32, Vec<CellPosition>> = HashMap::new();
165      for cell in self.get_empty_area_cells(area) {
166        for value in self.compute_cell_candidates(&cell) {
167          let entry = value_cells.entry(value).or_insert(vec![]);
168          entry.push(cell);
169        }
170      }
171
172      let mut used_cells_set: HashSet<CellPosition> = HashSet::new();
173      let mut values = vec![];
174      for (value_index, (value, value_cells)) in value_cells.into_iter().sorted_by_key(|e| (e.1.len(), e.0)).enumerate() {
175        used_cells_set.extend(value_cells);
176        values.push(value);
177        if value_index + 1 > used_cells_set.len() {
178          return (
179            false,
180            Some(InvalidStateReason {
181              state_type: InvalidStateType::AreaCandidates,
182              area: area.clone(),
183              values,
184            }),
185          )
186        }
187      }
188    }
189
190    (true, None)
191  }
192
193  fn check_thermo_area_valid(&self, area: &Area) -> (bool, Option<InvalidStateReason>) {
194    let mut crt_max_value: u32 = 0;
195
196    for CellPosition { row, col } in self.get_area_cells(area) {
197      let value = self.grid[row][col];
198      if value == 0 {
199        continue
200      }
201      if value <= crt_max_value {
202        return (
203          false,
204          Some(InvalidStateReason {
205            state_type: InvalidStateType::AreaConstraint,
206            area: area.clone(),
207            values: vec![crt_max_value, value],
208          }),
209        )
210      }
211      crt_max_value = value
212    }
213
214    (true, None)
215  }
216
217  fn check_arrow_valid(&self, arrow_index: usize) -> (bool, Option<InvalidStateReason>) {
218    let arrow = &self.constraints.arrows[arrow_index];
219    let (arrow_sum, arrow_full) = self.arrow_arrow_sum(arrow);
220    let (circle_number, circle_full) = self.arrow_circle_number(arrow);
221
222    if arrow_full && circle_full {
223      if arrow_sum == circle_number {
224        return (true, None)
225      }
226      return (
227        false,
228        Some(InvalidStateReason {
229          state_type: InvalidStateType::AreaConstraint,
230          area: Area::Arrow(arrow_index),
231          values: vec![],
232        }),
233      )
234    }
235
236    if circle_full {
237      if arrow_sum <= circle_number {
238        return (true, None)
239      }
240
241      return (
242        false,
243        Some(InvalidStateReason {
244          state_type: InvalidStateType::AreaConstraint,
245          area: Area::Arrow(arrow_index),
246          values: vec![],
247        }),
248      )
249    }
250
251    (true, None)
252  }
253
254  fn check_renban_valid(&self, area: &Area) -> (bool, Option<InvalidStateReason>) {
255    let check = self.check_area_region_valid(area);
256    if !check.0 {
257      return check
258    }
259
260    let mut min_value: u32 = self.constraints.grid_size as u32 + 1;
261    let mut max_value: u32 = 0;
262    let mut any_zero = false;
263    let values = self.get_area_values(area);
264    for &value in &values {
265      if value == 0 {
266        any_zero = true;
267      }
268      if value > 0 && value < min_value {
269        min_value = value;
270      }
271      if value > max_value {
272        max_value = value;
273      }
274    }
275
276    if max_value == 0 {
277      // All zeroes is valid (well, not necessarily, but it's not the checker's job)
278      return (true, None)
279    }
280
281    if (!any_zero && max_value - min_value + 1 != values.len() as u32) ||
282       (any_zero && max_value - min_value + 1 > values.len() as u32) {
283      return (
284        false,
285        Some(InvalidStateReason {
286          state_type: InvalidStateType::AreaConstraint,
287          area: area.clone(),
288          values: vec![],
289        }),
290      )
291    }
292
293    (true, None)
294  }
295
296  fn check_palindrome_valid(&self, area: &Area) -> (bool, Option<InvalidStateReason>) {
297    let values = self.get_area_values(area);
298    let mut left = 0;
299    let mut right = values.len() - 1;
300    while left < right {
301      if values[left] != 0 && values[right] != 0 && values[left] != values[right] {
302        return (
303          false,
304          Some(InvalidStateReason {
305            state_type: InvalidStateType::AreaConstraint,
306            area: area.clone(),
307            values: vec![left as u32, right as u32],
308          }),
309        )
310      }
311      left += 1;
312      right -= 1;
313    }
314
315    (true, None)
316  }
317
318  fn check_anti_knight_valid(&self) -> (bool, Option<InvalidStateReason>) {
319    for cell in self.get_area_cells(&Area::Grid) {
320      let value = self.grid[cell.row][cell.col];
321      if value == 0 {
322        continue
323      }
324
325      for peer in self.get_knight_peers(&cell) {
326        let peer_value = self.grid[peer.row][peer.col];
327        if peer_value == 0 {
328          continue
329        }
330        if value == peer_value {
331          return (
332            false,
333            Some(InvalidStateReason {
334              state_type: InvalidStateType::CellInvalidValue,
335              area: Area::Cell(cell.row, cell.col),
336              values: vec![value],
337            }),
338          )
339        }
340      }
341    }
342
343    (true, None)
344  }
345
346  fn check_anti_king_valid(&self) -> (bool, Option<InvalidStateReason>) {
347    for cell in self.get_area_cells(&Area::Grid) {
348      let value = self.grid[cell.row][cell.col];
349      if value == 0 {
350        continue
351      }
352
353      for peer in self.get_king_peers(&cell) {
354        let peer_value = self.grid[peer.row][peer.col];
355        if peer_value == 0 {
356          continue
357        }
358        if value == peer_value {
359          return (
360            false,
361            Some(InvalidStateReason {
362              state_type: InvalidStateType::CellInvalidValue,
363              area: Area::Cell(cell.row, cell.col),
364              values: vec![value],
365            }),
366          )
367        }
368      }
369    }
370
371    (true, None)
372  }
373
374  fn check_killer_area_valid(&self, area: &Area, killer_cage_index: usize) -> (bool, Option<InvalidStateReason>) {
375    let check = self.check_area_region_valid(area);
376    if !check.0 {
377      return check
378    }
379
380    let mut sum: u32 = 0;
381    let mut any_zero = false;
382    for cell in self.get_area_cells(&area) {
383      let value = self.grid[cell.row][cell.col];
384      if value == 0 {
385        any_zero = true;
386      }
387      sum += value;
388    }
389
390    let killer_cage = &self.constraints.killer_cages[killer_cage_index];
391    if let Some(killer_sum) = killer_cage.sum {
392      if sum != killer_sum && !any_zero || sum > killer_sum {
393        return (
394          false,
395          Some(InvalidStateReason {
396            state_type: InvalidStateType::AreaConstraint,
397            area: area.clone(),
398            values: vec![],
399          }),
400        )
401      }
402    }
403
404    (true, None)
405  }
406
407  fn check_kropki_dot_valid(&self, kropki_dot_index: usize) -> (bool, Option<InvalidStateReason>) {
408    let kropki_dot = &self.constraints.kropki_dots[kropki_dot_index];
409    let KropkiDot { dot_type, cell_1, cell_2 } = kropki_dot;
410    let mut value1 = self.grid[cell_1.row][cell_1.col];
411    let mut value2 = self.grid[cell_2.row][cell_2.col];
412    if value1 > value2 {
413      swap(&mut value1, &mut value2);
414    }
415    if value1 == 0 {
416      return (true, None)
417    }
418
419    let valid = match dot_type {
420      KropkiDotType::Consecutive => {
421        value1 + 1 == value2
422      },
423      KropkiDotType::Double => {
424        value1 * 2 == value2
425      },
426      KropkiDotType::Negative => {
427        value1 + 1 != value2 && value1 * 2 != value2
428      },
429    };
430
431    if valid {
432      return (true, None)
433    }
434
435    (
436      false,
437      Some(InvalidStateReason {
438        state_type: InvalidStateType::AreaConstraint,
439        area: Area::KropkiDot(kropki_dot_index),
440        values: vec![],
441      }),
442    )
443  }
444
445  fn check_odd_cells(&self) -> (bool, Option<InvalidStateReason>) {
446    for cell in &self.constraints.odd_cells {
447      let value = self.grid[cell.row][cell.col];
448      if value != 0 && value % 2 == 0 {
449        return (
450          false,
451          Some(InvalidStateReason {
452            state_type: InvalidStateType::CellInvalidValue,
453            area: Area::Cell(cell.row, cell.col),
454            values: vec![value],
455          }),
456        )
457      }
458    }
459    (true, None)
460  }
461
462  fn check_even_cells(&self) -> (bool, Option<InvalidStateReason>) {
463    for cell in &self.constraints.even_cells {
464      let value = self.grid[cell.row][cell.col];
465      if value != 0 && value % 2 == 1 {
466        return (
467          false,
468          Some(InvalidStateReason {
469            state_type: InvalidStateType::CellInvalidValue,
470            area: Area::Cell(cell.row, cell.col),
471            values: vec![value],
472          }),
473        )
474      }
475    }
476    (true, None)
477  }
478
479  fn check_top_bottom_valid(&self) -> (bool, Option<InvalidStateReason>) {
480    let valid = TopBottomCandidates::new(true).run(&self).is_empty();
481
482    if valid {
483      return (true, None)
484    }
485
486    (
487      false,
488      Some(InvalidStateReason {
489        state_type: InvalidStateType::AreaConstraint,
490        area: Area::Grid,
491        values: vec![],
492      }),
493    )
494  }
495}