/* * qqwing - Sudoku solver and generator * Copyright (C) 2006-2014 Stephen Ostermiller http://ostermiller.org/ * Copyright (C) 2007 Jacques Bensimon (jacques@ipm.com) * Copyright (C) 2011 Jean Guillerez (j.guillerez - orange.fr) * Copyright (C) 2014 Michael Catanzaro (mcatanzaro@gnome.org) * * 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. * * 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. * * 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. */ #include "config.h" #include #include #include "qqwing.hpp" namespace qqwing { string getVersion(){ return VERSION; } /** * While solving the puzzle, log steps taken in a log item. * This is useful for later printing out the solve history * or gathering statistics about how hard the puzzle was to * solve. */ class LogItem { public: enum LogType { GIVEN, SINGLE, HIDDEN_SINGLE_ROW, HIDDEN_SINGLE_COLUMN, HIDDEN_SINGLE_SECTION, GUESS, ROLLBACK, NAKED_PAIR_ROW, NAKED_PAIR_COLUMN, NAKED_PAIR_SECTION, POINTING_PAIR_TRIPLE_ROW, POINTING_PAIR_TRIPLE_COLUMN, ROW_BOX, COLUMN_BOX, HIDDEN_PAIR_ROW, HIDDEN_PAIR_COLUMN, HIDDEN_PAIR_SECTION }; LogItem(int round, LogType type); LogItem(int round, LogType type, int value, int position); int getRound(); void print(); LogType getType(); ~LogItem(); private: void init(int round, LogType type, int value, int position); /** * The recursion level at which this item was gathered. * Used for backing out log items solve branches that * don't lead to a solution. */ int round; /** * The type of log message that will determine the * message printed. */ LogType type; /** * Value that was set by the operation (or zero for no value) */ int value; /** * position on the board at which the value (if any) was set. */ int position; }; void shuffleArray(int* array, int size); SudokuBoard::Symmetry getRandomSymmetry(); int getLogCount(vector* v, LogItem::LogType type); static inline int cellToColumn(int cell); static inline int cellToRow(int cell); static inline int cellToSectionStartCell(int cell); static inline int cellToSection(int cell); static inline int rowToFirstCell(int row); static inline int columnToFirstCell(int column); static inline int sectionToFirstCell(int section); static inline int getPossibilityIndex(int valueIndex, int cell); static inline int rowColumnToCell(int row, int column); static inline int sectionToCell(int section, int offset); /** * Create a new Sudoku board */ SudokuBoard::SudokuBoard() : puzzle ( new int[BOARD_SIZE] ), solution ( new int[BOARD_SIZE] ), solutionRound ( new int[BOARD_SIZE] ), possibilities ( new int[POSSIBILITY_SIZE] ), randomBoardArray ( new int[BOARD_SIZE] ), randomPossibilityArray ( new int[ROW_COL_SEC_SIZE] ), recordHistory ( false ), logHistory( false ), solveHistory ( new vector() ), solveInstructions ( new vector() ), printStyle ( READABLE ), lastSolveRound (0) { {for (int i=0; isize(); i++){ delete solveHistory->at(i); }} solveHistory->clear(); solveInstructions->clear(); int round = 1; for (int position=0; position 0){ int valIndex = puzzle[position]-1; int valPos = getPossibilityIndex(valIndex,position); int value = puzzle[position]; if (possibilities[valPos] != 0) return false; mark(position,round,value); if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::GIVEN, value, position)); } } return true; } /** * Get the difficulty rating. */ SudokuBoard::Difficulty SudokuBoard::getDifficulty(){ if (getGuessCount() > 0) return SudokuBoard::EXPERT; if (getBoxLineReductionCount() > 0) return SudokuBoard::INTERMEDIATE; if (getPointingPairTripleCount() > 0) return SudokuBoard::INTERMEDIATE; if (getHiddenPairCount() > 0) return SudokuBoard::INTERMEDIATE; if (getNakedPairCount() > 0) return SudokuBoard::INTERMEDIATE; if (getHiddenSingleCount() > 0) return SudokuBoard::EASY; if (getSingleCount() > 0) return SudokuBoard::SIMPLE; return SudokuBoard::UNKNOWN; } /** * Get the difficulty rating. */ string SudokuBoard::getDifficultyAsString(){ SudokuBoard::Difficulty difficulty = getDifficulty(); switch (difficulty){ case SudokuBoard::EXPERT: return "Expert"; case SudokuBoard::INTERMEDIATE: return "Intermediate"; case SudokuBoard::EASY: return "Easy"; case SudokuBoard::SIMPLE: return "Simple"; default: return "Unknown"; } } /** * Get the number of cells for which the solution was determined * because there was only one possible value for that cell. */ int SudokuBoard::getSingleCount(){ return getLogCount(solveInstructions, LogItem::SINGLE); } /** * Get the number of cells for which the solution was determined * because that cell had the only possibility for some value in * the row, column, or section. */ int SudokuBoard::getHiddenSingleCount(){ return getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_ROW) + getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_COLUMN) + getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_SECTION); } /** * Get the number of naked pair reductions that were performed * in solving this puzzle. */ int SudokuBoard::getNakedPairCount(){ return getLogCount(solveInstructions, LogItem::NAKED_PAIR_ROW) + getLogCount(solveInstructions, LogItem::NAKED_PAIR_COLUMN) + getLogCount(solveInstructions, LogItem::NAKED_PAIR_SECTION); } /** * Get the number of hidden pair reductions that were performed * in solving this puzzle. */ int SudokuBoard::getHiddenPairCount(){ return getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_ROW) + getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_COLUMN) + getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_SECTION); } /** * Get the number of pointing pair/triple reductions that were performed * in solving this puzzle. */ int SudokuBoard::getPointingPairTripleCount(){ return getLogCount(solveInstructions, LogItem::POINTING_PAIR_TRIPLE_ROW)+ getLogCount(solveInstructions, LogItem::POINTING_PAIR_TRIPLE_COLUMN); } /** * Get the number of box/line reductions that were performed * in solving this puzzle. */ int SudokuBoard::getBoxLineReductionCount(){ return getLogCount(solveInstructions, LogItem::ROW_BOX)+ getLogCount(solveInstructions, LogItem::COLUMN_BOX); } /** * Get the number lucky guesses in solving this puzzle. */ int SudokuBoard::getGuessCount(){ return getLogCount(solveInstructions, LogItem::GUESS); } /** * Get the number of backtracks (unlucky guesses) required * when solving this puzzle. */ int SudokuBoard::getBacktrackCount(){ return getLogCount(solveHistory, LogItem::ROLLBACK); } void SudokuBoard::shuffleRandomArrays(){ shuffleArray(randomBoardArray, BOARD_SIZE); shuffleArray(randomPossibilityArray, ROW_COL_SEC_SIZE); } void SudokuBoard::clearPuzzle(){ // Clear any existing puzzle {for (int i=0; i 0){ int positionsym1 = -1; int positionsym2 = -1; int positionsym3 = -1; switch (symmetry){ case ROTATE90: positionsym2 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToColumn(position),cellToRow(position)); positionsym3 = rowColumnToCell(cellToColumn(position),ROW_COL_SEC_SIZE-1-cellToRow(position)); case ROTATE180: positionsym1 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToRow(position),ROW_COL_SEC_SIZE-1-cellToColumn(position)); break; case MIRROR: positionsym1 = rowColumnToCell(cellToRow(position),ROW_COL_SEC_SIZE-1-cellToColumn(position)); break; case FLIP: positionsym1 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToRow(position),cellToColumn(position)); break; case RANDOM: // NOTE: Should never happen break; case NONE: // NOTE: No need to do anything break; } // try backing out the value and // counting solutions to the puzzle int savedValue = puzzle[position]; puzzle[position] = 0; int savedSym1 = 0; if (positionsym1 >= 0){ savedSym1 = puzzle[positionsym1]; puzzle[positionsym1] = 0; } int savedSym2 = 0; if (positionsym2 >= 0){ savedSym2 = puzzle[positionsym2]; puzzle[positionsym2] = 0; } int savedSym3 = 0; if (positionsym3 >= 0){ savedSym3 = puzzle[positionsym3]; puzzle[positionsym3] = 0; } reset(); if (countSolutions(2, true) > 1){ // Put it back in, it is needed puzzle[position] = savedValue; if (positionsym1 >= 0 && savedSym1 != 0) puzzle[positionsym1] = savedSym1; if (positionsym2 >= 0 && savedSym2 != 0) puzzle[positionsym2] = savedSym2; if (positionsym3 >= 0 && savedSym3 != 0) puzzle[positionsym3] = savedSym3; } } }} // Clear all solution info, leaving just the puzzle. reset(); // Restore recording history. setRecordHistory(recHistory); setLogHistory(lHistory); return true; } void SudokuBoard::rollbackNonGuesses(){ // Guesses are odd rounds // Non-guesses are even rounds {for (int i=2; i<=lastSolveRound; i+=2){ rollbackRound(i); }} } void SudokuBoard::setPrintStyle(PrintStyle ps){ printStyle = ps; } void SudokuBoard::setRecordHistory(bool recHistory){ recordHistory = recHistory; } void SudokuBoard::setLogHistory(bool logHist){ logHistory = logHist; } void SudokuBoard::addHistoryItem(LogItem* l){ if (logHistory){ l->print(); cout << endl; } if (recordHistory){ solveHistory->push_back(l); solveInstructions->push_back(l); } else { delete l; } } void SudokuBoard::printHistory(vector* v){ if (!recordHistory){ cout << "History was not recorded."; if (printStyle == CSV){ cout << " -- "; } else { cout << endl; } } {for (unsigned int i=0;isize();i++){ cout << i+1 << ". "; v->at(i)->print(); if (printStyle == CSV){ cout << " -- "; } else { cout << endl; } }} if (printStyle == CSV){ cout << ","; } else { cout << endl; } } void SudokuBoard::printSolveInstructions(){ if (isSolved()){ printHistory(solveInstructions); } else { cout << "No solve instructions - Puzzle is not possible to solve." << endl; } } void SudokuBoard::printSolveHistory(){ printHistory(solveHistory); } bool SudokuBoard::solve(){ reset(); shuffleRandomArrays(); return solve(2); } bool SudokuBoard::solve(int round){ lastSolveRound = round; while (singleSolveMove(round)){ if (isSolved()) return true; if (isImpossible()) return false; } int nextGuessRound = round+1; int nextRound = round+2; for (int guessNumber=0; guess(nextGuessRound, guessNumber); guessNumber++){ if (isImpossible() || !solve(nextRound)){ rollbackRound(nextRound); rollbackRound(nextGuessRound); } else { return true; } } return false; } bool SudokuBoard::hasUniqueSolution(){ return countSolutionsLimited() == 1; } int SudokuBoard::countSolutions(){ return countSolutions(false); } int SudokuBoard::countSolutionsLimited(){ return countSolutions(true); } int SudokuBoard::countSolutions(bool limitToTwo){ // Don't record history while generating. bool recHistory = recordHistory; setRecordHistory(false); bool lHistory = logHistory; setLogHistory(false); reset(); int solutionCount = countSolutions(2, limitToTwo); // Restore recording history. setRecordHistory(recHistory); setLogHistory(lHistory); return solutionCount; } int SudokuBoard::countSolutions(int round, bool limitToTwo){ while (singleSolveMove(round)){ if (isSolved()){ rollbackRound(round); return 1; } if (isImpossible()){ rollbackRound(round); return 0; } } int solutions = 0; int nextRound = round+1; for (int guessNumber=0; guess(nextRound, guessNumber); guessNumber++){ solutions += countSolutions(nextRound, limitToTwo); if (limitToTwo && solutions >=2){ rollbackRound(round); return solutions; } } rollbackRound(round); return solutions; } void SudokuBoard::rollbackRound(int round){ if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::ROLLBACK)); {for (int i=0; isize() > 0 && solveInstructions->back()->getRound() == round){ solveInstructions->pop_back(); } } bool SudokuBoard::isSolved(){ {for (int i=0; i