qqwing/
lib.rs

1//! qqwing - Sudoku solver and generator
2//!
3//! Copyright (C) 2006-2014 Stephen Ostermiller <http://ostermiller.org/>
4//!
5//! Copyright (C) 2007 Jacques Bensimon (jacques@ipm.com)
6//!
7//! Copyright (C) 2007 Joel Yarde (joel.yarde - gmail.com)
8//!
9//!
10//! This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
11//!
12//! This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13//!
14//! You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15
16use rand::{self, random, seq::SliceRandom, thread_rng};
17use std::usize;
18use strum::{EnumIter, EnumString};
19use thiserror::Error;
20use tracing::{debug, info};
21
22use difficulty::Difficulty;
23use logitem::LogItem;
24use logtype::LogType;
25use symmetry::Symmetry;
26
27/// Module for puzzle difficulty.
28pub mod difficulty;
29/// Module for log item.
30pub mod logitem;
31/// Module for log type.
32pub mod logtype;
33/// Module for puzzle symmetry.
34pub mod symmetry;
35const UNSET_VALUE: usize = 4294967295;
36const NL: &str = "\n";
37const GRID_SIZE: usize = 3;
38const ROW_COL_SEC_SIZE: usize = GRID_SIZE * GRID_SIZE;
39const SEC_GROUP_SIZE: usize = ROW_COL_SEC_SIZE * GRID_SIZE;
40pub const BOARD_SIZE: usize = ROW_COL_SEC_SIZE * ROW_COL_SEC_SIZE;
41const POSSIBILITY_SIZE: usize = BOARD_SIZE * ROW_COL_SEC_SIZE;
42
43#[derive(Error, Debug)]
44pub enum QQWingError {
45    #[error("Marking position that already has been marked.")]
46    PositionAlreadyMarked,
47    #[error("Marking position that was marked another round.")]
48    PositionMarkedAnotherRound,
49    #[error("Marking impossible position.")]
50    PositionImpossible,
51}
52
53/// The board containing all the memory structures and methods for solving or
54/// generating sudoku puzzles.
55#[derive(Debug)]
56pub struct QQWing {
57    /**
58     * The last round of solving
59     */
60    last_solve_round: u8,
61
62    /**
63     * The 81 integers that make up a sudoku puzzle. Givens are 1-9, unknowns
64     * are 0. Once initialized, this puzzle remains as is. The answer is worked
65     * out in "solution".
66     */
67    puzzle: [u8; BOARD_SIZE],
68
69    /**
70     * The 81 integers that make up a sudoku puzzle. The solution is built here,
71     * after completion all will be 1-9.
72     */
73    solution: [u8; BOARD_SIZE],
74
75    /**
76     * Recursion depth at which each of the numbers in the solution were placed.
77     * Useful for backing out solve branches that don't lead to a solution.
78     */
79    solution_round: [u8; BOARD_SIZE],
80
81    /**
82     * The 729 integers that make up a the possible values for a Sudoku puzzle.
83     * (9 possibilities for each of 81 squares). If possibilities[i] is zero,
84     * then the possibility could still be filled in according to the Sudoku
85     * rules. When a possibility is eliminated, possibilities[i] is assigned the
86     * round (recursion level) at which it was determined that it could not be a
87     * possibility.
88     */
89    possibilities: [u8; POSSIBILITY_SIZE],
90
91    /**
92     * An array the size of the board (81) containing each of the numbers 0-n
93     * exactly once. This array may be shuffled so that operations that need to
94     * look at each cell can do so in a random order.
95     */
96    random_board_array: [u8; BOARD_SIZE],
97
98    /**
99     * An array with one element for each position (9), in some random order to
100     * be used when trying each position in turn during guesses.
101     */
102    random_possibility_array: [u8; ROW_COL_SEC_SIZE],
103
104    /**
105     * Whether or not to record history
106     */
107    record_history: bool,
108
109    /**
110     * Whether or not to print history as it happens
111     */
112    log_history: bool,
113
114    /**
115     * A list of moves used to solve the puzzle. This list contains all moves,
116     * even on solve branches that did not lead to a solution.
117     */
118    solve_history: Vec<LogItem>,
119
120    /**
121     * A list of moves used to solve the puzzle. This list contains only the
122     * moves needed to solve the puzzle, but doesn't contain information about
123     * bad guesses.
124     */
125    solve_instructions: Vec<LogItem>,
126
127    /**
128     * The style with which to print puzzles and solutions
129     */
130    pub print_style: PrintStyle,
131}
132
133impl QQWing {
134    pub fn new() -> Self {
135        Self {
136            last_solve_round: 0,
137            puzzle: [0; BOARD_SIZE],
138            solution: [0; BOARD_SIZE],
139            solution_round: [0; BOARD_SIZE],
140            possibilities: [0; POSSIBILITY_SIZE],
141            random_possibility_array: core::array::from_fn::<u8, ROW_COL_SEC_SIZE, _>(|i| i as u8),
142            random_board_array: core::array::from_fn::<u8, BOARD_SIZE, _>(|i| i as u8),
143            record_history: false,
144            log_history: false,
145            solve_history: Vec::new(),
146            solve_instructions: Vec::new(),
147            print_style: PrintStyle::READABLE,
148        }
149    }
150
151    /**
152     * Get the number of cells that are set in the puzzle (as opposed to figured
153     * out in the solution
154     */
155    fn get_given_count(&self) -> u32 {
156        let mut count = 0;
157        for i in 0..BOARD_SIZE {
158            if self.puzzle[i] != 0 {
159                count += 1;
160            }
161        }
162        count
163    }
164
165    /**
166     * Set the board to the given puzzle. The given puzzle must be an array of 81 integers.
167     */
168    pub fn set_puzzle(&mut self, init_puzzle: Vec<u8>) -> bool {
169        for i in 0..BOARD_SIZE {
170            self.puzzle[i] = init_puzzle[i];
171        }
172        self.reset()
173    }
174
175    /**
176     * Reset the board to its initial state with only the givens. This method
177     * clears any solution, resets statistics, and clears any history messages.
178     */
179    fn reset(&mut self) -> bool {
180        self.solution.fill(0);
181        self.solution_round.fill(0);
182        self.possibilities.fill(0);
183        self.solve_history.clear();
184        self.solve_instructions.clear();
185
186        let round = 1;
187        for position in 0..BOARD_SIZE {
188            if self.puzzle[position] > 0 {
189                let val_index = self.puzzle[position] - 1;
190                let val_pos = QQWing::get_possibility_index(val_index as usize, position);
191                let value = self.puzzle[position];
192                if self.possibilities[val_pos] != 0 {
193                    return false;
194                }
195                let _ = self.mark(position, round, value).unwrap();
196                if self.log_history || self.record_history {
197                    self.add_history_item(LogItem::new(
198                        round,
199                        LogType::Given,
200                        value as usize,
201                        position,
202                    ));
203                }
204            }
205        }
206
207        true
208    }
209
210    /**
211     * Get the difficulty rating.
212     *
213     * This method will return Difficulty::UNKNOWN unless
214     * a puzzle has been generated or set and then the following methods called:
215     * set_record_history(true), and solve()
216     */
217    pub fn get_difficulty(&self) -> Difficulty {
218        if self.get_guess_count() > 0 {
219            return Difficulty::EXPERT;
220        }
221        if self.get_box_line_reduction_count() > 0 {
222            return Difficulty::MEDIUM;
223        }
224        if self.get_pointing_pair_triple_count() > 0 {
225            return Difficulty::MEDIUM;
226        }
227        if self.get_hidden_pair_count() > 0 {
228            return Difficulty::MEDIUM;
229        }
230        if self.get_naked_pair_count() > 0 {
231            return Difficulty::MEDIUM;
232        }
233        if self.get_hidden_single_count() > 0 {
234            return Difficulty::EASY;
235        }
236        if self.get_single_count() > 0 {
237            return Difficulty::SIMPLE;
238        }
239        return Difficulty::UNKNOWN;
240    }
241
242    /**
243     * Get the number of cells for which the solution was determined because
244     * there was only one possible value for that cell.
245     */
246    fn get_single_count(&self) -> usize {
247        QQWing::get_log_count(&self.solve_instructions, LogType::Single)
248    }
249
250    /**
251     * Get the number of cells for which the solution was determined because
252     * that cell had the only possibility for some value in the row, column, or
253     * section.
254     */
255    fn get_hidden_single_count(&self) -> usize {
256        QQWing::get_log_count(&self.solve_instructions, LogType::HiddenSingleRow)
257            + QQWing::get_log_count(&self.solve_instructions, LogType::HiddenSingleColumn)
258            + QQWing::get_log_count(&self.solve_instructions, LogType::HiddenSingleSection)
259    }
260
261    /**
262     * Get the number of naked pair reductions that were performed in solving
263     * this puzzle.
264     */
265    fn get_naked_pair_count(&self) -> usize {
266        QQWing::get_log_count(&self.solve_instructions, LogType::NakedPairRow)
267            + QQWing::get_log_count(&self.solve_instructions, LogType::NakedPairColumn)
268            + QQWing::get_log_count(&self.solve_instructions, LogType::NakedPairSection)
269    }
270
271    /**
272     * Get the number of hidden pair reductions that were performed in solving
273     * this puzzle.
274     */
275    fn get_hidden_pair_count(&self) -> usize {
276        QQWing::get_log_count(&self.solve_instructions, LogType::HiddenPairRow)
277            + QQWing::get_log_count(&self.solve_instructions, LogType::HiddenPairColumn)
278            + QQWing::get_log_count(&self.solve_instructions, LogType::HiddenPairSection)
279    }
280
281    /**
282     * Get the number of pointing pair/triple reductions that were performed in
283     * solving this puzzle.
284     */
285    fn get_pointing_pair_triple_count(&self) -> usize {
286        QQWing::get_log_count(&self.solve_instructions, LogType::PointingPairTripleRow)
287            + QQWing::get_log_count(&self.solve_instructions, LogType::PointingPairTripleColumn)
288    }
289
290    /**
291     * Get the number of box/line reductions that were performed in solving this
292     * puzzle.
293     */
294    fn get_box_line_reduction_count(&self) -> usize {
295        QQWing::get_log_count(&self.solve_instructions, LogType::RowBox)
296            + QQWing::get_log_count(&self.solve_instructions, LogType::ColumnBox)
297    }
298
299    /**
300     * Get the number lucky guesses in solving this puzzle.
301     */
302    fn get_guess_count(&self) -> usize {
303        QQWing::get_log_count(&self.solve_instructions, LogType::Guess)
304    }
305
306    /**
307     * Get the number of backtracks (unlucky guesses) required when solving this
308     * puzzle.
309     */
310    fn get_backtrack_count(&self) -> usize {
311        QQWing::get_log_count(&self.solve_history, LogType::Rollback)
312    }
313
314    fn shuffle_random_arrays(&mut self) {
315        let mut rng = thread_rng();
316        self.random_board_array.shuffle(&mut rng);
317        self.random_possibility_array.shuffle(&mut rng);
318    }
319
320    fn clear_puzzle(&mut self) {
321        debug!("Clear any existing puzzle");
322        for i in 0..BOARD_SIZE {
323            self.puzzle[i] = 0;
324        }
325        self.reset();
326    }
327
328    /// Generate a new sudoku puzzle.
329    pub fn generate_puzzle(&mut self) -> bool {
330        self.generate_puzzle_symmetry(Symmetry::NONE)
331    }
332
333    fn generate_puzzle_symmetry(&mut self, symmetry: Symmetry) -> bool {
334        let mut symmetry = symmetry;
335        if symmetry == Symmetry::RANDOM {
336            symmetry = QQWing::get_random_symmetry();
337        }
338        debug!("Symmetry: {:?}", symmetry);
339        // Don't record history while generating.
340        let rec_history = self.record_history;
341        self.set_record_history(false);
342        let l_history = self.record_history;
343        self.set_log_history(false);
344
345        self.clear_puzzle();
346
347        // Start by getting the randomness in order so that
348        // each puzzle will be different from the last.
349        self.shuffle_random_arrays();
350
351        // Now solve the puzzle the whole way. The solve
352        // uses random algorithms, so we should have a
353        // really randomly totally filled sudoku
354        // Even when starting from an empty grid
355        self.solve();
356
357        if symmetry == Symmetry::NONE {
358            // Rollback any square for which it is obvious that
359            // the square doesn't contribute to a unique solution
360            // (ie, squares that were filled by logic rather
361            // than by guess)
362            self.rollback_non_guesses();
363        }
364
365        // Record all marked squares as the puzzle so
366        // that we can call countSolutions without losing it.
367        for i in 0..BOARD_SIZE {
368            self.puzzle[i] = self.solution[i];
369        }
370
371        // Rerandomize everything so that we test squares
372        // in a different order than they were added.
373        self.shuffle_random_arrays();
374
375        // Remove one value at a time and see if
376        // the puzzle still has only one solution.
377        // If it does, leave it out the point because
378        // it is not needed.
379        for i in 0..BOARD_SIZE {
380            // check all the positions, but in shuffled order
381            let position = self.random_board_array[i] as usize;
382            if self.puzzle[position] > 0 {
383                let mut positionsym1 = UNSET_VALUE;
384                let mut positionsym2 = UNSET_VALUE;
385                let mut positionsym3 = UNSET_VALUE;
386                match symmetry {
387                    Symmetry::ROTATE90 => {
388                        positionsym2 = QQWing::row_column_to_cell(
389                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_column(position),
390                            QQWing::cell_to_row(position),
391                        );
392                        positionsym3 = QQWing::row_column_to_cell(
393                            QQWing::cell_to_column(position),
394                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_row(position),
395                        );
396                    }
397                    Symmetry::ROTATE180 => {
398                        positionsym1 = QQWing::row_column_to_cell(
399                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_row(position),
400                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_column(position),
401                        )
402                    }
403                    Symmetry::MIRROR => {
404                        positionsym1 = QQWing::row_column_to_cell(
405                            QQWing::cell_to_row(position),
406                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_column(position),
407                        )
408                    }
409                    Symmetry::FLIP => {
410                        positionsym1 = QQWing::row_column_to_cell(
411                            ROW_COL_SEC_SIZE - 1 - QQWing::cell_to_row(position),
412                            QQWing::cell_to_column(position),
413                        )
414                    }
415                    _ => {}
416                }
417                // try backing out the value and
418                // counting solutions to the puzzle
419                let saved_value = self.puzzle[position];
420                self.puzzle[position] = 0;
421                let mut saved_sym1 = 0;
422                if positionsym1 != UNSET_VALUE {
423                    saved_sym1 = self.puzzle[positionsym1];
424                    self.puzzle[positionsym1] = 0;
425                }
426                let mut saved_sym2 = 0;
427                if positionsym2 != UNSET_VALUE {
428                    saved_sym2 = self.puzzle[positionsym2];
429                    self.puzzle[positionsym2] = 0;
430                }
431                let mut saved_sym3 = 0;
432                if positionsym3 != UNSET_VALUE {
433                    saved_sym3 = self.puzzle[positionsym3];
434                    self.puzzle[positionsym3] = 0;
435                }
436                self.reset();
437                if self.count_solutions_round(2, true) > 1 {
438                    // Put it back in, it is needed
439                    self.puzzle[position] = saved_value;
440                    if positionsym1 != UNSET_VALUE && saved_sym1 != 0 {
441                        self.puzzle[positionsym1] = saved_sym1;
442                    }
443                    if positionsym2 != UNSET_VALUE && saved_sym2 != 0 {
444                        self.puzzle[positionsym2] = saved_sym2;
445                    }
446                    if positionsym3 != UNSET_VALUE && saved_sym3 != 0 {
447                        self.puzzle[positionsym3] = saved_sym3;
448                    }
449                }
450            }
451        }
452
453        // Clear all solution info, leaving just the puzzle.
454        self.reset();
455
456        // Restore recording history.
457        self.set_record_history(rec_history);
458        self.set_log_history(l_history);
459
460        true
461    }
462
463    fn rollback_non_guesses(&mut self) {
464        // Guesses are odd rounds
465        // Non-guesses are even rounds
466        for i in 2..self.last_solve_round {
467            if i % 2 == 1 {
468                continue;
469            }
470            self.rollback_round(i);
471        }
472    }
473
474    pub fn set_print_style(&mut self, ps: PrintStyle) {
475        self.print_style = ps;
476    }
477
478    pub fn set_record_history(&mut self, rec_history: bool) {
479        self.record_history = rec_history;
480    }
481
482    pub fn set_log_history(&mut self, log_hist: bool) {
483        self.log_history = log_hist;
484    }
485
486    fn add_history_item(&mut self, l: LogItem) {
487        if self.log_history {
488            info!("{}", l);
489        }
490        if self.record_history {
491            self.solve_history.push(l.clone()); // ->push_back(l);
492            self.solve_instructions.push(l); // ->push_back(l);
493        }
494    }
495
496    pub fn print_history(&self, v: Vec<LogItem>) {
497        println!("{}", self.history_to_string(v));
498    }
499
500    fn history_to_string(&self, v: Vec<LogItem>) -> String {
501        let mut sb = String::new();
502        if !self.record_history {
503            sb.push_str("History was not recorded.");
504            if self.print_style == PrintStyle::CSV {
505                sb.push_str(" -- ");
506            } else {
507                sb.push_str(NL);
508            }
509        }
510        for i in 0..v.len() {
511            sb.push_str(&(i + 1).to_string());
512            sb.push_str(". ");
513            sb.push_str(format!("{}", v[i]).as_str());
514            if self.print_style == PrintStyle::CSV {
515                sb.push_str(" -- ");
516            } else {
517                sb.push_str(NL);
518            }
519        }
520        if self.print_style == PrintStyle::CSV {
521            sb.push_str(",");
522        } else {
523            sb.push_str(NL);
524        }
525        sb
526    }
527
528    pub fn print_solve_instructions(&self) {
529        println!("\nSolve instructions:");
530        println!("{}", self.get_solve_instructions_string());
531    }
532
533    fn get_solve_instructions_string(&self) -> String {
534        if self.is_solved() {
535            return self.history_to_string(self.solve_instructions.clone());
536        } else {
537            return "No solve instructions - Puzzle is not possible to solve.".to_string();
538        }
539    }
540
541    pub fn get_solve_instructions(&self) -> Vec<LogItem> {
542        match self.is_solved() {
543            true => self.solve_instructions.clone(),
544            false => Vec::new(),
545        }
546    }
547
548    pub fn print_solve_history(&self) {
549        self.print_history(self.solve_history.clone());
550    }
551
552    pub fn get_solve_history_string(&self) -> String {
553        self.history_to_string(self.solve_history.clone())
554    }
555
556    pub fn get_solve_history(&self) -> Vec<LogItem> {
557        self.solve_history.clone()
558    }
559
560    /// Solve the puzzle.
561    pub fn solve(&mut self) -> bool {
562        self.reset();
563        self.shuffle_random_arrays();
564        debug!("Solve round 2");
565        self.solve_round(2)
566    }
567
568    fn solve_round(&mut self, round: u8) -> bool {
569        self.last_solve_round = round;
570
571        while self.single_solve_move(round) {
572            if self.is_solved() {
573                return true;
574            }
575            if self.is_impossible() {
576                return false;
577            }
578        }
579
580        let next_guess_round = round + 1;
581        let next_round = round + 2;
582        let mut guess_number = 0;
583        while self.guess(next_guess_round, guess_number) {
584            if self.is_impossible() || !self.solve_round(next_round) {
585                self.rollback_round(next_round);
586                self.rollback_round(next_guess_round);
587            } else {
588                return true;
589            }
590            guess_number += 1;
591        }
592        false
593    }
594
595    /**
596     * return true if the puzzle has no solutions at all
597     */
598    pub fn has_no_solution(&mut self) -> bool {
599        self.count_solutions_limited() == 0
600    }
601
602    /**
603     * return true if the puzzle has a solution
604     * and only a single solution
605     */
606    pub fn has_unique_solution(&mut self) -> bool {
607        self.count_solutions_limited() == 1
608    }
609
610    /**
611     * return true if the puzzle has more than one solution
612     */
613
614    pub fn has_multiple_solutions(&mut self) -> bool {
615        self.count_solutions_limited() > 1
616    }
617
618    /**
619     * Count the number of solutions to the puzzle
620     */
621    pub fn count_total_solutions(&mut self) -> u32 {
622        self.count_solutions(false)
623    }
624
625    /**
626     * Count the number of solutions to the puzzle
627     * but return two any time there are two or
628     * more solutions.  This method will run much
629     * faster than count_total_solutions() when there
630     * are many possible solutions and can be used
631     * when you are interested in knowing if the
632     * puzzle has zero, one, or multiple solutions.
633     */
634    pub fn count_solutions_limited(&mut self) -> u32 {
635        self.count_solutions(true)
636    }
637
638    fn count_solutions(&mut self, limit_to_two: bool) -> u32 {
639        // Don't record history while generating.
640        let rec_history = self.record_history;
641        self.set_record_history(false);
642        let l_history = self.log_history;
643        self.set_log_history(false);
644
645        self.reset();
646        let solution_count = self.count_solutions_round(2, limit_to_two);
647
648        // Restore recording history.
649        self.set_record_history(rec_history);
650        self.set_log_history(l_history);
651
652        solution_count
653    }
654
655    fn count_solutions_round(&mut self, round: u8, limit_to_two: bool) -> u32 {
656        while self.single_solve_move(round) {
657            if self.is_solved() {
658                self.rollback_round(round);
659                return 1;
660            }
661            if self.is_impossible() {
662                self.rollback_round(round);
663                return 0;
664            }
665        }
666
667        let mut solutions = 0;
668        let next_round = round + 1;
669        let mut guess_number = 0;
670        while self.guess(next_round, guess_number) {
671            solutions += self.count_solutions_round(next_round, limit_to_two);
672            if limit_to_two && solutions >= 2 {
673                self.rollback_round(round);
674                return solutions;
675            }
676            guess_number += 1;
677        }
678        self.rollback_round(round);
679
680        solutions
681    }
682
683    fn rollback_round(&mut self, round: u8) {
684        if self.log_history || self.record_history {
685            self.add_history_item(LogItem::new(
686                round,
687                LogType::Rollback,
688                4294967295,
689                4294967295,
690            ));
691        }
692
693        for i in 0..BOARD_SIZE {
694            if self.solution_round[i] == round {
695                self.solution_round[i] = 0;
696                self.solution[i] = 0;
697            }
698        }
699        for i in 0..POSSIBILITY_SIZE {
700            if self.possibilities[i] == round {
701                self.possibilities[i] = 0;
702            }
703        }
704        while self.solve_instructions.len() > 0
705            && self.solve_instructions.last().unwrap().get_round() == round
706        {
707            let i = self.solve_instructions.len() - 1;
708            self.solve_instructions.remove(i);
709        }
710    }
711
712    /// Check if the puzzle is solved.
713    pub fn is_solved(&self) -> bool {
714        for i in 0..BOARD_SIZE {
715            if self.solution[i] == 0 {
716                return false;
717            }
718        }
719        true
720    }
721
722    fn is_impossible(&self) -> bool {
723        for position in 0..BOARD_SIZE {
724            if self.solution[position] == 0 {
725                let mut count = 0;
726                for val_index in 0..ROW_COL_SEC_SIZE {
727                    let val_pos = QQWing::get_possibility_index(val_index, position);
728                    if self.possibilities[val_pos] == 0 {
729                        count += 1;
730                    }
731                }
732                if count == 0 {
733                    return true;
734                }
735            }
736        }
737        false
738    }
739
740    fn find_position_with_fewest_possibilities(&self) -> usize {
741        let mut min_possibilities = 10;
742        let mut best_position = 0;
743        for i in 0..BOARD_SIZE {
744            let position = self.random_board_array[i];
745            if self.solution[position as usize] == 0 {
746                let mut count = 0;
747                for val_index in 0..ROW_COL_SEC_SIZE {
748                    let val_pos = QQWing::get_possibility_index(val_index, position as usize);
749                    if self.possibilities[val_pos] == 0 {
750                        count += 1;
751                    }
752                }
753                if count < min_possibilities {
754                    min_possibilities = count;
755                    best_position = position;
756                }
757            }
758        }
759        return best_position as usize;
760    }
761
762    fn guess(&mut self, round: u8, guess_number: u32) -> bool {
763        debug!("Guess round: {}, number: {}", round, guess_number);
764        let mut local_guess_count = 0;
765        let position = self.find_position_with_fewest_possibilities();
766        for i in 0..ROW_COL_SEC_SIZE {
767            let val_index = self.random_possibility_array[i];
768            let val_pos = QQWing::get_possibility_index(val_index as usize, position);
769            if self.possibilities[val_pos] == 0 {
770                if local_guess_count == guess_number {
771                    let value = val_index + 1;
772                    if self.log_history || self.record_history {
773                        self.add_history_item(LogItem::new(
774                            round,
775                            LogType::Guess,
776                            value as usize,
777                            position,
778                        ));
779                    }
780                    let _ = self.mark(position, round, value).unwrap();
781                    return true;
782                }
783                local_guess_count += 1;
784            }
785        }
786        false
787    }
788
789    fn single_solve_move(&mut self, round: u8) -> bool {
790        debug!("Single Solve Move, round: {}", round);
791        if self.only_possibility_for_cell(round) {
792            debug!("only_possibility_for_cell round {} is ture", round);
793            return true;
794        }
795        if self.only_value_in_section(round) {
796            debug!("only_value_in_section round {} is ture", round);
797            return true;
798        }
799        if self.only_value_in_row(round) {
800            debug!("only_value_in_row round {} is ture", round);
801            return true;
802        }
803        if self.only_value_in_column(round) {
804            debug!("only_value_in_column round {} is ture", round);
805            return true;
806        }
807        if self.handle_naked_pairs(round) {
808            debug!("handle_naked_pairs round {} is ture", round);
809            return true;
810        }
811        if self.pointing_row_reduction(round) {
812            debug!("pointing_row_reduction round {} is ture", round);
813            return true;
814        }
815        if self.pointing_column_reduction(round) {
816            debug!("pointing_column_reduction round {} is ture", round);
817            return true;
818        }
819        if self.row_box_reduction(round) {
820            debug!("row_box_reduction round {} is ture", round);
821            return true;
822        }
823        if self.col_box_reduction(round) {
824            debug!("col_box_reduction round {} is ture", round);
825            return true;
826        }
827        if self.hidden_pair_in_row(round) {
828            debug!("hidden_pair_in_row round {} is ture", round);
829            return true;
830        }
831        if self.hidden_pair_in_column(round) {
832            debug!("hidden_pair_in_column round {} is ture", round);
833            return true;
834        }
835        if self.hidden_pair_in_section(round) {
836            debug!("hidden_pair_in_section round {} is ture", round);
837            return true;
838        }
839        debug!("single_solve_move round {} is false", round);
840        false
841    }
842
843    fn col_box_reduction(&mut self, round: u8) -> bool {
844        debug!("col_box_reduction round: {}", round);
845        for val_index in 0..ROW_COL_SEC_SIZE {
846            for col in 0..ROW_COL_SEC_SIZE {
847                let col_start = col;
848                let mut in_one_box = true;
849                let mut col_box = UNSET_VALUE;
850                for i in 0..GRID_SIZE {
851                    for j in 0..GRID_SIZE {
852                        let row = i * GRID_SIZE + j;
853                        let position = QQWing::row_column_to_cell(row, col);
854                        let val_pos = QQWing::get_possibility_index(val_index, position);
855                        if self.possibilities[val_pos] == 0 {
856                            if col_box == UNSET_VALUE || col_box == i {
857                                col_box = i;
858                            } else {
859                                in_one_box = false;
860                            }
861                        }
862                    }
863                }
864                if in_one_box && col_box != UNSET_VALUE {
865                    let mut done_something = false;
866                    let row = GRID_SIZE * col_box;
867                    let sec_start =
868                        QQWing::cell_to_section_start_cell(QQWing::row_column_to_cell(row, col));
869                    let sec_start_row = QQWing::cell_to_row(sec_start);
870                    let sec_start_col = QQWing::cell_to_column(sec_start);
871                    for i in 0..GRID_SIZE {
872                        for j in 0..GRID_SIZE {
873                            let row2 = sec_start_row + i;
874                            let col2 = sec_start_col + j;
875                            let position = QQWing::row_column_to_cell(row2, col2);
876                            let val_pos = QQWing::get_possibility_index(val_index, position);
877                            if col != col2 && self.possibilities[val_pos] == 0 {
878                                self.possibilities[val_pos] = round;
879                                done_something = true;
880                            }
881                        }
882                    }
883                    if done_something {
884                        if self.log_history || self.record_history {
885                            self.add_history_item(LogItem::new(
886                                round,
887                                LogType::ColumnBox,
888                                val_index + 1,
889                                col_start,
890                            ));
891                        }
892                        return true;
893                    }
894                }
895            }
896        }
897        false
898    }
899
900    fn row_box_reduction(&mut self, round: u8) -> bool {
901        debug!("row_box_reduction round: {}", round);
902        for val_index in 0..ROW_COL_SEC_SIZE {
903            for row in 0..ROW_COL_SEC_SIZE {
904                let row_start = row * 9;
905                let mut in_one_box = true;
906                let mut row_box = UNSET_VALUE;
907                for i in 0..GRID_SIZE {
908                    for j in 0..GRID_SIZE {
909                        let column = i * GRID_SIZE + j;
910                        let position = QQWing::row_column_to_cell(row, column);
911                        let val_pos = QQWing::get_possibility_index(val_index, position);
912                        if self.possibilities[val_pos] == 0 {
913                            if row_box == UNSET_VALUE || row_box == i {
914                                row_box = i;
915                            } else {
916                                in_one_box = false;
917                            }
918                        }
919                    }
920                }
921                if in_one_box && row_box != UNSET_VALUE {
922                    let mut done_something = false;
923                    let column = GRID_SIZE * row_box;
924                    let sec_start =
925                        QQWing::cell_to_section_start_cell(QQWing::row_column_to_cell(row, column));
926                    let sec_start_row = QQWing::cell_to_row(sec_start);
927                    let sec_start_col = QQWing::cell_to_column(sec_start);
928                    for i in 0..GRID_SIZE {
929                        for j in 0..GRID_SIZE {
930                            let row2 = sec_start_row + i;
931                            let col2 = sec_start_col + j;
932                            let position = QQWing::row_column_to_cell(row2, col2);
933                            let val_pos = QQWing::get_possibility_index(val_index, position);
934                            if row != row2 && self.possibilities[val_pos] == 0 {
935                                self.possibilities[val_pos] = round;
936                                done_something = true;
937                            }
938                        }
939                    }
940                    if done_something {
941                        if self.log_history || self.record_history {
942                            self.add_history_item(LogItem::new(
943                                round,
944                                LogType::RowBox,
945                                val_index + 1,
946                                row_start,
947                            ));
948                        }
949                        return true;
950                    }
951                }
952            }
953        }
954        false
955    }
956
957    fn pointing_row_reduction(&mut self, round: u8) -> bool {
958        debug!("pointing_row_reduction round: {}", round);
959        for val_index in 0..ROW_COL_SEC_SIZE {
960            for section in 0..ROW_COL_SEC_SIZE {
961                let sec_start = QQWing::section_to_first_cell(section);
962                let mut in_one_row = true;
963                let mut box_row = UNSET_VALUE;
964                for j in 0..GRID_SIZE {
965                    for i in 0..GRID_SIZE {
966                        let sec_val = sec_start + i + (ROW_COL_SEC_SIZE * j);
967                        let val_pos = QQWing::get_possibility_index(val_index, sec_val);
968                        if self.possibilities[val_pos] == 0 {
969                            if box_row == UNSET_VALUE || box_row == j {
970                                box_row = j;
971                            } else {
972                                in_one_row = false;
973                            }
974                        }
975                    }
976                }
977                if in_one_row && box_row != UNSET_VALUE {
978                    let mut done_something = false;
979                    let row = QQWing::cell_to_row(sec_start) + box_row;
980                    let row_start = row * 9;
981
982                    for i in 0..ROW_COL_SEC_SIZE {
983                        let position = row_start + i;
984                        let section2 = QQWing::cell_to_section(position);
985                        let val_pos = QQWing::get_possibility_index(val_index, position);
986                        if section != section2 && self.possibilities[val_pos] == 0 {
987                            self.possibilities[val_pos] = round;
988                            done_something = true;
989                        }
990                    }
991                    if done_something {
992                        if self.log_history || self.record_history {
993                            self.add_history_item(LogItem::new(
994                                round,
995                                LogType::PointingPairTripleRow,
996                                val_index + 1,
997                                row_start,
998                            ));
999                        }
1000                        return true;
1001                    }
1002                }
1003            }
1004        }
1005        false
1006    }
1007
1008    fn pointing_column_reduction(&mut self, round: u8) -> bool {
1009        debug!("pointing_column_reduction round: {}", round);
1010        for val_index in 0..ROW_COL_SEC_SIZE {
1011            for section in 0..ROW_COL_SEC_SIZE {
1012                let sec_start = QQWing::section_to_first_cell(section);
1013                let mut in_one_col = true;
1014                let mut box_col = UNSET_VALUE;
1015                for i in 0..GRID_SIZE {
1016                    for j in 0..GRID_SIZE {
1017                        let sec_val = sec_start + i + (ROW_COL_SEC_SIZE * j);
1018                        let val_pos = QQWing::get_possibility_index(val_index, sec_val);
1019                        if self.possibilities[val_pos] == 0 {
1020                            if box_col == UNSET_VALUE || box_col == i {
1021                                box_col = i;
1022                            } else {
1023                                in_one_col = false;
1024                            }
1025                        }
1026                    }
1027                }
1028                if in_one_col && box_col != UNSET_VALUE {
1029                    let mut done_something = false;
1030                    let col = QQWing::cell_to_column(sec_start) + box_col;
1031                    let col_start = col;
1032
1033                    for i in 0..ROW_COL_SEC_SIZE {
1034                        let position = col_start + (ROW_COL_SEC_SIZE * i);
1035                        let section2 = QQWing::cell_to_section(position);
1036                        let val_pos = QQWing::get_possibility_index(val_index, position);
1037                        if section != section2 && self.possibilities[val_pos] == 0 {
1038                            self.possibilities[val_pos] = round;
1039                            done_something = true;
1040                        }
1041                    }
1042                    if done_something {
1043                        if self.log_history || self.record_history {
1044                            self.add_history_item(LogItem::new(
1045                                round,
1046                                LogType::PointingPairTripleColumn,
1047                                val_index + 1,
1048                                col_start,
1049                            ));
1050                        }
1051                        return true;
1052                    }
1053                }
1054            }
1055        }
1056        false
1057    }
1058
1059    fn count_possibilities(&self, position: usize) -> u32 {
1060        let mut count = 0;
1061        for val_index in 0..ROW_COL_SEC_SIZE {
1062            let val_pos = QQWing::get_possibility_index(val_index, position);
1063            if self.possibilities[val_pos] == 0 {
1064                count += 1;
1065            }
1066        }
1067        count
1068    }
1069
1070    fn are_possibilities_same(&self, position1: usize, position2: usize) -> bool {
1071        for val_index in 0..ROW_COL_SEC_SIZE {
1072            let val_pos1 = QQWing::get_possibility_index(val_index, position1);
1073            let val_pos2 = QQWing::get_possibility_index(val_index, position2);
1074            if (self.possibilities[val_pos1] == 0 || self.possibilities[val_pos2] == 0)
1075                && (self.possibilities[val_pos1] != 0 || self.possibilities[val_pos2] != 0)
1076            {
1077                return false;
1078            }
1079        }
1080        true
1081    }
1082
1083    fn remove_possibilities_in_one_from_two(
1084        &mut self,
1085        position1: usize,
1086        position2: usize,
1087        round: u8,
1088    ) -> bool {
1089        let mut done_something = false;
1090        for val_index in 0..ROW_COL_SEC_SIZE {
1091            let val_pos1 = QQWing::get_possibility_index(val_index, position1);
1092            let val_pos2 = QQWing::get_possibility_index(val_index, position2);
1093
1094            if self.possibilities[val_pos1] == 0 && self.possibilities[val_pos2] == 0 {
1095                self.possibilities[val_pos2] = round;
1096                done_something = true;
1097            }
1098        }
1099        done_something
1100    }
1101
1102    fn hidden_pair_in_column(&mut self, round: u8) -> bool {
1103        debug!("hidden_pair_in_column round: {}", round);
1104        for column in 0..ROW_COL_SEC_SIZE {
1105            for val_index in 0..ROW_COL_SEC_SIZE {
1106                let mut r1 = UNSET_VALUE;
1107                let mut r2 = UNSET_VALUE;
1108                let mut val_count = 0;
1109                for row in 0..ROW_COL_SEC_SIZE {
1110                    let position = QQWing::row_column_to_cell(row, column);
1111                    let val_pos = QQWing::get_possibility_index(val_index, position);
1112                    if self.possibilities[val_pos] == 0 {
1113                        if r1 == UNSET_VALUE || r1 == row {
1114                            r1 = row;
1115                        } else if r2 == UNSET_VALUE || r2 == row {
1116                            r2 = row;
1117                        }
1118                        val_count += 1;
1119                    }
1120                }
1121                if val_count == 2 {
1122                    for val_index2 in (val_index + 1)..ROW_COL_SEC_SIZE {
1123                        let mut r3 = UNSET_VALUE;
1124                        let mut r4 = UNSET_VALUE;
1125                        let mut val_count2 = 0;
1126                        for row in 0..ROW_COL_SEC_SIZE {
1127                            let position = QQWing::row_column_to_cell(row, column);
1128                            let val_pos = QQWing::get_possibility_index(val_index2, position);
1129                            if self.possibilities[val_pos] == 0 {
1130                                if r3 == UNSET_VALUE || r3 == row {
1131                                    r3 = row;
1132                                } else if r4 == UNSET_VALUE || r4 == row {
1133                                    r4 = row;
1134                                }
1135                                val_count2 += 1;
1136                            }
1137                        }
1138                        if val_count2 == 2 && r1 == r3 && r2 == r4 {
1139                            let mut done_something = false;
1140                            for val_index3 in 0..ROW_COL_SEC_SIZE {
1141                                if val_index3 != val_index && val_index3 != val_index2 {
1142                                    let position1 = QQWing::row_column_to_cell(r1, column);
1143                                    let position2 = QQWing::row_column_to_cell(r2, column);
1144                                    let val_pos1 =
1145                                        QQWing::get_possibility_index(val_index3, position1);
1146                                    let val_pos2 =
1147                                        QQWing::get_possibility_index(val_index3, position2);
1148                                    if self.possibilities[val_pos1] == 0 {
1149                                        self.possibilities[val_pos1] = round;
1150                                        done_something = true;
1151                                    }
1152                                    if self.possibilities[val_pos2] == 0 {
1153                                        self.possibilities[val_pos2] = round;
1154                                        done_something = true;
1155                                    }
1156                                }
1157                            }
1158                            if done_something {
1159                                if self.log_history || self.record_history {
1160                                    self.add_history_item(LogItem::new(
1161                                        round,
1162                                        LogType::HiddenPairColumn,
1163                                        val_index + 1,
1164                                        QQWing::row_column_to_cell(r1, column),
1165                                    ));
1166                                }
1167                                return true;
1168                            }
1169                        }
1170                    }
1171                }
1172            }
1173        }
1174        false
1175    }
1176
1177    fn hidden_pair_in_section(&mut self, round: u8) -> bool {
1178        debug!("hidden_pair_in_section round: {}", round);
1179        for section in 0..ROW_COL_SEC_SIZE {
1180            for val_index in 0..ROW_COL_SEC_SIZE {
1181                let mut si1 = UNSET_VALUE;
1182                let mut si2 = UNSET_VALUE;
1183                let mut val_count = 0;
1184                for sec_ind in 0..ROW_COL_SEC_SIZE {
1185                    let position = QQWing::section_to_cell(section, sec_ind);
1186                    let val_pos = QQWing::get_possibility_index(val_index, position);
1187                    if self.possibilities[val_pos] == 0 {
1188                        if si1 == UNSET_VALUE || si1 == sec_ind {
1189                            si1 = sec_ind;
1190                        } else if si2 == UNSET_VALUE || si2 == sec_ind {
1191                            si2 = sec_ind;
1192                        }
1193                        val_count += 1;
1194                    }
1195                }
1196                if val_count == 2 {
1197                    for val_index2 in (val_index + 1)..ROW_COL_SEC_SIZE {
1198                        let mut si3 = UNSET_VALUE;
1199                        let mut si4 = UNSET_VALUE;
1200                        let mut val_count2 = 0;
1201                        for sec_ind in 0..ROW_COL_SEC_SIZE {
1202                            let position = QQWing::section_to_cell(section, sec_ind);
1203                            let val_pos = QQWing::get_possibility_index(val_index2, position);
1204                            if self.possibilities[val_pos] == 0 {
1205                                if si3 == UNSET_VALUE || si3 == sec_ind {
1206                                    si3 = sec_ind;
1207                                } else if si4 == UNSET_VALUE || si4 == sec_ind {
1208                                    si4 = sec_ind;
1209                                }
1210                                val_count2 += 1;
1211                            }
1212                        }
1213                        if val_count2 == 2 && si1 == si3 && si2 == si4 {
1214                            let mut done_something = false;
1215                            for val_index3 in 0..ROW_COL_SEC_SIZE {
1216                                if val_index3 != val_index && val_index3 != val_index2 {
1217                                    let position1 = QQWing::section_to_cell(section, si1);
1218                                    let position2 = QQWing::section_to_cell(section, si2);
1219                                    let val_pos1 =
1220                                        QQWing::get_possibility_index(val_index3, position1);
1221                                    let val_pos2 =
1222                                        QQWing::get_possibility_index(val_index3, position2);
1223                                    if self.possibilities[val_pos1] == 0 {
1224                                        self.possibilities[val_pos1] = round;
1225                                        done_something = true;
1226                                    }
1227                                    if self.possibilities[val_pos2] == 0 {
1228                                        self.possibilities[val_pos2] = round;
1229                                        done_something = true;
1230                                    }
1231                                }
1232                            }
1233                            if done_something {
1234                                if self.log_history || self.record_history {
1235                                    self.add_history_item(LogItem::new(
1236                                        round,
1237                                        LogType::HiddenPairSection,
1238                                        val_index + 1,
1239                                        QQWing::section_to_cell(section, si1),
1240                                    ));
1241                                }
1242                                return true;
1243                            }
1244                        }
1245                    }
1246                }
1247            }
1248        }
1249        false
1250    }
1251
1252    fn hidden_pair_in_row(&mut self, round: u8) -> bool {
1253        debug!("hidden_pair_in_row round: {}", round);
1254        for row in 0..ROW_COL_SEC_SIZE {
1255            for val_index in 0..ROW_COL_SEC_SIZE {
1256                let mut c1 = UNSET_VALUE;
1257                let mut c2 = UNSET_VALUE;
1258                let mut val_count = 0;
1259                for column in 0..ROW_COL_SEC_SIZE {
1260                    let position = QQWing::row_column_to_cell(row, column);
1261                    let val_pos = QQWing::get_possibility_index(val_index, position);
1262                    if self.possibilities[val_pos] == 0 {
1263                        if c1 == UNSET_VALUE || c1 == column {
1264                            c1 = column;
1265                        } else if c2 == UNSET_VALUE || c2 == column {
1266                            c2 = column;
1267                        }
1268                        val_count += 1;
1269                    }
1270                }
1271                if val_count == 2 {
1272                    for val_index2 in (val_index + 1)..ROW_COL_SEC_SIZE {
1273                        let mut c3 = UNSET_VALUE;
1274                        let mut c4 = UNSET_VALUE;
1275                        let mut val_count2 = 0;
1276                        for column in 0..ROW_COL_SEC_SIZE {
1277                            let position = QQWing::row_column_to_cell(row, column);
1278                            let val_pos = QQWing::get_possibility_index(val_index2, position);
1279                            if self.possibilities[val_pos] == 0 {
1280                                if c3 == UNSET_VALUE || c3 == column {
1281                                    c3 = column;
1282                                } else if c4 == UNSET_VALUE || c4 == column {
1283                                    c4 = column;
1284                                }
1285                                val_count2 += 1;
1286                            }
1287                        }
1288                        if val_count2 == 2 && c1 == c3 && c2 == c4 {
1289                            let mut done_something = false;
1290                            for val_index3 in 0..ROW_COL_SEC_SIZE {
1291                                if val_index3 != val_index && val_index3 != val_index2 {
1292                                    let position1 = QQWing::row_column_to_cell(row, c1);
1293                                    let position2 = QQWing::row_column_to_cell(row, c2);
1294                                    let val_pos1 =
1295                                        QQWing::get_possibility_index(val_index3, position1);
1296                                    let val_pos2 =
1297                                        QQWing::get_possibility_index(val_index3, position2);
1298                                    if self.possibilities[val_pos1] == 0 {
1299                                        self.possibilities[val_pos1] = round;
1300                                        done_something = true;
1301                                    }
1302                                    if self.possibilities[val_pos2] == 0 {
1303                                        self.possibilities[val_pos2] = round;
1304                                        done_something = true;
1305                                    }
1306                                }
1307                            }
1308                            if done_something {
1309                                if self.log_history || self.record_history {
1310                                    self.add_history_item(LogItem::new(
1311                                        round,
1312                                        LogType::HiddenPairRow,
1313                                        val_index + 1,
1314                                        QQWing::row_column_to_cell(row, c1),
1315                                    ));
1316                                }
1317                                return true;
1318                            }
1319                        }
1320                    }
1321                }
1322            }
1323        }
1324        false
1325    }
1326
1327    fn handle_naked_pairs(&mut self, round: u8) -> bool {
1328        debug!("handle_naked_pairs round: {}", round);
1329        for position in 0..BOARD_SIZE {
1330            let possibilities = self.count_possibilities(position);
1331            if possibilities == 2 {
1332                let row = QQWing::cell_to_row(position);
1333                let column = QQWing::cell_to_column(position);
1334                let section = QQWing::cell_to_section_start_cell(position);
1335                for position2 in position..BOARD_SIZE {
1336                    if position != position2 {
1337                        let possibilities2 = self.count_possibilities(position2);
1338                        if possibilities2 == 2 && self.are_possibilities_same(position, position2) {
1339                            if row == QQWing::cell_to_row(position2) {
1340                                let mut done_something = false;
1341                                for column2 in 0..ROW_COL_SEC_SIZE {
1342                                    let position3 = QQWing::row_column_to_cell(row, column2);
1343                                    if position3 != position
1344                                        && position3 != position2
1345                                        && self.remove_possibilities_in_one_from_two(
1346                                            position, position3, round,
1347                                        )
1348                                    {
1349                                        done_something = true;
1350                                    }
1351                                }
1352                                if done_something {
1353                                    if self.log_history || self.record_history {
1354                                        self.add_history_item(LogItem::new(
1355                                            round,
1356                                            LogType::NakedPairRow,
1357                                            0,
1358                                            position,
1359                                        ));
1360                                    }
1361                                    return true;
1362                                }
1363                            }
1364                            if column == QQWing::cell_to_column(position2) {
1365                                let mut done_something = false;
1366                                for row2 in 0..ROW_COL_SEC_SIZE {
1367                                    let position3 = QQWing::row_column_to_cell(row2, column);
1368                                    if position3 != position
1369                                        && position3 != position2
1370                                        && self.remove_possibilities_in_one_from_two(
1371                                            position, position3, round,
1372                                        )
1373                                    {
1374                                        done_something = true;
1375                                    }
1376                                }
1377                                if done_something {
1378                                    if self.log_history || self.record_history {
1379                                        self.add_history_item(LogItem::new(
1380                                            round,
1381                                            LogType::NakedPairColumn,
1382                                            0,
1383                                            position,
1384                                        ));
1385                                    }
1386                                    return true;
1387                                }
1388                            }
1389                            if section == QQWing::cell_to_section_start_cell(position2) {
1390                                let mut done_something = false;
1391                                let sec_start = QQWing::cell_to_section_start_cell(position);
1392                                for i in 0..GRID_SIZE {
1393                                    for j in 0..GRID_SIZE {
1394                                        let position3 = sec_start + i + (ROW_COL_SEC_SIZE * j);
1395                                        if position3 != position
1396                                            && position3 != position2
1397                                            && self.remove_possibilities_in_one_from_two(
1398                                                position, position3, round,
1399                                            )
1400                                        {
1401                                            done_something = true;
1402                                        }
1403                                    }
1404                                }
1405                                if done_something {
1406                                    if self.log_history || self.record_history {
1407                                        self.add_history_item(LogItem::new(
1408                                            round,
1409                                            LogType::NakedPairSection,
1410                                            0,
1411                                            position,
1412                                        ));
1413                                    }
1414                                    return true;
1415                                }
1416                            }
1417                        }
1418                    }
1419                }
1420            }
1421        }
1422        false
1423    }
1424
1425    /**
1426     * Mark exactly one cell which is the only possible value for some row, if
1427     * such a cell exists. This method will look in a row for a possibility that
1428     * is only listed for one cell. This type of cell is often called a
1429     * "hidden single"
1430     */
1431    fn only_value_in_row(&mut self, round: u8) -> bool {
1432        debug!("only_value_in_row round: {}", round);
1433        for row in 0..ROW_COL_SEC_SIZE {
1434            for val_index in 0..ROW_COL_SEC_SIZE {
1435                let mut count = 0;
1436                let mut last_position = 0;
1437                for col in 0..ROW_COL_SEC_SIZE {
1438                    let position = (row * ROW_COL_SEC_SIZE) + col;
1439                    let val_pos = QQWing::get_possibility_index(val_index, position);
1440                    if self.possibilities[val_pos] == 0 {
1441                        count += 1;
1442                        last_position = position;
1443                    }
1444                }
1445                if count == 1 {
1446                    let value = val_index + 1;
1447                    if self.log_history || self.record_history {
1448                        self.add_history_item(LogItem::new(
1449                            round,
1450                            LogType::HiddenSingleRow,
1451                            value,
1452                            last_position,
1453                        ));
1454                    }
1455                    let _ = self.mark(last_position, round, value as u8).unwrap();
1456                    return true;
1457                }
1458            }
1459        }
1460        false
1461    }
1462
1463    /**
1464     * Mark exactly one cell which is the only possible value for some column,
1465     * if such a cell exists. This method will look in a column for a
1466     * possibility that is only listed for one cell. This type of cell is often
1467     * called a "hidden single"
1468     */
1469    fn only_value_in_column(&mut self, round: u8) -> bool {
1470        debug!("only_value_in_column round: {}", round);
1471        for col in 0..ROW_COL_SEC_SIZE {
1472            for val_index in 0..ROW_COL_SEC_SIZE {
1473                let mut count = 0;
1474                let mut last_position = 0;
1475                for row in 0..ROW_COL_SEC_SIZE {
1476                    let position = QQWing::row_column_to_cell(row, col);
1477                    let val_pos = QQWing::get_possibility_index(val_index, position);
1478                    if self.possibilities[val_pos] == 0 {
1479                        count += 1;
1480                        last_position = position;
1481                    }
1482                }
1483                if count == 1 {
1484                    let value = val_index + 1;
1485                    if self.log_history || self.record_history {
1486                        self.add_history_item(LogItem::new(
1487                            round,
1488                            LogType::HiddenSingleColumn,
1489                            value,
1490                            last_position,
1491                        ));
1492                    }
1493                    let _ = self.mark(last_position, round, value as u8).unwrap();
1494                    return true;
1495                }
1496            }
1497        }
1498        false
1499    }
1500
1501    /**
1502     * Mark exactly one cell which is the only possible value for some section,
1503     * if such a cell exists. This method will look in a section for a
1504     * possibility that is only listed for one cell. This type of cell is often
1505     * called a "hidden single"
1506     */
1507    fn only_value_in_section(&mut self, round: u8) -> bool {
1508        debug!("only_value_in_section round: {}", round);
1509        for sec in 0..ROW_COL_SEC_SIZE {
1510            let sec_pos = QQWing::section_to_first_cell(sec);
1511            for val_index in 0..ROW_COL_SEC_SIZE {
1512                let mut count = 0;
1513                let mut last_position = 0;
1514                for i in 0..GRID_SIZE {
1515                    for j in 0..GRID_SIZE {
1516                        let position = sec_pos + i + ROW_COL_SEC_SIZE * j;
1517                        let val_pos = QQWing::get_possibility_index(val_index, position);
1518                        if self.possibilities[val_pos] == 0 {
1519                            count += 1;
1520                            last_position = position;
1521                        }
1522                    }
1523                }
1524                if count == 1 {
1525                    let value = val_index + 1;
1526                    if self.log_history || self.record_history {
1527                        self.add_history_item(LogItem::new(
1528                            round,
1529                            LogType::HiddenSingleSection,
1530                            value,
1531                            last_position,
1532                        ));
1533                    }
1534                    let _ = self.mark(last_position, round, value as u8).unwrap();
1535                    return true;
1536                }
1537            }
1538        }
1539        false
1540    }
1541
1542    /**
1543     * Mark exactly one cell that has a single possibility, if such a cell
1544     * exists. This method will look for a cell that has only one possibility.
1545     * This type of cell is often called a "single"
1546     */
1547    fn only_possibility_for_cell(&mut self, round: u8) -> bool {
1548        debug!("only_possibility_for_cell round: {}", round);
1549        for position in 0..BOARD_SIZE {
1550            if self.solution[position] == 0 {
1551                let mut count = 0;
1552                let mut last_value = 0;
1553                for val_index in 0..ROW_COL_SEC_SIZE {
1554                    let val_pos = QQWing::get_possibility_index(val_index, position);
1555                    if self.possibilities[val_pos] == 0 {
1556                        count += 1;
1557                        last_value = val_index + 1;
1558                    }
1559                }
1560                if count == 1 {
1561                    let _ = self.mark(position, round, last_value as u8).unwrap();
1562                    if self.log_history || self.record_history {
1563                        self.add_history_item(LogItem::new(
1564                            round,
1565                            LogType::Single,
1566                            last_value,
1567                            position,
1568                        ));
1569                    }
1570                    return true;
1571                }
1572            }
1573        }
1574        false
1575    }
1576
1577    /**
1578     * Mark the given value at the given position. Go through the row, column,
1579     * and section for the position and remove the value from the possibilities.
1580     *
1581     * @param position Position into the board (0-80)
1582     * @param round Round to mark for rollback purposes
1583     * @param value The value to go in the square at the given position
1584     */
1585    fn mark(&mut self, position: usize, round: u8, value: u8) -> Result<bool, QQWingError> {
1586        debug!(
1587            "Mark position: {}, round: {}, value: {}",
1588            position, round, value
1589        );
1590        if self.solution[position] != 0 {
1591            return Err(QQWingError::PositionAlreadyMarked);
1592        }
1593        if self.solution_round[position] != 0 {
1594            return Err(QQWingError::PositionAlreadyMarked);
1595        }
1596
1597        let val_index = value - 1;
1598        self.solution[position] = value;
1599
1600        let poss_ind = QQWing::get_possibility_index(val_index as usize, position);
1601        if self.possibilities[poss_ind] != 0 {
1602            return Err(QQWingError::PositionAlreadyMarked);
1603        }
1604
1605        // Take this value out of the possibilities for everything in the row
1606        self.solution_round[position] = round;
1607        let row_start = QQWing::cell_to_row(position) * ROW_COL_SEC_SIZE;
1608        for col in 0..ROW_COL_SEC_SIZE {
1609            let row_val = row_start + col;
1610            let val_pos = QQWing::get_possibility_index(val_index as usize, row_val);
1611            // System.out.println("Row Start: "+row_start+" Row Value: "+rowVal+" Value Position: "+val_pos);
1612            if self.possibilities[val_pos] == 0 {
1613                self.possibilities[val_pos] = round;
1614            }
1615        }
1616
1617        // Take this value out of the possibilities for everything in the column
1618        let col_start = QQWing::cell_to_column(position);
1619        for i in 0..ROW_COL_SEC_SIZE {
1620            let col_val = col_start + (ROW_COL_SEC_SIZE * i);
1621            let val_pos = QQWing::get_possibility_index(val_index as usize, col_val);
1622            // System.out.println("Col Start: "+col_start+" Col Value: "+colVal+" Value Position: "+val_pos);
1623            if self.possibilities[val_pos] == 0 {
1624                self.possibilities[val_pos] = round;
1625            }
1626        }
1627
1628        // Take this value out of the possibilities for everything in section
1629        let sec_start = QQWing::cell_to_section_start_cell(position);
1630        for i in 0..GRID_SIZE {
1631            for j in 0..GRID_SIZE {
1632                let sec_val = sec_start + i + (ROW_COL_SEC_SIZE * j);
1633                let val_pos = QQWing::get_possibility_index(val_index as usize, sec_val);
1634                // System.out.println("Sec Start: "+sec_start+" Sec Value: "+sec_val+" Value Position: "+val_pos);
1635                if self.possibilities[val_pos] == 0 {
1636                    self.possibilities[val_pos] = round;
1637                }
1638            }
1639        }
1640
1641        // This position itself is determined, it should have possibilities.
1642        for val_index in 0..ROW_COL_SEC_SIZE {
1643            let val_pos = QQWing::get_possibility_index(val_index as usize, position);
1644            if self.possibilities[val_pos] == 0 {
1645                self.possibilities[val_pos] = round;
1646            }
1647        }
1648        Ok(true)
1649    }
1650
1651    /**
1652     * print the given BOARD_SIZEd array of ints as a sudoku puzzle. Use print
1653     * options from member variables.
1654     */
1655    fn print(&self, sudoku: [u8; 81]) {
1656        println!("{}", self.puzzle_to_string(sudoku));
1657    }
1658
1659    fn puzzle_to_string(&self, sudoku: [u8; 81]) -> String {
1660        let mut sb = String::new();
1661        for i in 0..BOARD_SIZE {
1662            if self.print_style == PrintStyle::READABLE {
1663                sb.push_str(" ");
1664            }
1665            if sudoku[i] == 0 {
1666                sb.push_str(".");
1667            } else {
1668                sb.push_str(sudoku[i].to_string().as_str());
1669            }
1670            if i == BOARD_SIZE - 1 {
1671                if self.print_style == PrintStyle::CSV {
1672                    sb.push_str(",");
1673                } else {
1674                    sb.push_str(NL);
1675                }
1676                if self.print_style == PrintStyle::READABLE
1677                    || self.print_style == PrintStyle::COMPACT
1678                {
1679                    sb.push_str(NL);
1680                }
1681            } else if i % ROW_COL_SEC_SIZE == ROW_COL_SEC_SIZE - 1 {
1682                if self.print_style == PrintStyle::READABLE
1683                    || self.print_style == PrintStyle::COMPACT
1684                {
1685                    sb.push_str(NL);
1686                }
1687                if i % SEC_GROUP_SIZE == SEC_GROUP_SIZE - 1 {
1688                    if self.print_style == PrintStyle::READABLE {
1689                        sb.push_str("-------|-------|-------");
1690                        sb.push_str(NL);
1691                    }
1692                }
1693            } else if i % GRID_SIZE == GRID_SIZE - 1 {
1694                if self.print_style == PrintStyle::READABLE {
1695                    sb.push_str(" |");
1696                }
1697            }
1698        }
1699        sb
1700    }
1701
1702    /// Print any stats we were able to gather while solving the puzzle.
1703    pub fn get_stats(&self) -> String {
1704        let mut sb = String::new();
1705        let given_count = self.get_given_count();
1706        let single_count = self.get_single_count();
1707        let hidden_single_count = self.get_hidden_single_count();
1708        let naked_pair_count = self.get_naked_pair_count();
1709        let hidden_pair_count = self.get_hidden_pair_count();
1710        let pointing_pair_triple_count = self.get_pointing_pair_triple_count();
1711        let box_reduction_count = self.get_box_line_reduction_count();
1712        let guess_count = self.get_guess_count();
1713        let backtrack_count = self.get_backtrack_count();
1714        let difficulty_string = self.get_difficulty();
1715        if self.print_style == PrintStyle::CSV {
1716            sb.push_str(format!("{:?}", difficulty_string).as_str());
1717            sb.push_str(",");
1718            sb.push_str(given_count.to_string().as_str());
1719            sb.push_str(",");
1720            sb.push_str(single_count.to_string().as_str());
1721            sb.push_str(",");
1722            sb.push_str(hidden_single_count.to_string().as_str());
1723            sb.push_str(",");
1724            sb.push_str(naked_pair_count.to_string().as_str());
1725            sb.push_str(",");
1726            sb.push_str(hidden_pair_count.to_string().as_str());
1727            sb.push_str(",");
1728            sb.push_str(pointing_pair_triple_count.to_string().as_str());
1729            sb.push_str(",");
1730            sb.push_str(box_reduction_count.to_string().as_str());
1731            sb.push_str(",");
1732            sb.push_str(guess_count.to_string().as_str());
1733            sb.push_str(",");
1734            sb.push_str(backtrack_count.to_string().as_str());
1735            sb.push_str(",");
1736        } else {
1737            sb.push_str("Difficulty: ");
1738            sb.push_str(format!("{:?}", difficulty_string).as_str());
1739            sb.push_str(NL);
1740            sb.push_str("Number of Givens: ");
1741            sb.push_str(given_count.to_string().as_str());
1742            sb.push_str(NL);
1743            sb.push_str("Number of Singles: ");
1744            sb.push_str(single_count.to_string().as_str());
1745            sb.push_str(NL);
1746            sb.push_str("Number of Hidden Singles: ");
1747            sb.push_str(hidden_single_count.to_string().as_str());
1748            sb.push_str(NL);
1749            sb.push_str("Number of Naked Pairs: ");
1750            sb.push_str(naked_pair_count.to_string().as_str());
1751            sb.push_str(NL);
1752            sb.push_str("Number of Hidden Pairs: ");
1753            sb.push_str(hidden_pair_count.to_string().as_str());
1754            sb.push_str(NL);
1755            sb.push_str("Number of Pointing Pairs/Triples: ");
1756            sb.push_str(pointing_pair_triple_count.to_string().as_str());
1757            sb.push_str(NL);
1758            sb.push_str("Number of Box/Line Intersections: ");
1759            sb.push_str(box_reduction_count.to_string().as_str());
1760            sb.push_str(NL);
1761            sb.push_str("Number of Guesses: ");
1762            sb.push_str(guess_count.to_string().as_str());
1763            sb.push_str(NL);
1764            sb.push_str("Number of Backtracks: ");
1765            sb.push_str(backtrack_count.to_string().as_str());
1766            sb.push_str(NL);
1767        }
1768        sb
1769    }
1770
1771    /**
1772     * Print the sudoku puzzle.
1773     */
1774    pub fn print_puzzle(&self) {
1775        self.print(self.puzzle);
1776    }
1777
1778    /**
1779     * Given a vector of LogItems, determine how many log items in the vector
1780     * are of the specified type.
1781     */
1782    fn get_log_count(v: &Vec<LogItem>, logtype: LogType) -> usize {
1783        let mut count = 0;
1784        for i in 0..v.len() {
1785            if v[i].log_type == logtype {
1786                count += 1;
1787            }
1788        }
1789        return count;
1790    }
1791
1792    fn get_random_symmetry() -> Symmetry {
1793        let values = [
1794            Symmetry::NONE,
1795            Symmetry::ROTATE90,
1796            Symmetry::ROTATE180,
1797            Symmetry::MIRROR,
1798            Symmetry::FLIP,
1799            Symmetry::RANDOM,
1800        ];
1801        // not the first and last value which are NONE and RANDOM
1802        values[(random::<usize>() % (values.len() - 1)) + 1].clone()
1803    }
1804
1805    /**
1806     * Given a value for a cell (0-8) and a cell number (0-80) calculate the
1807     * offset into the possibility array (0-728).
1808     */
1809    pub(crate) fn get_possibility_index(value_index: usize, cell: usize) -> usize {
1810        value_index + (ROW_COL_SEC_SIZE * cell)
1811    }
1812
1813    /**
1814     * Given the index of a cell (0-80) calculate the row (0-8) in which it
1815     * resides.
1816     */
1817    pub(crate) fn cell_to_row(cell: usize) -> usize {
1818        cell / ROW_COL_SEC_SIZE
1819    }
1820
1821    /**
1822     * Given the index of a cell (0-80) calculate the column (0-8) in which that
1823     * cell resides.
1824     */
1825    pub(crate) fn cell_to_column(cell: usize) -> usize {
1826        cell % ROW_COL_SEC_SIZE
1827    }
1828
1829    /**
1830     * Given the index of a cell (0-80) calculate the section (0-8) in which it
1831     * resides.
1832     */
1833    pub(crate) fn cell_to_section(cell: usize) -> usize {
1834        (cell / SEC_GROUP_SIZE * GRID_SIZE) + (QQWing::cell_to_column(cell) / GRID_SIZE)
1835    }
1836
1837    /**
1838     * Given the index of a cell (0-80) calculate the cell (0-80) that is the
1839     * upper left start cell of that section.
1840     */
1841    pub(crate) fn cell_to_section_start_cell(cell: usize) -> usize {
1842        (cell / SEC_GROUP_SIZE * SEC_GROUP_SIZE)
1843            + (QQWing::cell_to_column(cell) / GRID_SIZE * GRID_SIZE)
1844    }
1845
1846    /**
1847     * Given a row (0-8) and a column (0-8) calculate the cell (0-80).
1848     */
1849    pub(crate) fn row_column_to_cell(row: usize, column: usize) -> usize {
1850        row * ROW_COL_SEC_SIZE + column
1851    }
1852
1853    /**
1854     * Given a section (0-8) calculate the first cell (0-80) of that section.
1855     */
1856    pub(crate) fn section_to_first_cell(section: usize) -> usize {
1857        (section % GRID_SIZE * GRID_SIZE) + (section / GRID_SIZE * SEC_GROUP_SIZE)
1858    }
1859
1860    /**
1861     * Given a section (0-8) and an offset into that section (0-8) calculate the
1862     * cell (0-80)
1863     */
1864    pub(crate) fn section_to_cell(section: usize, offset: usize) -> usize {
1865        QQWing::section_to_first_cell(section)
1866            + ((offset / GRID_SIZE) * ROW_COL_SEC_SIZE)
1867            + (offset % GRID_SIZE)
1868    }
1869}
1870
1871#[derive(Debug, PartialEq, Clone, EnumString, EnumIter)]
1872pub enum PrintStyle {
1873    ONELINE,
1874    COMPACT,
1875    READABLE,
1876    CSV,
1877}