sudoku/strategy/
solver.rs

1use crate::bitset::Set;
2use crate::board::Candidate;
3use crate::board::*;
4use crate::helper::{CellArray, DigitArray, HouseArray, Unsolvable};
5use crate::strategy::{
6    deduction::{Deduction, Deductions},
7    strategies::*,
8};
9use crate::Sudoku;
10
11type EliminationsRange = std::ops::Range<usize>;
12type _Deduction = Deduction<EliminationsRange>;
13
14/// The `StrategySolver` is the struct for solving sudokus with
15/// strategies that humans commonly apply.
16///
17/// It is built from a single `Sudoku` for which it stores the current
18/// state and the history of applied strategies. It can find hints
19/// or solve the `Sudoku` completely and return the solution path.
20/// From the solving path, the difficulty can be graded.
21
22// To allow for the above functionality, this struct contains caches
23// of various properties of the sudoku grid. The caches are lazily updated
24// on demand. This avoids both unnecessary and repetitive work.
25//
26// Two histories are kept:
27// 1. A list of all strategies that were successfully used to deduce or eliminate entries
28// 2. Two lists for all entered and all eliminated digit-cell-entries
29//    The former also includes initial clues.
30//
31// The 1st list is for reporting the sequence of strategies applied
32// The 2nd list is for the updating of internal caches.
33// It is kept simple to offer an easy interface and can contain duplicates.
34//
35// These two histories can contain overlapping information and the
36// 1st one can also contain references to the 2nd but not vice versa.
37#[derive(Debug, Clone)]
38pub struct StrategySolver {
39    pub(crate) deductions: Vec<_Deduction>,
40    pub(crate) deduced_entries: Vec<Candidate>,
41    pub(crate) eliminated_entries: Vec<Candidate>,
42    pub(crate) n_solved: u8, // deduced_entries can contain duplicates so a separate counter is necessary
43
44    // optimization hints for strategies
45    pub(crate) hidden_singles_last_house: u8,
46
47    // The initial state of a sudoku given as a puzzle.
48    // If the solution is unique, this can be used for the strategy of
49    // AvoidableRectangles
50    // We can't assume that this struct is created only from clues nor that the information about them
51    // will always be present for the caller
52    #[allow(unused)]
53    pub(crate) clues: Option<Sudoku>,
54    // current state of the sudoku
55    // for when it's faster to recompute from the end state
56    // than update through the new entries
57    pub(crate) grid: State<Sudoku>,
58    // TODO: combine states that are updated together
59    // Mask of possible numbers in cell
60    pub(crate) cell_poss_digits: State<CellArray<Set<Digit>>>,
61    // Mask of solved digits in house
62    pub(crate) house_solved_digits: State<HouseArray<Set<Digit>>>,
63    // Mask of possible positions for a house and number
64    pub(crate) house_poss_positions: State<HouseArray<DigitArray<Set<Position<House>>>>>,
65}
66
67impl StrategySolver {
68    fn empty() -> StrategySolver {
69        StrategySolver {
70            deductions: vec![],
71            deduced_entries: vec![],
72            eliminated_entries: vec![],
73            n_solved: 0,
74            hidden_singles_last_house: 0,
75            clues: None,
76            grid: State::from(Sudoku([0; 81])),
77            cell_poss_digits: State::from(CellArray([Set::ALL; 81])),
78            house_solved_digits: State::from(HouseArray([Set::NONE; 27])),
79            house_poss_positions: State::from(HouseArray([DigitArray([Set::ALL; 9]); 27])),
80        }
81    }
82
83    /// Construct a new StrategySolver
84    pub fn from_sudoku(sudoku: Sudoku) -> StrategySolver {
85        let deduced_entries = sudoku
86            .iter()
87            .enumerate()
88            .filter_map(|(cell, opt_num)| opt_num.map(|digit| Candidate::new(cell as u8, digit)))
89            .collect();
90
91        StrategySolver {
92            deduced_entries,
93            ..StrategySolver::empty()
94        }
95    }
96
97    /// Construct a new StrategySolver with information about the initial clues.
98    /// This is only necessary if the [`AvoidableRectangles`](super::strategies::Strategy::AvoidableRectangles) is used.
99    pub fn from_sudoku_and_clues(sudoku: Sudoku, clues: Sudoku) -> StrategySolver {
100        StrategySolver {
101            clues: Some(clues),
102            ..StrategySolver::from_sudoku(sudoku)
103        }
104    }
105
106    /// Construct a new StrategySolver from an array of [`CellState`s](crate::board::CellState).
107    /// This allows communicating the impossibility of some candidates, that aren't already
108    /// trivially conflicting with entries. The cell order in the array is the same as for
109    /// [`crate::Sudoku`]s, i.e. left-to-right, top-to-bottom.
110    pub fn from_grid_state(grid_state: [CellState; 81]) -> StrategySolver {
111        let mut entries = vec![];
112        let mut eliminated_candidates = vec![];
113
114        for (cell, &cell_state) in Cell::all().zip(grid_state.iter()) {
115            match cell_state {
116                CellState::Digit(digit) => entries.push(Candidate { cell, digit }),
117                CellState::Candidates(cands) => {
118                    for digit in !cands {
119                        eliminated_candidates.push(Candidate { cell, digit });
120                    }
121                }
122            }
123        }
124
125        StrategySolver {
126            deduced_entries: entries,
127            eliminated_entries: eliminated_candidates,
128            ..StrategySolver::empty()
129        }
130    }
131
132    /// Construct a new StrategySolver from a printout of cell candidates.
133    /// This allows communicating the impossibility of some candidates, that aren't already
134    /// trivially conflicting with entries.
135    #[allow(unused)] // it is used, but only in conditionally compiled tests
136    pub(crate) fn from_grid_state_str(grid_state: &str) -> StrategySolver {
137        let mut _grid_state = [CellState::Candidates(Set::NONE); 81];
138        let entries = grid_state
139            .lines()
140            .flat_map(str::split_whitespace)
141            .filter(|&entry| entry == "_" || entry.parse::<u32>().is_ok());
142
143        for (cell_state, entry) in _grid_state.iter_mut().zip(entries) {
144            let candidates = entry
145                .as_bytes()
146                .iter()
147                .map(|byte| byte - b'0')
148                // only ascii bytes 1-9 will pass Digit::new_checked
149                .filter_map(Digit::new_checked)
150                .fold(Set::NONE, std::ops::BitOr::bitor);
151            let state = match candidates.unique().unwrap_or(None) {
152                Some(digit) => CellState::Digit(digit),
153                None => CellState::Candidates(candidates),
154            };
155            *cell_state = state;
156        }
157
158        Self::from_grid_state(_grid_state)
159    }
160
161    /// Returns the current state of the Sudoku
162    pub fn to_sudoku(&mut self) -> Sudoku {
163        self.update_grid();
164        self.grid.state
165    }
166
167    /// Returns the current state of the Sudoku including potential candidates
168    pub fn grid_state(&self) -> [CellState; 81] {
169        // cloning so .update_grid() can be called which updates caches
170        let mut solver = self.clone();
171
172        let mut grid = [CellState::Candidates(Set::NONE); 81];
173
174        solver.update_grid();
175        let _ = solver._update_cell_poss_house_solved(false, false);
176
177        for (cell, &digits) in solver.cell_poss_digits.state.iter().enumerate() {
178            grid[cell] = CellState::Candidates(digits);
179        }
180        for (cell, &digit) in solver
181            .grid
182            .state
183            .0
184            .iter()
185            .enumerate()
186            .filter(|(_, &digit)| digit != 0)
187        {
188            grid[cell] = CellState::Digit(Digit::new(digit));
189        }
190        grid
191    }
192
193    /// Returns the current state of the given `cell`
194    pub fn cell_state(&mut self, cell: Cell) -> CellState {
195        self.update_grid();
196        let _ = self._update_cell_poss_house_solved(false, false);
197
198        let digit = self.grid.state.0[cell.as_index()];
199        if digit != 0 {
200            CellState::Digit(Digit::new(digit))
201        } else {
202            let digits = self.cell_poss_digits.state[cell];
203            CellState::Candidates(digits)
204        }
205    }
206
207    /// Try to insert the given candidate. Fails, if the cell already contains a digit.
208    #[allow(clippy::result_unit_err)] // yes, it should be changed
209    pub fn insert_candidate(&mut self, candidate: Candidate) -> Result<(), ()> {
210        self.update_grid();
211        Self::push_new_candidate(
212            &mut self.grid.state,
213            &mut self.deduced_entries,
214            candidate,
215            &mut self.deductions,
216            Deduction::NakedSingles(candidate),
217        )
218        .map_err(|Unsolvable| ())?;
219        // TODO: remove the initial strategy insertion
220        self.deductions.pop();
221
222        Ok(())
223    }
224
225    #[rustfmt::skip]
226    fn into_deductions(self) -> Deductions {
227        let Self { deductions, deduced_entries, eliminated_entries, .. } = self;
228        Deductions { deductions, deduced_entries, eliminated_entries }
229    }
230
231    fn update_grid(&mut self) {
232        for &Candidate { cell, digit } in &self.deduced_entries {
233            self.grid.state.0[cell.as_index()] = digit.get();
234        }
235    }
236
237    /// Try to solve the sudoku using the given `strategies`. Returns a `Result` of the sudoku and a struct containing the series of deductions.
238    /// If a solution was found, `Ok(..)` is returned, otherwise `Err(..)`.
239    #[allow(clippy::result_large_err)] // nonsense, Ok and Err are the same size.
240    pub fn solve(mut self, strategies: &[Strategy]) -> Result<(Sudoku, Deductions), (Sudoku, Deductions)> {
241        self.try_solve(strategies);
242        self.update_grid();
243        match self.is_solved() {
244            true => Ok((self.grid.state, self.into_deductions())),
245            false => Err((self.grid.state, self.into_deductions())),
246        }
247    }
248
249    // FIXME: change name
250    /// Try to solve the sudoku using the given `strategies`. Returns `true` if new deductions were made.
251    fn try_solve(&mut self, strategies: &[Strategy]) -> bool {
252        // first strategy can be optimized
253        let (first, rest) = match strategies.split_first() {
254            Some(tup) => tup,
255            // no chance without strategies
256            None => return false,
257        };
258        let lens = (self.deduced_entries.len(), self.eliminated_entries.len());
259        'outer: loop {
260            if self.is_solved() {
261                break;
262            }
263
264            let n_deductions = self.deduced_entries.len();
265            let n_eliminated = self.eliminated_entries.len();
266            if first.deduce_all(self, true).is_err() {
267                break;
268            };
269            if self.deduced_entries.len() > n_deductions {
270                continue 'outer;
271            }
272
273            for strategy in rest {
274                if strategy.deduce_one(self).is_err() {
275                    break;
276                };
277                if self.deduced_entries.len() > n_deductions || self.eliminated_entries.len() > n_eliminated {
278                    continue 'outer;
279                }
280            }
281            break;
282        }
283        lens < (self.deduced_entries.len(), self.eliminated_entries.len())
284    }
285
286    /// Check whether the sudoku has been completely solved.
287    pub fn is_solved(&self) -> bool {
288        self.n_solved == 81
289    }
290
291    fn update_cell_poss_house_solved(&mut self) -> Result<(), Unsolvable> {
292        self._update_cell_poss_house_solved(false, true)
293    }
294
295    pub(crate) fn _update_cell_poss_house_solved(
296        &mut self,
297        find_naked_singles: bool,
298        early_return_on_error: bool,
299    ) -> Result<(), Unsolvable> {
300        let new_eliminations;
301        {
302            let (_, le_cp, cell_poss) = self.cell_poss_digits.get_mut();
303            new_eliminations = *le_cp as usize > self.eliminated_entries.len();
304
305            for &candidate in &self.eliminated_entries[*le_cp as _..] {
306                let impossibles = candidate.digit_set();
307
308                // deductions made here may conflict with entries already in the queue
309                // in the queue. In that case the sudoku is impossible.
310                let res = Self::remove_impossibilities(
311                    &mut self.grid.state,
312                    cell_poss,
313                    candidate.cell,
314                    impossibles,
315                    &mut self.deduced_entries,
316                    &mut self.deductions,
317                    find_naked_singles,
318                );
319                if early_return_on_error {
320                    res?;
321                }
322            }
323            *le_cp = self.eliminated_entries.len() as _;
324        }
325
326        self.insert_entries(find_naked_singles, new_eliminations)
327    }
328
329    fn update_house_poss_positions(&mut self) {
330        // TODO: this has to do massive amounts of work
331        //       may just be easier to recompute from full grid every time
332
333        let (ld, le, house_poss_positions) = self.house_poss_positions.get_mut();
334        // remove now impossible positions from list
335        for candidate in &self.eliminated_entries[*le as usize..] {
336            let cell = candidate.cell;
337            let row_pos = cell.row_pos();
338            let col_pos = cell.col_pos();
339            let block_pos = cell.block_pos();
340            // just 1 digit
341            let digit = candidate.digit;
342
343            house_poss_positions[cell.row()][digit].remove(row_pos.as_set());
344            house_poss_positions[cell.col()][digit].remove(col_pos.as_set());
345            house_poss_positions[cell.block()][digit].remove(block_pos.as_set());
346        }
347        *le = self.eliminated_entries.len() as _;
348
349        for candidate in &self.deduced_entries[*ld as usize..] {
350            let cell = candidate.cell;
351            let digit = candidate.digit;
352
353            // remove digit from every house pos in all neighboring cells
354            for cell in cell.neighbors() {
355                let row_pos = cell.row_pos();
356                let col_pos = cell.col_pos();
357                let block_pos = cell.block_pos();
358                house_poss_positions[cell.row()][digit].remove(row_pos.as_set());
359                house_poss_positions[cell.col()][digit].remove(col_pos.as_set());
360                house_poss_positions[cell.block()][digit].remove(block_pos.as_set());
361            }
362
363            let row = cell.row();
364            let col = cell.col();
365            let block = cell.block();
366            let row_pos = cell.row_pos();
367            let col_pos = cell.col_pos();
368            let block_pos = cell.block_pos();
369
370            // remove candidate pos as possible place for all nums
371            for digit in Digit::all() {
372                house_poss_positions[row][digit].remove(row_pos.as_set());
373                house_poss_positions[col][digit].remove(col_pos.as_set());
374                house_poss_positions[block][digit].remove(block_pos.as_set());
375            }
376
377            // remove all pos as possible place for candidate digit
378            house_poss_positions[row][digit] = Set::NONE;
379            house_poss_positions[col][digit] = Set::NONE;
380            house_poss_positions[block][digit] = Set::NONE;
381        }
382        *ld = self.deduced_entries.len() as _;
383    }
384
385    #[inline(always)]
386    fn insert_entries(&mut self, find_naked_singles: bool, new_eliminations: bool) -> Result<(), Unsolvable> {
387        // code hereafter depends on this
388        // but it's not necessary in general
389        assert!(self.cell_poss_digits.next_deduced == self.house_solved_digits.next_deduced);
390
391        if new_eliminations {
392            // start off with batch insertion so every cell is visited at least once
393            // because other strategies may have touched their possibilities which singly_insertion may miss
394            self.batch_insert_entries(find_naked_singles)?;
395        }
396        loop {
397            match self.deduced_entries.len() - self.cell_poss_digits.next_deduced as usize {
398                0 => break Ok(()),
399                1..=4 => self.insert_entries_singly(find_naked_singles)?,
400                _ => self.batch_insert_entries(find_naked_singles)?,
401            }
402        }
403    }
404
405    // for each candidate in the stack, insert it (if cell is unsolved)
406    // and then remove possibility from each cell neighboring it in all
407    // houses (rows, cols, fields) eagerly
408    // check for naked singles and impossible cells during this check
409    fn insert_entries_singly(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
410        let (ld_cp, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
411        let (ld_zs, _, house_solved_digits) = self.house_solved_digits.get_mut();
412
413        loop {
414            if self.deduced_entries.len() <= *ld_cp as usize {
415                break;
416            }
417            let candidate = self.deduced_entries[*ld_cp as usize];
418            *ld_cp += 1;
419            *ld_zs += 1;
420            let candidate_mask = candidate.digit_set();
421            // cell already solved from previous candidate in stack, skip
422            if cell_poss_digits[candidate.cell].is_empty() {
423                continue;
424            }
425
426            // is candidate still possible?
427            if (cell_poss_digits[candidate.cell] & candidate_mask).is_empty() {
428                return Err(Unsolvable);
429            }
430
431            Self::_insert_candidate_cp_zs(
432                candidate,
433                &mut self.n_solved,
434                cell_poss_digits,
435                house_solved_digits,
436            );
437            for cell in candidate.cell.neighbors() {
438                if candidate_mask.overlaps(cell_poss_digits[cell]) {
439                    Self::remove_impossibilities(
440                        &mut self.grid.state,
441                        cell_poss_digits,
442                        cell,
443                        candidate_mask,
444                        &mut self.deduced_entries,
445                        &mut self.deductions,
446                        find_naked_singles,
447                    )?;
448                };
449            }
450
451            // found a lot of naked singles, switch to batch insertion
452            if self.deduced_entries.len() - *ld_cp as usize > 4 {
453                return Ok(());
454            }
455        }
456        Ok(())
457    }
458
459    #[inline]
460    fn _insert_candidate_cp_zs(
461        candidate: Candidate,
462        n_solved: &mut u8,
463        cell_poss_digits: &mut CellArray<Set<Digit>>,
464        house_solved_digits: &mut HouseArray<Set<Digit>>,
465    ) {
466        *n_solved += 1;
467        cell_poss_digits[candidate.cell] = Set::NONE;
468        house_solved_digits[candidate.row()] |= candidate.digit_set();
469        house_solved_digits[candidate.col()] |= candidate.digit_set();
470        house_solved_digits[candidate.block()] |= candidate.digit_set();
471    }
472
473    fn batch_insert_entries(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
474        self._batch_insert_entries()?;
475        self._batch_remove_conflicts(find_naked_singles)
476    }
477
478    /// Insert all outstanding candidates without removing conflicting cells in neighboring cells.
479    /// Errors, if two different digits are candidates for the same cell.
480    fn _batch_insert_entries(&mut self) -> Result<(), Unsolvable> {
481        let (ld_cp, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
482        let (ld_zs, _, house_solved_digits) = self.house_solved_digits.get_mut();
483        while self.deduced_entries.len() > *ld_cp as usize {
484            let candidate = self.deduced_entries[*ld_cp as usize];
485            *ld_cp += 1;
486            *ld_zs += 1;
487            // cell already solved from previous candidate in stack, skip
488            if cell_poss_digits[candidate.cell].is_empty() {
489                continue;
490            }
491
492            let candidate_mask = candidate.digit_set();
493
494            // is candidate still possible?
495            // have to check house possibilities, because cell possibility
496            // is temporarily out of date
497            if house_solved_digits[candidate.row()].overlaps(candidate_mask)
498                || house_solved_digits[candidate.col()].overlaps(candidate_mask)
499                || house_solved_digits[candidate.block()].overlaps(candidate_mask)
500            {
501                return Err(Unsolvable);
502            }
503
504            Self::_insert_candidate_cp_zs(
505                candidate,
506                &mut self.n_solved,
507                cell_poss_digits,
508                house_solved_digits,
509            );
510        }
511        Ok(())
512    }
513
514    fn _batch_remove_conflicts(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
515        let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
516        let (_, _, house_solved_digits) = self.house_solved_digits.get_mut();
517        // update cell possibilities from house masks
518        for cell in Cell::all() {
519            if cell_poss_digits[cell].is_empty() {
520                continue;
521            }
522            let houses_mask = house_solved_digits[cell.row()]
523                | house_solved_digits[cell.col()]
524                | house_solved_digits[cell.block()];
525
526            Self::remove_impossibilities(
527                &mut self.grid.state,
528                cell_poss_digits,
529                cell,
530                houses_mask,
531                &mut self.deduced_entries,
532                &mut self.deductions,
533                find_naked_singles,
534            )?;
535        }
536        Ok(())
537    }
538
539    fn update_for_grid_state_str(&mut self) {
540        // naked singles and solved entries aren't distinguishable in the string representation
541        // so treat them as naked singles uniformly and remove all conflicting candidates
542        self.try_solve(&[Strategy::NakedSingles]);
543
544        // if the sudoku is impossible, the above will have stopped early.
545        // Remove conflicts with all entered candidates
546        //
547        // Note: This won't suffice if two different digits for the same cell are in self.deduced_entries.
548        // In that case, _batch_insert_entries() will still short circuit.
549        self._batch_remove_conflicts_no_check();
550    }
551
552    /// Update cell possibilities and find naked singles and empty cells.
553    /// To be used after _batch_insert_entries.
554    fn _batch_remove_conflicts_no_check(&mut self) {
555        let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
556        let (_, _, house_solved_digits) = self.house_solved_digits.get_mut();
557        // update cell possibilities from house masks
558        for cell in Cell::all() {
559            if cell_poss_digits[cell].is_empty() {
560                continue;
561            }
562            let houses_mask = house_solved_digits[cell.row()]
563                | house_solved_digits[cell.col()]
564                | house_solved_digits[cell.block()];
565
566            let cell_mask = &mut cell_poss_digits[cell];
567            cell_mask.remove(houses_mask);
568        }
569    }
570
571    // remove impossible digits from masks for given cell
572    // also check for naked singles and impossibility of sudoku
573    fn remove_impossibilities(
574        sudoku: &mut Sudoku,
575        cell_poss_digits: &mut CellArray<Set<Digit>>,
576        cell: Cell,
577        impossible: Set<Digit>,
578        deduced_entries: &mut Vec<Candidate>,
579        deductions: &mut Vec<_Deduction>,
580        find_naked_singles: bool,
581    ) -> Result<(), Unsolvable> {
582        let cell_mask = &mut cell_poss_digits[cell];
583        cell_mask.remove(impossible);
584
585        if find_naked_singles {
586            if let Some(digit) = cell_mask.unique()? {
587                let candidate = Candidate { cell, digit };
588                Self::push_new_candidate(
589                    sudoku,
590                    deduced_entries,
591                    candidate,
592                    deductions,
593                    Deduction::NakedSingles(candidate),
594                )?;
595            }
596        } else if cell_mask.is_empty() {
597            return Err(Unsolvable);
598        }
599        Ok(())
600    }
601
602    fn push_new_candidate(
603        sudoku: &mut Sudoku,
604        deduced_entries: &mut Vec<Candidate>,
605        candidate: Candidate,
606        deductions: &mut Vec<_Deduction>,
607        strategy: _Deduction, // either a user-given or naked or hidden singles
608    ) -> Result<(), Unsolvable> {
609        #[cfg(debug_assertions)]
610        {
611            use self::Deduction::*;
612            match strategy {
613                NakedSingles(..) | HiddenSingles(..) => (),
614                _ => panic!("Internal error: Called push_new_candidate with wrong strategy type"),
615            };
616        }
617
618        let old_num = &mut sudoku.0[candidate.cell.as_index()];
619        match *old_num {
620            n if n == candidate.digit.get() => return Ok(()), // previously solved
621            0 => (),                                          // not solved
622            _ => return Err(Unsolvable),                      // conflict
623        }
624        *old_num = candidate.digit.get();
625        deduced_entries.push(candidate);
626        deductions.push(strategy);
627        Ok(())
628    }
629
630    // Enter iterator of new impossible candidates.
631    // If there are any, enter deduction and return true, else false.
632    fn enter_conflicts(
633        eliminated: &mut Vec<Candidate>,
634        deductions: &mut Vec<Deduction<EliminationsRange>>,
635        conflicts: impl IntoIterator<Item = Candidate>,
636        deduction: impl FnOnce(EliminationsRange) -> Deduction<EliminationsRange>,
637    ) -> bool {
638        let len_before = eliminated.len();
639        eliminated.extend(conflicts);
640        let conflicts_rg = len_before..eliminated.len();
641
642        // there are 2 conflicting .is_empty() methods
643        // one is not stable (inherent on range), the other is not in scope (ExactSizeIterator)
644        #[allow(clippy::len_zero)]
645        let has_conflicts = conflicts_rg.len() > 0;
646
647        if has_conflicts {
648            deductions.push(deduction(conflicts_rg));
649        }
650        has_conflicts
651    }
652
653    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
654    ////////      Strategies
655    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
656
657    pub(crate) fn find_naked_singles(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
658        self.update_cell_poss_house_solved()?;
659
660        {
661            let cell_poss_digits = &self.cell_poss_digits.state;
662            let grid = &mut self.grid.state;
663            let deduced_entries = &mut self.deduced_entries;
664            let deductions = &mut self.deductions;
665
666            naked_singles::find_naked_singles(cell_poss_digits, stop_after_first, |candidate| {
667                deduced_entries.push(candidate);
668                Self::push_new_candidate(
669                    grid,
670                    deduced_entries,
671                    candidate,
672                    deductions,
673                    Deduction::NakedSingles(candidate),
674                )
675            })?;
676        }
677
678        // call update again so newly found entries are inserted
679        self.update_cell_poss_house_solved()
680    }
681
682    pub(crate) fn find_hidden_singles(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
683        self.update_cell_poss_house_solved()?;
684
685        {
686            let cell_poss_digits = &self.cell_poss_digits.state;
687            let house_solved_digits = &self.house_solved_digits.state;
688            let grid = &mut self.grid.state;
689            let deduced_entries = &mut self.deduced_entries;
690            let deductions = &mut self.deductions;
691
692            hidden_singles::find_hidden_singles(
693                &mut self.hidden_singles_last_house,
694                cell_poss_digits,
695                house_solved_digits,
696                stop_after_first,
697                |candidate, house| {
698                    let deduction = Deduction::HiddenSingles(candidate, house.categorize());
699                    Self::push_new_candidate(grid, deduced_entries, candidate, deductions, deduction)
700                },
701            )?;
702        }
703
704        // call update again so newly found entries are inserted
705        self.update_cell_poss_house_solved()
706    }
707
708    // stop after first will only eliminate line OR field neighbors for ONE number
709    // even if multiple are found at the same time
710    pub(crate) fn find_locked_candidates(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
711        self.update_cell_poss_house_solved()?;
712        let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
713        let eliminated_entries = &mut self.eliminated_entries;
714        let deductions = &mut self.deductions;
715
716        locked_candidates::find_locked_candidates(
717            cell_poss_digits,
718            stop_after_first,
719            |miniline, digit, _miniline_cands, neighbors, is_pointing| {
720                let conflicts = neighbors
721                    .iter()
722                    // helps on some, hurts on others
723                    //.filter(|neighb| _miniline_cands[neighb.as_index() % 9].contains(digit))
724                    .flat_map(|neighb| neighb.cells())
725                    .flat_map(|cell| {
726                        let conflicts = cell_poss_digits[cell] & digit;
727                        conflicts.into_iter().map(move |digit| Candidate { cell, digit })
728                    });
729
730                let on_locked = |conflicts| Deduction::LockedCandidates {
731                    miniline,
732                    digit,
733                    is_pointing,
734                    conflicts,
735                };
736                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_locked)
737            },
738        )
739    }
740
741    pub(crate) fn find_naked_subsets(
742        &mut self,
743        subset_size: u8,
744        stop_after_first: bool,
745    ) -> Result<(), Unsolvable> {
746        self.update_cell_poss_house_solved()?;
747        let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
748        let house_solved_digits = &mut self.house_solved_digits.state;
749        let eliminated_entries = &mut self.eliminated_entries;
750        let deductions = &mut self.deductions;
751
752        naked_subsets::find_naked_subsets(
753            cell_poss_digits,
754            house_solved_digits,
755            subset_size,
756            stop_after_first,
757            |house, positions, digits| {
758                let conflicts = (!positions)
759                    .into_iter()
760                    .map(|pos| house.cell_at(pos))
761                    .flat_map(|cell| {
762                        let conflicts = cell_poss_digits[cell] & digits;
763                        conflicts.into_iter().map(move |digit| Candidate { cell, digit })
764                    });
765
766                let on_conflict = |conflicts| Deduction::Subsets {
767                    house,
768                    positions,
769                    digits,
770                    conflicts,
771                };
772
773                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
774            },
775        )
776    }
777
778    pub(crate) fn find_hidden_subsets(
779        &mut self,
780        subset_size: u8,
781        stop_after_first: bool,
782    ) -> Result<(), Unsolvable> {
783        self.update_cell_poss_house_solved()?;
784        self.update_house_poss_positions();
785        let house_poss_positions = &self.house_poss_positions.state;
786        let house_solved_digits = &self.house_solved_digits.state;
787        let eliminated_entries = &mut self.eliminated_entries;
788        let deductions = &mut self.deductions;
789
790        hidden_subsets::find_hidden_subsets(
791            house_solved_digits,
792            house_poss_positions,
793            subset_size,
794            stop_after_first,
795            |house, digits, positions| {
796                let house_poss_positions = house_poss_positions[house];
797
798                let conflicts = (!digits).into_iter().flat_map(|digit| {
799                    let conflicts = house_poss_positions[digit] & positions;
800                    conflicts
801                        .into_iter()
802                        .map(|pos| house.cell_at(pos))
803                        .map(move |cell| Candidate { cell, digit })
804                });
805
806                let on_conflict = |conflicts| Deduction::Subsets {
807                    house,
808                    digits,
809                    positions,
810                    conflicts,
811                };
812
813                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
814            },
815        )
816    }
817
818    pub(crate) fn find_xwings(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
819        self.find_fish(2, stop_after_first)
820    }
821
822    pub(crate) fn find_swordfish(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
823        self.find_fish(3, stop_after_first)
824    }
825
826    pub(crate) fn find_jellyfish(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
827        self.find_fish(4, stop_after_first)
828    }
829
830    fn find_fish(&mut self, target_size: u8, stop_after_first: bool) -> Result<(), Unsolvable> {
831        self.update_house_poss_positions();
832        self.update_cell_poss_house_solved()?;
833
834        let cell_poss_digits = &self.cell_poss_digits.state;
835        let eliminated_entries = &mut self.eliminated_entries;
836        let deductions = &mut self.deductions;
837        let house_poss_positions = &self.house_poss_positions.state;
838
839        basic_fish::find_fish(
840            house_poss_positions,
841            target_size,
842            stop_after_first,
843            |all_lines, digit, lines, positions_in_line| {
844                let conflicts = all_lines
845                    .without(lines)
846                    .into_iter()
847                    .flat_map(|line| positions_in_line.into_iter().map(move |pos| line.cell_at(pos)))
848                    .filter(|&cell| cell_poss_digits[cell].contains(digit))
849                    .map(|cell| Candidate { cell, digit });
850
851                let on_conflict = |conflicts| Deduction::BasicFish {
852                    lines,
853                    digit,
854                    conflicts,
855                    positions: positions_in_line,
856                };
857
858                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
859            },
860        )
861    }
862
863    pub(crate) fn find_mutant_fish(
864        &mut self,
865        target_size: u8,
866        stop_after_first: bool,
867    ) -> Result<(), Unsolvable> {
868        self.update_house_poss_positions();
869        self.update_cell_poss_house_solved()?;
870
871        let cell_poss_digits = &self.cell_poss_digits.state;
872        let eliminated_entries = &mut self.eliminated_entries;
873        let deductions = &mut self.deductions;
874        let house_poss_positions = &self.house_poss_positions.state;
875
876        mutant_fish::find_mutant_fish(
877            house_poss_positions,
878            target_size,
879            stop_after_first,
880            |digit, candidate_cells, base, cover: Set<House>| {
881                let cover_cells = cover
882                    .into_iter()
883                    .map(House::cells)
884                    .fold(Set::NONE, std::ops::BitOr::bitor);
885
886                let impossible_cells: Set<Cell> = cover_cells ^ candidate_cells;
887
888                let conflicts = impossible_cells
889                    .into_iter()
890                    .filter(|&cell| cell_poss_digits[cell].contains(digit))
891                    .map(|cell| Candidate { cell, digit });
892
893                let on_conflict = |conflicts| Deduction::Fish {
894                    digit,
895                    base,
896                    cover,
897                    conflicts,
898                };
899
900                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
901            },
902        )
903    }
904
905    pub(crate) fn find_xy_wing(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
906        self.update_cell_poss_house_solved()?;
907        let cell_poss_digits = &self.cell_poss_digits.state;
908        let eliminated_entries = &mut self.eliminated_entries;
909        let deductions = &mut self.deductions;
910
911        xy_wing::find_xy_wing(
912            cell_poss_digits,
913            stop_after_first,
914            |(cell_hinge, poss_digits_hinge), [(cell_pincer1, poss_digs1), (cell_pincer2, poss_digs2)]| {
915                // TODO: pass common digit as argument to closure
916                let common_digit = (poss_digs1 & poss_digs2).unique().unwrap().unwrap();
917                let common_neighbors = cell_pincer1.neighbors_set() & cell_pincer2.neighbors_set();
918
919                let conflicts = common_neighbors
920                    .into_iter()
921                    .filter(|&cell| cell_poss_digits[cell].contains(common_digit))
922                    .map(|cell| Candidate {
923                        cell,
924                        digit: common_digit,
925                    });
926
927                let on_conflict = |conflicts| Deduction::Wing {
928                    hinge: cell_hinge,
929                    hinge_digits: poss_digits_hinge,
930                    pincers: cell_pincer1.as_set() | cell_pincer2,
931                    conflicts,
932                };
933
934                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
935            },
936        )
937    }
938
939    pub(crate) fn find_xyz_wing(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
940        self.update_cell_poss_house_solved()?;
941        let cell_poss_digits = &self.cell_poss_digits.state;
942        let eliminated_entries = &mut self.eliminated_entries;
943        let deductions = &mut self.deductions;
944
945        xyz_wing::find_xyz_wing(
946            cell_poss_digits,
947            stop_after_first,
948            |(cell_hinge, poss_digits_hinge), [(cell_pincer1, poss_digs1), (cell_pincer2, poss_digs2)]| {
949                // TODO: pass common digit as argument to closure
950                let common_digit = (poss_digs1 & poss_digs2).unique().unwrap().unwrap();
951                let common_neighbors =
952                    cell_hinge.neighbors_set() & cell_pincer1.neighbors_set() & cell_pincer2.neighbors_set();
953
954                assert_eq!(common_neighbors.len(), 2);
955
956                let conflicts = common_neighbors
957                    .into_iter()
958                    .filter(|&cell| cell_poss_digits[cell].contains(common_digit))
959                    .map(|cell| Candidate {
960                        cell,
961                        digit: common_digit,
962                    });
963
964                let on_conflict = |conflicts| Deduction::Wing {
965                    hinge: cell_hinge,
966                    hinge_digits: poss_digits_hinge,
967                    pincers: cell_pincer1.as_set() | cell_pincer2,
968                    conflicts,
969                };
970
971                Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
972            },
973        )
974    }
975
976    /*
977    pub(crate) fn find_singles_chain(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
978        #[derive(Copy, Clone, PartialEq, Eq)]
979        enum Color {
980            A,
981            B,
982        }
983
984        /// Recursively visit all cells connected by being the only 2 possible candidates in a house.
985        /// mark all visited cells
986        fn follow_links(digit: Digit, cell: Cell, is_a: bool, sudoku: &StrategySolver, cell_color: &mut CellArray<Option<Color>>, link_nr: u8, cell_linked: &mut CellArray<u8>) {
987            if cell_linked[cell] <= link_nr { return }
988
989            for &(con_house, current_pos) in &[
990                (cell.row().house(), cell.row_pos()),
991                (cell.col().house(), cell.col_pos()),
992                (cell.block().house(), cell.block_pos()),
993            ] {
994                let house_poss_positions = sudoku.house_poss_positions.state[con_house][digit];
995                if house_poss_positions.len() == 2 {
996                    let other_pos = house_poss_positions.without(current_pos.as_set()).one_possibility();
997                    let other_cell = con_house.cell_at(other_pos);
998
999                    match cell_linked[other_cell] <= link_nr {
1000                        true => continue,
1001                        false => cell_linked[other_cell] = link_nr,
1002                    };
1003
1004                    cell_color[other_cell] = if is_a { Some(Color::A) } else { Some(Color::B) };
1005
1006                    follow_links(digit, other_cell, !is_a, sudoku, cell_color, link_nr, cell_linked);
1007                }
1008            }
1009        }
1010
1011        for digit in Set::<Digit>::ALL {
1012            let mut link_nr = 0;
1013
1014            let mut cell_linked = CellArray([0; 81]);
1015            let mut cell_color = CellArray([None; 81]);
1016
1017            for house in House::all() {
1018                let house_poss_positions = self.house_poss_positions.state[house][digit];
1019                if house_poss_positions.len() == 2 {
1020                    let first = house_poss_positions.one_possibility();
1021                    let cell = house.cell_at(first);
1022
1023                    if cell_color[cell].is_none() {
1024                        follow_links(digit, cell, true, self, &mut cell_color, link_nr, &mut cell_linked);
1025                        link_nr += 1;
1026                    };
1027                }
1028            }
1029
1030            for link_nr in 0..link_nr {
1031                // Rule 1:
1032                // if two cells in the same row, part of the same chain
1033                // have the same color, those cells must not contain the number
1034                // Rule 2:
1035                // if one cell is neighbor to two cells with opposite colors
1036                // it can not contain the number
1037
1038
1039                // ===== Rule 1 ======
1040                for house in House::all() {
1041                    // Collect colors in this link chain and this house
1042                    let mut house_colors = [None; 9];
1043                    for (pos, cell) in house.cells()
1044                        .into_iter()
1045                        .enumerate()
1046                        // TODO: Double check the logic here
1047                        // this used to take the pos for indexing
1048                        .filter(|&(_, cell)| cell_linked[cell] == link_nr)
1049                    {
1050                        house_colors[pos] = cell_color[cell];
1051                    }
1052
1053                    let (n_a, n_b) = house_colors.iter()
1054                        .fold((0, 0), |(n_a, n_b), &color| {
1055                            match color {
1056                                Some(Color::A) => (n_a+1, n_b),
1057                                Some(Color::B) => (n_a, n_b+1),
1058                                None => (n_a, n_b),
1059                            }
1060                        });
1061
1062                    fn mark_impossible(digit: Digit, link_nr: u8, color: Color, cell_color: CellArray<Option<Color>>, cell_linked: CellArray<u8>, impossible_entries: &mut Vec<Candidate>) {
1063                        Cell::all().zip(cell_color.iter()).zip(cell_linked.iter())
1064                            .filter(|&((_, &cell_color), &cell_link_nr)| link_nr == cell_link_nr && Some(color) == cell_color)
1065                            .for_each(|((cell, _), _)| impossible_entries.push( Candidate { cell, digit }));
1066                    }
1067
1068                    let impossible_color;
1069                    match (n_a >= 2, n_b >= 2) {
1070                        (true, true) => return Err(Unsolvable),
1071                        (true, false) => impossible_color = Color::A,
1072                        (false, true) => impossible_color = Color::B,
1073                        (false, false) => continue,
1074                    };
1075                    mark_impossible(digit, link_nr, impossible_color, cell_color, cell_linked, &mut self.eliminated_entries);
1076                    // chain handled, go to next
1077                    // note: as this eagerly marks a color impossible as soon as a double in any color is found
1078                    //       a case of two doubles in some later house will not always be found
1079                    //       impossibility is then detected further down the strategy chain
1080                    break
1081                }
1082
1083                // ===== Rule 2 =====
1084                let mut cell_sees_color = CellArray([(false, false); 81]);
1085                for ((cell, &cell_color), _) in Cell::all()
1086                    .zip(cell_color.iter())
1087                    .zip(cell_linked.iter())
1088                    .filter(|&((_, &cell_color), &cell_link_nr)| link_nr == cell_link_nr && cell_color.is_some())
1089                {
1090                    for &house in &cell.houses() {
1091                        for neighbor_cell in house.cells().into_iter().filter(|&c| cell != c) {
1092                            let (sees_a, sees_b) = cell_sees_color[neighbor_cell];
1093                            if cell_color == Some(Color::A) && !sees_a {
1094                                cell_sees_color[neighbor_cell].0 = true;
1095                                if sees_b {
1096                                    self.eliminated_entries.push( Candidate{ cell: neighbor_cell, digit })
1097                                }
1098                            } else if cell_color == Some(Color::B) && !sees_b {
1099                                cell_sees_color[neighbor_cell].1 = true;
1100                                if sees_a {
1101                                    self.eliminated_entries.push( Candidate{ cell: neighbor_cell, digit })
1102                                }
1103                            }
1104                        }
1105                    }
1106                }
1107            }
1108        }
1109        Ok(())
1110    }
1111    */
1112}
1113
1114impl std::fmt::Display for StrategySolver {
1115    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
1116        let mut solver = self.clone();
1117
1118        solver.update_for_grid_state_str();
1119
1120        print_grid_state(
1121            f,
1122            solver.grid_state(),
1123            "┌",
1124            "┐",
1125            "└",
1126            "┘",
1127            "├",
1128            "┤",
1129            "┬",
1130            "┴",
1131            "┼",
1132            "─",
1133            "│",
1134            /*
1135            "+",
1136            "+",
1137            "+",
1138            "+",
1139            "+",
1140            "+",
1141            "+",
1142            "+",
1143            "+",
1144            "-",
1145            "|",
1146            */
1147        )
1148    }
1149}
1150
1151#[derive(Debug, Clone)]
1152pub(crate) struct State<T> {
1153    next_deduced: u16,
1154    last_eliminated: u16, // probably doesn't exceed 2^8, but can't prove it
1155    state: T,
1156}
1157
1158impl<T> State<T> {
1159    fn from(this: T) -> Self {
1160        State {
1161            next_deduced: 0,
1162            last_eliminated: 0,
1163            state: this,
1164        }
1165    }
1166}
1167
1168impl<T> State<T> {
1169    fn get_mut(&mut self) -> (&mut u16, &mut u16, &mut T) {
1170        let State {
1171            next_deduced: ld,
1172            last_eliminated: le,
1173            state,
1174        } = self;
1175        (ld, le, state)
1176    }
1177}
1178
1179#[cfg(test)]
1180mod test {
1181    use super::*;
1182    fn read_sudokus(sudokus_str: &str) -> Vec<Sudoku> {
1183        sudokus_str
1184            .lines()
1185            .map(|line| Sudoku::from_str_line(line).unwrap_or_else(|err| panic!("{:?}", err)))
1186            .collect()
1187    }
1188
1189    fn strategy_solver_correct_solution<F>(sudokus: Vec<Sudoku>, solved_sudokus: Vec<Sudoku>, solver: F)
1190    where
1191        F: Fn(StrategySolver, &[Strategy]) -> Result<(Sudoku, Deductions), (Sudoku, Deductions)>,
1192    {
1193        let n_sudokus = sudokus.len();
1194        let strategies = Strategy::ALL;
1195        let mut unsolved = vec![];
1196        for (i, (sudoku, solved_sudoku)) in sudokus.into_iter().zip(solved_sudokus).enumerate() {
1197            let cache = StrategySolver::from_sudoku(sudoku);
1198            match solver(cache, strategies) {
1199                Ok((solution, _deductions)) => assert_eq!(solution, solved_sudoku),
1200                Err((part_solved, _deductions)) => unsolved.push((i, sudoku, part_solved, solved_sudoku)),
1201            }
1202        }
1203        if !unsolved.is_empty() {
1204            println!("Could not solve {}/{} sudokus:\n", unsolved.len(), n_sudokus);
1205
1206            for (i, sudoku, part_solution, _solution) in unsolved {
1207                println!(
1208                    "\nsudoku nr {}:\n{}\n{}\n{}",
1209                    i + 1,
1210                    sudoku.to_str_line(),
1211                    part_solution.to_str_line(),
1212                    _solution.to_str_line()
1213                );
1214            }
1215            panic!();
1216        }
1217    }
1218
1219    #[test]
1220    fn strategy_solver_correct_solution_easy_sudokus() {
1221        let sudokus = read_sudokus(include_str!("../../sudokus/Lines/easy_sudokus.txt"));
1222        let solved_sudokus = read_sudokus(include_str!("../../sudokus/Lines/solved_easy_sudokus.txt"));
1223        strategy_solver_correct_solution(sudokus, solved_sudokus, StrategySolver::solve);
1224    }
1225
1226    #[test]
1227    fn strategy_solver_correct_solution_medium_sudokus() {
1228        // the 9th sudoku requires more advanced strategies
1229        let filter_9 = |vec: Vec<_>| {
1230            vec.into_iter()
1231                .enumerate()
1232                .filter(|&(i, _)| i != 8)
1233                .map(|(_, sudoku)| sudoku)
1234                .collect::<Vec<_>>()
1235        };
1236        let sudokus = filter_9(read_sudokus(include_str!(
1237            "../../sudokus/Lines/medium_sudokus.txt"
1238        )));
1239        let solved_sudokus = filter_9(read_sudokus(include_str!(
1240            "../../sudokus/Lines/solved_medium_sudokus.txt"
1241        )));
1242        strategy_solver_correct_solution(sudokus, solved_sudokus, StrategySolver::solve);
1243    }
1244
1245    #[test]
1246    fn roundtrip_grid_state_str() {
1247        let sudokus = read_sudokus(include_str!("../../sudokus/Lines/easy_sudokus.txt"));
1248
1249        for sudoku in sudokus {
1250            let solver = StrategySolver::from_sudoku(sudoku);
1251            let grid_state_string = solver.to_string();
1252            let solver2 = StrategySolver::from_grid_state_str(&grid_state_string);
1253            let grid_state_string2 = solver2.to_string();
1254            if grid_state_string != grid_state_string2 {
1255                panic!("\n{} \n{}", grid_state_string, grid_state_string2);
1256            }
1257        }
1258    }
1259
1260    #[test]
1261    fn grid_state_str_impossible_sudoku() {
1262        let sudoku =
1263        //"12345678.........9..4.376..6..4..5...3.....7...7..2..4..521.3............7...481.";
1264        "12345678.........9...............................................................";
1265        let sudoku = Sudoku::from_str_line(sudoku).unwrap();
1266
1267        let solver = StrategySolver::from_sudoku(sudoku);
1268
1269        #[rustfmt::skip]
1270        let expected =
1271"┌──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
1272│ 1         2         3        │ 4         5         6        │ 7         8         _        │
1273│ 45678     45678     45678    │ 12378     12378     12378    │ 123456    123456    9        │
1274│ 456789    456789    456789   │ 123789    123789    123789   │ 123456    123456    123456   │
1275├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
1276│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1277│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1278│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1279├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
1280│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1281│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1282│ 23456789  13456789  12456789 │ 12356789  12346789  12345789 │ 12345689  12345679  12345678 │
1283└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
1284";
1285
1286        assert_eq!(expected, &solver.to_string());
1287
1288        //let solver2 = StrategySolver::from_grid_state_str(&grid_state_string);
1289        //let grid_state_string2 = solver2.to_string();
1290        //if grid_state_string != grid_state_string2 {
1291        //    panic!("\n{}\n{}", grid_state_string, grid_state_string2);
1292        //}
1293    }
1294}
1295
1296fn print_grid_state(
1297    f: &mut std::fmt::Formatter,
1298    grid_state: [CellState; 81],
1299    upper_left_corner: &str,
1300    upper_right_corner: &str,
1301    lower_left_corner: &str,
1302    lower_right_corner: &str,
1303
1304    left_junction: &str,
1305    right_junction: &str,
1306    upper_junction: &str,
1307    lower_junction: &str,
1308    middle_junction: &str,
1309
1310    horizontal_bar: &str,
1311    vertical_bar: &str,
1312) -> Result<(), std::fmt::Error> {
1313    // TODO: Decide what to print if a cell has no candidates anymore
1314    //       leaving empty is possible, but more difficult to parse correctly
1315    //       '_' is another good alternative
1316    let mut column_widths = [0; 9];
1317    for (col, col_width) in column_widths.iter_mut().enumerate() {
1318        let max_width = (0..9)
1319            .map(|row| row * 9 + col)
1320            .map(|cell| match grid_state[cell] {
1321                CellState::Digit(_) => 1,
1322                CellState::Candidates(digits) => std::cmp::max(digits.len(), 1),
1323            })
1324            .max()
1325            .unwrap();
1326
1327        *col_width = max_width;
1328    }
1329
1330    let mut lengths = [0; 3];
1331    for stack in 0..3 {
1332        lengths[stack] = column_widths[stack * 3..][..3].iter().sum::<u8>() as usize;
1333    }
1334    _print_separator(
1335        f,
1336        upper_left_corner,
1337        upper_junction,
1338        upper_right_corner,
1339        horizontal_bar,
1340        lengths,
1341    )?;
1342    for band in 0..3 {
1343        for row in 0..3 {
1344            write!(f, "{}", vertical_bar)?;
1345            for stack in 0..3 {
1346                for col in 0..3 {
1347                    let full_row = band * 3 + row;
1348                    let full_col = stack * 3 + col;
1349                    let cell_state = grid_state[full_row * 9 + full_col];
1350                    match cell_state {
1351                        CellState::Digit(digit) => {
1352                            write!(f, " {:<1$} ", digit.get(), column_widths[full_col] as usize)?
1353                        }
1354                        CellState::Candidates(cands) => {
1355                            write!(f, " ")?;
1356                            if cands.is_empty() {
1357                                write!(f, "_")?;
1358                            } else {
1359                                for digit in cands {
1360                                    write!(f, "{}", digit.get())?;
1361                                }
1362                            }
1363                            write!(
1364                                f,
1365                                "{:1$}",
1366                                ' ',
1367                                (1 + column_widths[full_col] - std::cmp::max(cands.len(), 1)) as usize
1368                            )?;
1369                        }
1370                    }
1371                }
1372                write!(f, "{}", vertical_bar)?;
1373            }
1374            writeln!(f)?;
1375        }
1376        if band != 2 {
1377            _print_separator(
1378                f,
1379                left_junction,
1380                middle_junction,
1381                right_junction,
1382                horizontal_bar,
1383                lengths,
1384            )?;
1385        }
1386    }
1387    _print_separator(
1388        f,
1389        lower_left_corner,
1390        lower_junction,
1391        lower_right_corner,
1392        horizontal_bar,
1393        lengths,
1394    )
1395}
1396
1397fn _print_separator(
1398    f: &mut std::fmt::Formatter,
1399    left_junction: &str,
1400    middle_junction: &str,
1401    right_junction: &str,
1402    horizontal_bar: &str,
1403    lengths: [usize; 3],
1404) -> Result<(), std::fmt::Error> {
1405    writeln!(
1406        f,
1407        "{left}{line0}{middle}{line1}{middle}{line2}{right}",
1408        line0 = horizontal_bar.repeat(lengths[0] + 6),
1409        line1 = horizontal_bar.repeat(lengths[1] + 6),
1410        line2 = horizontal_bar.repeat(lengths[2] + 6),
1411        left = left_junction,
1412        middle = middle_junction,
1413        right = right_junction,
1414    )
1415}