lisudoku_solver/solver/logical_solver/
technique.rs

1use std::collections::HashSet;
2use crate::{solver::Solver, types::{Area, CellPosition, InvalidStateReason, InvalidStateType, Rule, SolutionStep}};
3
4pub trait Technique {
5  // It is a grid step if it fills in a value in the grid
6  fn is_grid_step(&self) -> bool { false }
7
8  // All XCandidates type techniques should return true because they need to run first
9  // These are the techniques that enforce constraints
10  fn is_candidate_validity_update_step(&self) -> bool { false }
11
12  fn get_rule(&self) -> Rule;
13
14  fn build_solution_step(
15    &self,
16    cells: Vec<CellPosition>, values: Vec<u32>, areas: Vec<Area>, affected_cells: Vec<CellPosition>
17  ) -> SolutionStep {
18    SolutionStep::new(self.get_rule(), cells, values, areas, affected_cells)
19  }
20
21  fn build_grid_solution_step(
22    &self, cells: Vec<CellPosition>, values: Vec<u32>, areas: Vec<Area>, solver: &Solver,
23  ) -> SolutionStep {
24    assert!(self.is_grid_step());
25    let affected_cells = if solver.candidates_active {
26      assert_eq!(cells.len(), 1);
27      let cell = cells[0];
28      let value = values[0];
29      let values_set = &HashSet::from([value]);
30      solver.get_affected_by_cell(&cell, values_set)
31    } else {
32      vec![]
33    };
34    self.build_solution_step(cells, values, areas, affected_cells)
35  }
36
37  fn build_simple_solution_step(
38    &self,
39    values: Vec<u32>, areas: Vec<Area>, affected_cells: Vec<CellPosition>
40  ) -> SolutionStep {
41    self.build_solution_step(vec![], values, areas, affected_cells)
42  }
43
44  fn run(&self, solver: &Solver) -> Vec<SolutionStep>;
45
46  // This is the default base implementation, but can be overridden
47  fn apply(&self, step: &SolutionStep, solver: &mut Solver) -> (bool, Option<InvalidStateReason>) {
48    if self.is_grid_step() {
49      if solver.candidates_active {
50        assert_eq!(step.cells.len(), 1);
51      }
52      let cell = step.cells[0];
53      let CellPosition { row, col } = cell;
54      let value = step.values[0];
55
56      if solver.step_count_limit.is_some() {
57        // In this mode we can apply multiple steps at once and
58        // we could find naked and hidden single for the same cell
59        // So if we find a contradiction we should stop
60
61        if solver.grid[row][col] != 0 && solver.grid[row][col] != value {
62          return (false, Some(InvalidStateReason {
63            state_type: InvalidStateType::CellInvalidValue,
64            area: Area::Cell(cell.row, cell.col),
65            values: vec![value],
66          }))
67        }
68      } else {
69        assert_eq!(solver.grid[row][col], 0, "Attempted to overwrite cell");
70      }
71
72      if solver.grid[row][col] == 0 {
73        solver.grid[row][col] = value;
74
75        if solver.candidates_active {
76          solver.candidates[row][col].clear();
77          solver.update_candidates(&step.affected_cells, value);
78        }
79      }
80    } else if self.apply_corresponding_indices() {
81      for (index, cell) in step.affected_cells.iter().enumerate() {
82        let value = step.values[index];
83        solver.candidates[cell.row][cell.col].remove(&value);
84      }
85    } else {
86      for &CellPosition { row, col } in &step.affected_cells {
87        for value in &step.values {
88          solver.candidates[row][col].remove(value);
89        }
90      }
91    }
92
93    (true, None)
94  }
95
96  fn apply_corresponding_indices(&self) -> bool { false }
97}