1use 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
27pub mod difficulty;
29pub mod logitem;
31pub mod logtype;
33pub 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#[derive(Debug)]
56pub struct QQWing {
57 last_solve_round: u8,
61
62 puzzle: [u8; BOARD_SIZE],
68
69 solution: [u8; BOARD_SIZE],
74
75 solution_round: [u8; BOARD_SIZE],
80
81 possibilities: [u8; POSSIBILITY_SIZE],
90
91 random_board_array: [u8; BOARD_SIZE],
97
98 random_possibility_array: [u8; ROW_COL_SEC_SIZE],
103
104 record_history: bool,
108
109 log_history: bool,
113
114 solve_history: Vec<LogItem>,
119
120 solve_instructions: Vec<LogItem>,
126
127 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 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 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 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 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 fn get_single_count(&self) -> usize {
247 QQWing::get_log_count(&self.solve_instructions, LogType::Single)
248 }
249
250 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 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 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 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 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 fn get_guess_count(&self) -> usize {
303 QQWing::get_log_count(&self.solve_instructions, LogType::Guess)
304 }
305
306 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 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 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 self.shuffle_random_arrays();
350
351 self.solve();
356
357 if symmetry == Symmetry::NONE {
358 self.rollback_non_guesses();
363 }
364
365 for i in 0..BOARD_SIZE {
368 self.puzzle[i] = self.solution[i];
369 }
370
371 self.shuffle_random_arrays();
374
375 for i in 0..BOARD_SIZE {
380 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 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 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 self.reset();
455
456 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 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()); self.solve_instructions.push(l); }
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 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 pub fn has_no_solution(&mut self) -> bool {
599 self.count_solutions_limited() == 0
600 }
601
602 pub fn has_unique_solution(&mut self) -> bool {
607 self.count_solutions_limited() == 1
608 }
609
610 pub fn has_multiple_solutions(&mut self) -> bool {
615 self.count_solutions_limited() > 1
616 }
617
618 pub fn count_total_solutions(&mut self) -> u32 {
622 self.count_solutions(false)
623 }
624
625 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 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 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 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 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 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 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 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 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 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 if self.possibilities[val_pos] == 0 {
1613 self.possibilities[val_pos] = round;
1614 }
1615 }
1616
1617 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 if self.possibilities[val_pos] == 0 {
1624 self.possibilities[val_pos] = round;
1625 }
1626 }
1627
1628 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 if self.possibilities[val_pos] == 0 {
1636 self.possibilities[val_pos] = round;
1637 }
1638 }
1639 }
1640
1641 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 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 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 pub fn print_puzzle(&self) {
1775 self.print(self.puzzle);
1776 }
1777
1778 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 values[(random::<usize>() % (values.len() - 1)) + 1].clone()
1803 }
1804
1805 pub(crate) fn get_possibility_index(value_index: usize, cell: usize) -> usize {
1810 value_index + (ROW_COL_SEC_SIZE * cell)
1811 }
1812
1813 pub(crate) fn cell_to_row(cell: usize) -> usize {
1818 cell / ROW_COL_SEC_SIZE
1819 }
1820
1821 pub(crate) fn cell_to_column(cell: usize) -> usize {
1826 cell % ROW_COL_SEC_SIZE
1827 }
1828
1829 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 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 pub(crate) fn row_column_to_cell(row: usize, column: usize) -> usize {
1850 row * ROW_COL_SEC_SIZE + column
1851 }
1852
1853 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 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}