File indexing completed on 2024-04-28 04:04:44

0001 /****************************************************************************
0002  *    Copyright 2011  Ian Wadham <iandw.au@gmail.com>                       *
0003  *    Copyright 2006  David Bau <david bau @ gmail com> Original algorithms *
0004  *    Copyright 2015  Ian Wadham <iandw.au@gmail.com>                       *
0005  *                                                                          *
0006  *    This program is free software; you can redistribute it and/or         *
0007  *    modify it under the terms of the GNU General Public License as        *
0008  *    published by the Free Software Foundation; either version 2 of        *
0009  *    the License, or (at your option) any later version.                   *
0010  *                                                                          *
0011  *    This program is distributed in the hope that it will be useful,       *
0012  *    but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *    GNU General Public License for more details.                          *
0015  *                                                                          *
0016  *    You should have received a copy of the GNU General Public License     *
0017  *    along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0018  ****************************************************************************/
0019 
0020 #include "sudokuboard.h"
0021 
0022 #include "debug.h"
0023 #include "ksudoku_logging.h"
0024 
0025 #include "state.h"
0026 #include "mathdokugenerator.h"
0027 
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KStandardGuiItem>
0031 
0032 #include <QElapsedTimer>
0033 #include <QRandomGenerator>
0034 
0035 #include <cstdio>
0036 #include <ctime>
0037 
0038 SudokuBoard::SudokuBoard (SKGraph * graph)
0039     :
0040     m_type         (graph->specificType()),
0041     m_order        (graph->order()),
0042     m_blockSize    (graph->base()),
0043     m_boardSize    (0),
0044     m_boardArea    (graph->size()),
0045     m_overlap      (0),
0046     m_nGroups      (graph->cliqueCount()),
0047     m_groupSize    (m_order),
0048     m_graph        (graph),
0049     m_vacant       (VACANT),
0050     m_unusable     (UNUSABLE)
0051 {
0052     m_stats.type      = m_type;
0053     m_stats.blockSize = m_blockSize;
0054     m_stats.order     = m_order;
0055     m_boardSize       = graph->sizeX(); // TODO - IDW. Rationalise grid sizes.
0056     qCDebug(KSudokuLog) << "SudokuBoard: type " << m_type << graph->name()
0057                         << ", block " << m_blockSize << ", order " << m_order
0058                         << ", BoardArea " << m_boardArea;
0059 }
0060 
0061 #define SEEDED_RANDROMGENERATOR 0
0062 
0063 static QRandomGenerator* randomGenerator()
0064 {
0065 #if SEEDED_RANDROMGENERATOR
0066     static QRandomGenerator ourRandomGenerator;
0067     return &ourRandomGenerator;
0068 #else
0069     return QRandomGenerator::global();
0070 #endif
0071 }
0072 
0073 void SudokuBoard::setSeed()
0074 {
0075     static bool started = false;
0076     if (started) {
0077 #if SEEDED_RANDROMGENERATOR
0078         qCDebug(KSudokuLog) << "setSeed(): RESET IS TURNED ON";
0079         randomGenerator()->seed(m_stats.seed); // IDW test.
0080 #else
0081         qCDebug(KSudokuLog) << "setSeed(): RESET IS TURNED OFF";
0082 #endif
0083     }
0084     else {
0085         started = true;
0086         m_stats.seed = std::time(nullptr);
0087 #if SEEDED_RANDROMGENERATOR
0088         randomGenerator()->seed(m_stats.seed);
0089 #endif
0090         qCDebug(KSudokuLog) << "setSeed(): SEED = " << m_stats.seed;
0091     }
0092 }
0093 
0094 bool SudokuBoard::generatePuzzle             (BoardContents & puzzle,
0095                                               BoardContents & solution,
0096                                               Difficulty difficultyRequired,
0097                                               Symmetry symmetry)
0098 {
0099     qCDebug(KSudokuLog) << "Entered generatePuzzle(): difficulty "
0100                         << difficultyRequired << ", symmetry " << symmetry;
0101     setSeed();
0102 
0103     SudokuType puzzleType = m_graph->specificType();
0104     if ((puzzleType == Mathdoku) || (puzzleType == KillerSudoku)) {
0105     // Generate variants of Mathdoku (aka KenKen TM) or Killer Sudoku types.
0106     int maxTries = 10;
0107     int numTries = 0;
0108     bool success = false;
0109     while (true) {
0110         MathdokuGenerator mg (m_graph);
0111         // Find numbers to satisfy Sudoku rules: they will be the solution.
0112         solution = fillBoard();
0113         // Generate a Mathdoku or Killer Sudoku puzzle having this solution.
0114         numTries++;
0115         success = mg.generateMathdokuTypes (puzzle, solution,
0116                     &m_KSudokuMoves, difficultyRequired);
0117         if (success) {
0118         return true;
0119         }
0120         else if (numTries >= maxTries) {
0121         QWidget owner;
0122         if (KMessageBox::questionTwoActions (&owner,
0123                 i18n("Attempts to generate a puzzle failed after "
0124                  "about 200 tries. Try again?"),
0125                 i18n("Mathdoku or Killer Sudoku Puzzle"),
0126                 KGuiItem(i18n("&Try Again")), KStandardGuiItem::cancel())
0127                 == KMessageBox::SecondaryAction) {
0128             return false;   // Go back to the Welcome screen.
0129         }
0130         numTries = 0;       // Try again.
0131         }
0132     }
0133     }
0134     else {
0135     // Generate variants of Sudoku (2D) and Roxdoku (3D) types.
0136     return generateSudokuRoxdokuTypes (puzzle, solution,
0137                                     difficultyRequired, symmetry);
0138     }
0139 }
0140 
0141 bool SudokuBoard::generateSudokuRoxdokuTypes (BoardContents & puzzle,
0142                                               BoardContents & solution,
0143                                               Difficulty difficultyRequired,
0144                                               Symmetry symmetry)
0145 {
0146     const int     maxTries = 20;
0147     int           count = 0;
0148     float         bestRating = 0.0;
0149     int           bestDifficulty = 0;
0150     int           bestNClues = 0;
0151     int           bestNGuesses = 0;
0152     int           bestFirstGuessAt = 0;
0153     BoardContents currPuzzle;
0154     BoardContents currSolution;
0155 
0156     QElapsedTimer t;
0157     t.start();
0158     if (m_graph->sizeZ() > 1) {
0159     symmetry = NONE;        // Symmetry not implemented in 3-D.
0160     }
0161     if (symmetry == RANDOM_SYM) {   // Choose a symmetry at random.
0162         symmetry = (Symmetry) (randomGenerator()->bounded((int) LAST_CHOICE));
0163     }
0164     qCDebug(KSudokuLog) << "SYMMETRY IS" << symmetry;
0165     if (symmetry == DIAGONAL_1) {
0166     // If diagonal symmetry, choose between NW->SE and NE->SW diagonals.
0167         symmetry = (randomGenerator()->bounded(2) == 0) ? DIAGONAL_1 : DIAGONAL_2;
0168         qCDebug(KSudokuLog) << "Diagonal symmetry, choosing " <<
0169             ((symmetry == DIAGONAL_1) ? "DIAGONAL_1" : "DIAGONAL_2");
0170     }
0171 
0172     while (true) {
0173         // Fill the board with values that satisfy the Sudoku rules but are
0174         // chosen in a random way: these values are the solution of the puzzle.
0175         currSolution = this->fillBoard();
0176         qCDebug(KSudokuLog) << "Return from fillBoard() - time to fill board:"
0177                             << t.elapsed() << " msec";
0178 
0179         // Randomly insert solution-values into an empty board until a point is
0180         // reached where all the cells in the solution can be logically deduced.
0181         currPuzzle = insertValues (currSolution, difficultyRequired, symmetry);
0182         qCDebug(KSudokuLog) << "Return from insertValues() - duration:"
0183                             << t.elapsed() << " msec";
0184 
0185         if (difficultyRequired > m_stats.difficulty) {
0186             // Make the puzzle harder by removing values at random.
0187             currPuzzle = removeValues (currSolution, currPuzzle,
0188                                        difficultyRequired, symmetry);
0189             qCDebug(KSudokuLog) << "Return from removeValues() - duration:"
0190                                 << t.elapsed() << " msec";
0191         }
0192 
0193         Difficulty d = calculateRating (currPuzzle, 5);
0194         count++;
0195         qCInfo(KSudokuLog) <<  "CYCLE" << count << ", achieved difficulty" << d
0196                            << ", required" << difficultyRequired << ", rating"
0197                            << m_accum.rating;
0198 
0199     // Use the highest rated puzzle so far.
0200     if (m_accum.rating > bestRating) {
0201         bestRating       = m_accum.rating;
0202         bestDifficulty   = d;
0203         bestNClues       = m_stats.nClues;
0204         bestNGuesses     = m_accum.nGuesses;
0205         bestFirstGuessAt = m_stats.firstGuessAt;
0206         solution         = currSolution;
0207         puzzle           = currPuzzle;
0208     }
0209 
0210     // Express the rating to 1 decimal place in whatever locale we have.
0211     QString ratingStr = ki18n("%1").subs(bestRating, 0, 'f', 1).toString();
0212     // Check and explain the Sudoku/Roxdoku puzzle-generator's results.
0213     if ((d < difficultyRequired) && (count >= maxTries)) {
0214             // Exit after max attempts?
0215             QWidget owner;
0216             int ans = KMessageBox::questionTwoActions (&owner,
0217                       i18n("After %1 tries, the best difficulty level achieved by the generator "
0218                "is %2, with internal difficulty rating %3, but you "
0219                "requested difficulty level %4.\n"
0220                "\n"
0221                "Do you wish to let the generator try again or accept the puzzle as is?\n"
0222                "\n"
0223                "Hint: you can try to increase the difficulty rating by doing the following: "
0224                "Continue with the 'Accept' button, choose Game -> New, then change the Symmetry setting "
0225                "to 'No Symmetry' or some low symmetry type and then use 'Generate A Puzzle' again.",
0226                maxTries, bestDifficulty,
0227                ratingStr, difficultyRequired),
0228                       i18n("Difficulty Level"),
0229                       KGuiItem(i18n("&Try Again")), KGuiItem(i18n("&Accept")));
0230             if (ans == KMessageBox::PrimaryAction) {
0231                 count = 0;  // Continue on if the puzzle is not hard enough.
0232                 continue;
0233             }
0234             break;      // Exit if the puzzle is accepted.
0235     }
0236         if ((d >= difficultyRequired) || (count >= maxTries)) {
0237             QWidget owner;
0238         int ans = 0;
0239         if (m_accum.nGuesses == 0) {
0240                 ans = KMessageBox::questionTwoActions (&owner,
0241                i18n("It will be possible to solve the generated puzzle "
0242                 "by logic alone. No guessing will be required.\n"
0243                 "\n"
0244                 "The internal difficulty rating is %1. There are "
0245                 "%2 clues at the start and %3 moves to go.",
0246                 ratingStr, bestNClues,
0247                 (m_stats.nCells - bestNClues)),
0248                i18n("Difficulty Level"),
0249                        KStandardGuiItem::ok(), KGuiItem(i18n("&Retry")));
0250         }
0251         else {
0252                 QString avGuessStr = ki18n("%1").subs(((float) bestNGuesses) /
0253             5.0, 0, 'f', 1).toString(); // Format as for ratingStr.
0254                 ans = KMessageBox::questionTwoActions (&owner,
0255                i18n("Solving the generated puzzle will require an "
0256                 "average of %1 guesses or branch points and if you "
0257                 "guess wrong, backtracking will be necessary. The "
0258                 "first guess should come after %2 moves.\n"
0259                 "\n"
0260                 "The internal difficulty rating is %3, there are "
0261                 "%4 clues at the start and %5 moves to go.",
0262                 avGuessStr, bestFirstGuessAt, ratingStr,
0263                 bestNClues, (m_stats.nCells - bestNClues)),
0264                        i18n("Difficulty Level"),
0265                        KStandardGuiItem::ok(), KGuiItem(i18n("&Retry")));
0266         }
0267         // Exit when the required difficulty or number of tries is reached.
0268             if (ans == KMessageBox::SecondaryAction) {
0269                 count = 0;
0270                 bestRating = 0.0;
0271                 bestDifficulty = 0;
0272                 bestNClues = 0;
0273                 bestNGuesses = 0;
0274                 bestFirstGuessAt = 0;
0275                 continue;   // Start again if the user rejects this puzzle.
0276             }
0277         break;      // Exit if the puzzle is OK.
0278         }
0279     }
0280 
0281     qCDebug(KSudokuLog) << "FINAL PUZZLE" << puzzle;
0282 
0283     return true;
0284 }
0285 
0286 Difficulty SudokuBoard::calculateRating (const BoardContents & puzzle,
0287                                          int nSamples)
0288 {
0289     float avGuesses;
0290     float avDeduces;
0291     float avDeduced;
0292     float fracClues;
0293     m_accum.nSingles = m_accum.nSpots = m_accum.nGuesses = m_accum.nDeduces = 0;
0294     m_accum.rating   = 0.0;
0295 
0296     BoardContents solution;
0297     clear (solution);
0298     setSeed();
0299 
0300     for (int n = 0; n < nSamples; n++) {
0301         dbo1 "SOLVE PUZZLE %d\n", n);
0302         solution = solveBoard (puzzle, nSamples == 1 ? NotRandom : Random);
0303         dbo1 "PUZZLE SOLVED %d\n", n);
0304         analyseMoves (m_stats);
0305         fracClues = float (m_stats.nClues) / float (m_stats.nCells);
0306         m_accum.nSingles += m_stats.nSingles;
0307         m_accum.nSpots   += m_stats.nSpots;
0308         m_accum.nGuesses += m_stats.nGuesses;
0309         m_accum.nDeduces += m_stats.nDeduces;
0310         m_accum.rating   += m_stats.rating;
0311 
0312         avDeduced = float(m_stats.nSingles + m_stats.nSpots) / m_stats.nDeduces;
0313         dbo2 "  Type %2d %2d: clues %3d %3d %2.1f%% %3d moves   %3dP %3dS %3dG "
0314              "%3dM %3dD %3.1fR\n",
0315              m_stats.type, m_stats.order,
0316              m_stats.nClues, m_stats.nCells,
0317              fracClues * 100.0, (m_stats.nCells -  m_stats.nClues),
0318              m_stats.nSingles, m_stats.nSpots, m_stats.nGuesses,
0319              (m_stats.nSingles + m_stats.nSpots + m_stats.nGuesses),
0320              m_stats.nDeduces, m_stats.rating);
0321     }
0322 
0323     avGuesses = float (m_accum.nGuesses) / nSamples;
0324     avDeduces = float (m_accum.nDeduces) / nSamples;
0325     avDeduced = float (m_accum.nSingles + m_accum.nSpots) / m_accum.nDeduces;
0326     m_accum.rating = m_accum.rating / nSamples;
0327     m_accum.difficulty = calculateDifficulty (m_accum.rating);
0328     dbo1 "  Av guesses %2.1f  Av deduces %2.1f"
0329         "  Av per deduce %3.1f  rating %2.1f difficulty %d\n",
0330         avGuesses, avDeduces, avDeduced, m_accum.rating, m_accum.difficulty);
0331 
0332     return m_accum.difficulty;
0333 }
0334 
0335 int SudokuBoard::checkPuzzle (const BoardContents & puzzle,
0336                               const BoardContents & solution)
0337 {
0338     BoardContents answer = solveBoard (puzzle);
0339     if (answer.isEmpty()) {
0340         dbo1 "checkPuzzle: There is NO SOLUTION.\n");
0341         return -1;      // There is no solution.
0342     }
0343     if ((! solution.isEmpty()) && (answer != solution)) {
0344         dbo1 "checkPuzzle: The SOLUTION DIFFERS from the one supplied.\n");
0345         return -2;      // The solution differs from the one supplied.
0346     }
0347 
0348     analyseMoves (m_stats);
0349 
0350     answer.clear();
0351     answer = tryGuesses (Random);
0352     if (! answer.isEmpty()) {
0353         dbo1 "checkPuzzle: There is MORE THAN ONE SOLUTION.\n");
0354         return -3;      // There is more than one solution.
0355     }
0356 
0357     return calculateDifficulty (m_stats.rating);
0358 }
0359 
0360 void SudokuBoard::getMoveList (QList<int> & moveList)
0361 {
0362     moveList = m_KSudokuMoves;
0363 }
0364 
0365 BoardContents & SudokuBoard::solveBoard (const BoardContents & boardValues,
0366                                                GuessingMode gMode)
0367 {
0368     qCInfo(KSudokuLog) << "solveBoard()" << boardValues;
0369     m_currentValues = boardValues;
0370     return solve (gMode);
0371 }
0372 
0373 BoardContents & SudokuBoard::solve (GuessingMode gMode = Random)
0374 {
0375     // Eliminate any previous solver work.
0376     qDeleteAll (m_states);
0377     m_states.clear();
0378 
0379     m_moves.clear();
0380     m_moveTypes.clear();
0381     int nClues = 0;
0382     int nCells = 0;
0383     int value  = 0;
0384     for (int n = 0; n < m_boardArea; n++) {
0385         value = m_currentValues.at(n);
0386         if (value != m_unusable) {
0387             nCells++;
0388             if (value != m_vacant) {
0389                 nClues++;
0390             }
0391         }
0392     }
0393     m_stats.nClues = nClues;
0394     m_stats.nCells = nCells;
0395     dbo1 "STATS: CLUES %d, CELLS %d, PERCENT %.1f\n", nClues, nCells,
0396                                         nClues * 100.0 / float (nCells));
0397 
0398     // Attempt to deduce the solution in one hit.
0399     GuessesList g = deduceValues (m_currentValues, gMode);
0400     if (g.isEmpty()) {
0401         // The entire solution can be deduced by applying the Sudoku rules.
0402         dbo1 "NO GUESSES NEEDED, the solution can be entirely deduced.\n");
0403         return m_currentValues;
0404     }
0405 
0406     // We need to use a mix of guessing, deducing and backtracking.
0407     m_states.push (new State (this, g, 0,
0408                    m_currentValues, m_moves, m_moveTypes));
0409     return tryGuesses (gMode);
0410 }
0411 
0412 BoardContents & SudokuBoard::tryGuesses (GuessingMode gMode = Random)
0413 {
0414     while (m_states.count() > 0) {
0415         GuessesList guesses = m_states.top()->guesses();
0416         int n = m_states.top()->guessNumber();
0417         if ((n >= guesses.count()) || (guesses.at (0) == -1)) {
0418             dbo2 "POP: Out of guesses at level %" PRIdQSIZETYPE "\n", m_states.count());
0419             delete m_states.pop();
0420             if (!m_states.isEmpty()) {
0421                 m_moves.clear();
0422                 m_moveTypes.clear();
0423                 m_moves = m_states.top()->moves();
0424                 m_moveTypes = m_states.top()->moveTypes();
0425             }
0426             continue;
0427         }
0428         m_states.top()->setGuessNumber (n + 1);
0429         m_currentValues = m_states.top()->values();
0430         m_moves.append (guesses.at(n));
0431         m_moveTypes.append (Guess);
0432         m_currentValues [pairPos (guesses.at(n))] = pairVal (guesses.at(n));
0433         dbo2 "\nNEXT GUESS: level %" PRIdQSIZETYPE ", guess number %d\n",
0434                 m_states.count(), n);
0435         dbo2 "  Pick %d %d row %d col %d\n",
0436                 pairVal (guesses.at(n)), pairPos (guesses.at(n)),
0437                 pairPos (guesses.at(n))/m_boardSize + 1,
0438                 pairPos (guesses.at(n))%m_boardSize + 1);
0439 
0440         guesses = deduceValues (m_currentValues, gMode);
0441 
0442         if (guesses.isEmpty()) {
0443             // NOTE: We keep the stack of states.  It is needed by checkPuzzle()
0444         //       for the multiple-solutions test and deleted when its parent
0445         //       SudokuBoard object (i.e. this->) is deleted.
0446             return m_currentValues;
0447         }
0448         m_states.push (new State (this, guesses, 0,
0449                        m_currentValues, m_moves, m_moveTypes));
0450     }
0451 
0452     // No solution.
0453     m_currentValues.clear();
0454     return m_currentValues;
0455 }
0456 
0457 BoardContents SudokuBoard::insertValues (const BoardContents & solution,
0458                                          const Difficulty      required,
0459                                          const Symmetry        symmetry)
0460 {
0461     BoardContents puzzle;
0462     BoardContents filled;
0463     QList<int> sequence (m_boardArea);
0464     int cell  = 0;
0465     int value = 0;
0466 
0467     // Set up empty board areas.
0468     clear (puzzle);
0469     clear (filled);
0470 
0471     // Add cells in random order, but skip cells that can be deduced from them.
0472     dbo1 "Start INSERTING: %" PRIdQSIZETYPE " solution values\n", solution.count());
0473     randomSequence (sequence);
0474 
0475     int index = 0;
0476     for (int n = 0; n < m_boardArea; n++) {
0477         cell  = sequence.at (n);
0478         value = filled.at (cell);
0479         if (value == 0) {
0480             index = n;
0481             changeClues (puzzle, cell, symmetry, solution);
0482             changeClues (filled, cell, symmetry, solution);
0483 
0484             deduceValues (filled, Random /* NotRandom */);
0485             qCDebug(KSudokuLog) << "Puzzle:" << puzzle << "; filled" << filled;
0486         }
0487     }
0488     qCDebug(KSudokuLog) << "Puzzle:" << puzzle;
0489 
0490     while (true) {
0491         // Check the difficulty of the puzzle.
0492         solveBoard (puzzle);
0493         analyseMoves (m_stats);
0494         m_stats.difficulty = calculateDifficulty (m_stats.rating);
0495         if (m_stats.difficulty <= required) {
0496             break;  // The difficulty is as required or not enough yet.
0497         }
0498         // The puzzle needs to be made easier.  Add randomly-selected clues.
0499         for (int n = index; n < m_boardArea; n++) {
0500             cell  = sequence.at (n);
0501             if (puzzle.at (cell) == 0) {
0502                 changeClues (puzzle, cell, symmetry, solution);
0503                 index = n;
0504                 break;
0505             }
0506         }
0507         dbo1 "At index %d, added value %d, cell %d, row %d, col %d\n",
0508                 index, solution.at (cell),
0509                 cell, cell/m_boardSize + 1, cell%m_boardSize + 1);
0510     }
0511     qCDebug(KSudokuLog) << "Puzzle:" << puzzle;
0512     return puzzle;
0513 }
0514 
0515 BoardContents SudokuBoard::removeValues (const BoardContents & solution,
0516                                                BoardContents & puzzle,
0517                                          const Difficulty      required,
0518                                          const Symmetry        symmetry)
0519 {
0520     // Make the puzzle harder by removing values at random, making sure at each
0521     // step that the puzzle has a solution, the correct solution and only one
0522     // solution.  Stop when these conditions can no longer be met and the
0523     // required difficulty is reached or failed to be reached with the current
0524     // (random) selection of board values.
0525 
0526     // Remove values in random order, but put them back if the solution fails.
0527     BoardContents vacant;
0528     QList<int> sequence (m_boardArea);
0529     int          cell       = 0;
0530     int          value      = 0;
0531     QList<int>   tailOfRemoved;
0532 
0533     // No guesses until this much of the puzzle, including clues, is filled in.
0534     float        guessLimit = 0.6;
0535     int          noGuesses  = (int) (guessLimit * m_stats.nCells + 0.5);
0536     dbo1 "Guess limit = %.2f, nCells = %d, nClues = %d, noGuesses = %d\n",
0537             guessLimit, m_stats.nCells, m_stats.nClues, noGuesses);
0538 
0539     dbo1 "Start REMOVING:\n");
0540     randomSequence (sequence);
0541     clear (vacant);
0542 
0543     for (int n = 0; n < m_boardArea; n++) {
0544         cell  = sequence.at (n);
0545         value = puzzle.at (cell);
0546         if ((value == 0) || (value == m_unusable)) {
0547             continue;           // Skip empty or unusable cells.
0548         }
0549     // Try removing this clue and its symmetry partners (if any).
0550     changeClues (puzzle, cell, symmetry, vacant);
0551         dbo1 "ITERATION %d: Removed %d from cell %d\n", n, value, cell);
0552 
0553         // Check the solution is still OK and calculate the difficulty roughly.
0554         int result = checkPuzzle (puzzle, solution);
0555 
0556         // Do not force the human solver to start guessing too soon.
0557         if ((result >= 0) && (required != Unlimited) &&
0558             (m_stats.firstGuessAt <= (noGuesses - m_stats.nClues))) {
0559             dbo1 "removeValues: FIRST GUESS is too soon: move %d of %d.\n",
0560                     m_stats.firstGuessAt, m_stats.nCells - m_stats.nClues);
0561             result = -4;
0562         }
0563 
0564         // If the solution is not OK, replace the removed value(s).
0565         if (result < 0) {
0566             dbo1 "ITERATION %d: Replaced %d at cell %d, check returned %d\n",
0567                     n, value, cell, result);
0568         changeClues (puzzle, cell, symmetry, solution);
0569         }
0570 
0571         // If the solution is OK, check the difficulty (roughly).
0572         else {
0573             m_stats.difficulty = (Difficulty) result;
0574             dbo1 "CURRENT DIFFICULTY %d\n", m_stats.difficulty);
0575 
0576             if (m_stats.difficulty == required) {
0577                 // Save removed positions while the difficulty is as required.
0578                 tailOfRemoved.append (cell);
0579                 dbo1 "OVERSHOOT %" PRIdQSIZETYPE " at sequence %d\n",
0580                         tailOfRemoved.count(), n);
0581             }
0582 
0583             else if (m_stats.difficulty > required) {
0584                 // Finish if the required difficulty is exceeded.
0585                 qCInfo(KSudokuLog) << "Break on difficulty - replaced" << value
0586                                    << "at cell" << cell << ", overshoot is"
0587                                    << tailOfRemoved.count();
0588                 // Replace the value involved.
0589             changeClues (puzzle, cell, symmetry, solution);
0590                 break;
0591             }
0592         }
0593     }
0594 
0595     // If the required difficulty was reached and was not Unlimited, replace
0596     // half the saved values.
0597     //
0598     // This should avoid chance fluctuations in the calculated difficulty (when
0599     // the solution involves guessing) and provide a puzzle that is within the
0600     // required difficulty range.
0601     if ((required != Unlimited) && (tailOfRemoved.count() > 1)) {
0602         for (int k = 0; k < tailOfRemoved.count() / 2; k++) {
0603             cell = tailOfRemoved.takeLast();
0604             dbo1 "Replaced clue(s) for cell %d\n", cell);
0605             changeClues (puzzle, cell, symmetry, solution);
0606         }
0607     }
0608     return puzzle;
0609 }
0610 
0611 void SudokuBoard::analyseMoves (Statistics & s)
0612 {
0613     dbo1 "\nanalyseMoves()\n");
0614     s.nCells       = m_stats.nCells;
0615     s.nClues       = m_stats.nClues;
0616     s.firstGuessAt = s.nCells - s.nClues + 1;
0617 
0618     s.nSingles = s.nSpots = s.nDeduces = s.nGuesses = 0;
0619     m_KSudokuMoves.clear();
0620     Move m;
0621     Move mType;
0622     while (! m_moves.isEmpty()) {
0623         m = m_moves.takeFirst();
0624         mType = m_moveTypes.takeFirst();
0625     int val = pairVal(m);
0626     int pos = pairPos(m);
0627     int row = m_graph->cellPosY (pos);
0628     int col = m_graph->cellPosX (pos);
0629 
0630         switch (mType) {
0631         case Single:
0632             dbo2 "  Single Pick %d %d row %d col %d\n", val, pos, row+1, col+1);
0633         m_KSudokuMoves.append (pos);
0634             s.nSingles++;
0635             break;
0636         case Spot:
0637             dbo2 "  Single Spot %d %d row %d col %d\n", val, pos, row+1, col+1);
0638         m_KSudokuMoves.append (pos);
0639             s.nSpots++;
0640             break;
0641         case Deduce:
0642             dbo2 "Deduce: Iteration %d\n", m);
0643             s.nDeduces++;
0644             break;
0645         case Guess:
0646             dbo2 "GUESS:        %d %d row %d col %d\n", val, pos, row+1, col+1);
0647         m_KSudokuMoves.append (pos);
0648             if (s.nGuesses < 1) {
0649                 s.firstGuessAt = s.nSingles + s.nSpots + 1;
0650             }
0651             s.nGuesses++;
0652             break;
0653         case Wrong:
0654             dbo2 "WRONG GUESS:  %d %d row %d col %d\n", val, pos, row+1, col+1);
0655             break;
0656         case Result:
0657             break;
0658         }
0659     }
0660 
0661     // Calculate the empirical formula for the difficulty rating.  Note that
0662     // guess-points are effectively weighted by 3, because the deducer must
0663     // always iterate one more time to establish that a guess is needed.
0664     s.rating = 2 * s.nGuesses + s.nDeduces - (float(s.nClues)/s.nCells);
0665 
0666     // Calculate the difficulty level for empirical ranges of the rating.
0667     s.difficulty = calculateDifficulty (s.rating);
0668 
0669     dbo1 "  aM: Type %2d %2d: clues %3d %3d %2.1f%%   %3dP %3dS %3dG "
0670          "%3dM %3dD %3.1fR D=%d F=%d\n\n",
0671          m_stats.type, m_stats.order,
0672          s.nClues, s.nCells, ((float) s.nClues / s.nCells) * 100.0,
0673          s.nSingles, s.nSpots, s.nGuesses, (s.nSingles + s.nSpots + s.nGuesses),
0674          s.nDeduces, s.rating, s.difficulty, s.firstGuessAt);
0675 }
0676 
0677 Difficulty SudokuBoard::calculateDifficulty (float rating)
0678 {
0679     // These ranges of the rating were arrived at empirically by solving a few
0680     // dozen published puzzles and comparing SudokuBoard's rating value with the
0681     // description of difficulty given by the publisher, e.g. Diabolical or Evil
0682     // puzzles gave ratings in the range 10.0 to 20.0, so became Diabolical.
0683 
0684     Difficulty d = Unlimited;
0685 
0686     if (rating < 1.7) {
0687         d = VeryEasy;
0688     }
0689     else if (rating < 2.7) {
0690         d = Easy;
0691     }
0692     else if (rating < 4.6) {
0693         d = Medium;
0694     }
0695     else if (rating < 10.0) {
0696         d = Hard;
0697     }
0698     else if (rating < 20.0) {
0699         d = Diabolical;
0700     }
0701 
0702     return d;
0703 }
0704 
0705 void SudokuBoard::print (const BoardContents & boardValues)
0706 {
0707     // Used for test and debug, but the format is also parsable and loadable.
0708 
0709     char nLabels[] = "123456789";
0710     char aLabels[] = "abcdefghijklmnopqrstuvwxy";
0711     int index, value;
0712 
0713     if (boardValues.size() != m_boardArea) {
0714         printf ("Error: %" PRIdQSIZETYPE " board values to be printed, %d values required.\n\n",
0715             boardValues.size(), m_boardArea);
0716         return;
0717     }
0718 
0719     int depth = m_graph->sizeZ();   // If 2-D, depth == 1, else depth > 1.
0720     for (int k = 0; k < depth; k++) {
0721       int z = (depth > 1) ? (depth - k - 1) : k;
0722       for (int j = 0; j < m_graph->sizeY(); j++) {
0723     if ((j != 0) && (j % m_blockSize == 0)) {
0724         printf ("\n");      // Gap between square blocks.
0725     }
0726         int y = (depth > 1) ? (m_graph->sizeY() - j - 1) : j;
0727         for (int x = 0; x < m_graph->sizeX(); x++) {
0728             index = m_graph->cellIndex (x, y, z);
0729             value = boardValues.at (index);
0730             if (x % m_blockSize == 0) {
0731                 printf ("  ");      // Gap between square blocks.
0732             }
0733             if (value == m_unusable) {
0734                 printf (" '");      // Unused cell (e.g. in Samurai).
0735             }
0736             else if (value == 0) {
0737                 printf (" -");      // Empty cell (to be solved).
0738             }
0739             else {
0740                 value--;
0741                 char label = (m_order > 9) ? aLabels[value] : nLabels[value];
0742                 printf (" %c", label);  // Given cell (or clue).
0743             }
0744         }
0745         printf ("\n");          // End of row.
0746       }
0747       printf ("\n");            // Next Z or end of 2D puzzle/solution.
0748     }
0749 }
0750 
0751 GuessesList SudokuBoard::deduceValues (BoardContents & boardValues,
0752                                        GuessingMode gMode = Random)
0753 {
0754     int iteration = 0;
0755     setUpValueRequirements (boardValues);
0756     while (true) {
0757         iteration++;
0758         m_moves.append (iteration);
0759         m_moveTypes.append (Deduce);
0760         dbo2 "DEDUCE: Iteration %d\n", iteration);
0761         bool stuck = true;
0762         int  count = 0;
0763         GuessesList guesses;
0764 
0765         for (int cell = 0; cell < m_boardArea; cell++) {
0766             if (boardValues.at (cell) == m_vacant) {
0767                 GuessesList newGuesses;
0768                 qint32 numbers = m_validCellValues.at (cell);
0769                 dbo3 "Cell %d, valid numbers %03o\n", cell, numbers);
0770                 if (numbers == 0) {
0771                     dbo2 "SOLUTION FAILED: RETURN at cell %d\n", cell);
0772                     return solutionFailed (guesses);
0773                 }
0774                 int validNumber = 1;
0775                 while (numbers != 0) {
0776                     dbo3 "Numbers = %03o, validNumber = %d\n",
0777                             numbers, validNumber);
0778                     if (numbers & 1) {
0779                         newGuesses.append (setPair (cell, validNumber));
0780                     }
0781                     numbers = numbers >> 1;
0782                     validNumber++;
0783                 }
0784                 if (newGuesses.count() == 1) {
0785                     m_moves.append (newGuesses.first());
0786                     m_moveTypes.append (Single);
0787                     boardValues [cell] = pairVal (newGuesses.takeFirst());
0788                     dbo3 "  Single Pick %d %d row %d col %d\n",
0789                             boardValues.at (cell), cell,
0790                             cell/m_boardSize + 1, cell%m_boardSize + 1);
0791                     updateValueRequirements (boardValues, cell);
0792                     stuck = false;
0793                 }
0794                 else if (stuck) {
0795                     // Select a list of guesses.
0796                     if (guesses.isEmpty() ||
0797                         (newGuesses.count() < guesses.count())) {
0798                         guesses = newGuesses;
0799                         count = 1;
0800                     }
0801                     else if (newGuesses.count() > guesses.count()) {
0802                         ;
0803                     }
0804                     else if (gMode == Random) {
0805                         if (randomGenerator()->bounded(count) == 0) {
0806                             guesses = newGuesses;
0807                         }
0808                         count++;
0809                     }
0810                 }
0811             } // End if
0812         } // Next cell
0813 
0814         for (int group = 0; group < m_nGroups; group++) {
0815         QList<int> cellList = m_graph->clique (group);
0816             qint32 numbers = m_requiredGroupValues.at (group);
0817             dbo3 "Group %d, valid numbers %03o\n", group, numbers);
0818             if (numbers == 0) {
0819                 continue;
0820             }
0821             int    validNumber = 1;
0822             qint32 bit         = 1;
0823             int    cell        = 0;
0824             while (numbers != 0) {
0825                 if (numbers & 1) {
0826                     GuessesList newGuesses;
0827                     int index = group * m_groupSize;
0828                     for (int n = 0; n < m_groupSize; n++) {
0829             cell = cellList.at (n);
0830                         if ((m_validCellValues.at (cell) & bit) != 0) {
0831                             newGuesses.append (setPair (cell, validNumber));
0832                         }
0833                         index++;
0834                     }
0835                     if (newGuesses.isEmpty()) {
0836                         dbo2 "SOLUTION FAILED: RETURN at group %d\n", group);
0837                         return solutionFailed (guesses);
0838                     }
0839                     else if (newGuesses.count() == 1) {
0840                         m_moves.append (newGuesses.first());
0841                         m_moveTypes.append (Spot);
0842                         cell = pairPos (newGuesses.takeFirst());
0843                         boardValues [cell] = validNumber;
0844                         dbo3 "  Single Spot in Group %d value %d %d "
0845                                 "row %d col %d\n",
0846                                 group, validNumber, cell,
0847                                 cell/m_boardSize + 1, cell%m_boardSize + 1);
0848                         updateValueRequirements (boardValues, cell);
0849                         stuck = false;
0850                     }
0851                     else if (stuck) {
0852                         // Select a list of guesses.
0853                         if (guesses.isEmpty() ||
0854                             (newGuesses.count() < guesses.count())) {
0855                             guesses = newGuesses;
0856                             count = 1;
0857                         }
0858                         else if (newGuesses.count() > guesses.count()) {
0859                             ;
0860                         }
0861                         else if (gMode == Random){
0862                             if (randomGenerator()->bounded(count) == 0) {
0863                                 guesses = newGuesses;
0864                             }
0865                             count++;
0866                         }
0867                     }
0868                 } // End if (numbers & 1)
0869                 numbers = numbers >> 1;
0870                 bit     = bit << 1;
0871                 validNumber++;
0872             } // Next number
0873         } // Next group
0874 
0875         if (stuck) {
0876             GuessesList original = guesses;
0877             if (gMode == Random) {
0878                 // Shuffle the guesses.
0879                 QList<int> sequence (guesses.count());
0880                 randomSequence (sequence);
0881 
0882                 guesses.clear();
0883                 for (int i = 0; i < original.count(); i++) {
0884                     guesses.append (original.at (sequence.at (i)));
0885                 }
0886             }
0887             dbo2 "Guess    ");
0888             for (int i = 0; i < original.count(); i++) {
0889                 dbo3 "%d,%d ",
0890                         pairPos (original.at(i)), pairVal (original.at(i)));
0891             }
0892             dbo2 "\n");
0893             dbo2 "Shuffled ");
0894             for (int i = 0; i < guesses.count(); i++) {
0895                 dbo3 "%d,%d ",
0896                         pairPos (guesses.at (i)), pairVal (guesses.at(i)));
0897             }
0898             dbo2 "\n");
0899             return guesses;
0900         }
0901     } // End while (true)
0902 }
0903 
0904 GuessesList SudokuBoard::solutionFailed (GuessesList & guesses)
0905 {
0906     guesses.clear();
0907     guesses.append (-1);
0908     return guesses;
0909 }
0910 
0911 void SudokuBoard::clear (BoardContents & boardValues)
0912 {
0913     boardValues = m_graph->emptyBoard();    // Set cells vacant or unusable.
0914 }
0915 
0916 BoardContents & SudokuBoard::fillBoard()
0917 {
0918     // Solve the empty board, thus filling it with values at random.  These
0919     // values can be the starting point for generating a puzzle and also the
0920     // final solution of that puzzle.
0921 
0922     clear (m_currentValues);
0923 
0924     // Fill a central block with values 1 to m_order in random sequence.  This
0925     // reduces the solveBoard() time considerably, esp. if blockSize is 4 or 5.
0926     QList<int> sequence (m_order);
0927     QList<int> cellList = m_graph->clique (m_nGroups / 2);
0928     randomSequence (sequence);
0929     for (int n = 0; n < m_order; n++) {
0930         m_currentValues [cellList.at (n)] = sequence.at (n) + 1;
0931     }
0932 
0933     solveBoard (m_currentValues);
0934     dbo1 "BOARD FILLED\n");
0935     return m_currentValues;
0936 }
0937 
0938 void SudokuBoard::randomSequence (QList<int> & sequence)
0939 {
0940     if (sequence.isEmpty()) return;
0941 
0942     // Fill the vector with consecutive integers.
0943     int size = sequence.size();
0944     for (int i = 0; i < size; i++) {
0945         sequence [i] = i;
0946     }
0947 
0948     if (size == 1) return;
0949 
0950     // Shuffle the integers.
0951     int last = size;
0952     int z    = 0;
0953     int temp = 0;
0954     for (int i = 0; i < size; i++) {
0955         z = randomGenerator()->bounded(last);
0956         last--;
0957         temp            = sequence.at (z);
0958         sequence [z]    = sequence.at (last);
0959         sequence [last] = temp;
0960     }
0961 }
0962 
0963 void SudokuBoard::setUpValueRequirements (BoardContents & boardValues)
0964 {
0965     // Set a 1-bit for each possible cell-value in this order of Sudoku, for
0966     // example 9 bits for a 9x9 grid with 3x3 blocks.
0967     qint32 allValues = (1 << m_order) - 1;
0968 
0969     dbo2 "Enter setUpValueRequirements()\n");
0970     if (dbgLevel >= 2) {
0971         this->print (boardValues);
0972     }
0973 
0974     // Set bit-patterns to show what values each row, col or block needs.
0975     // The starting pattern is allValues, but bits are set to zero as the
0976     // corresponding values are supplied during puzzle generation and solving.
0977 
0978     m_requiredGroupValues.fill (0, m_nGroups);
0979     int    index = 0;
0980     qint32 bitPattern = 0;
0981     for (int group = 0; group < m_nGroups; group++) {
0982     dbo3 "Group %3d ", group);
0983     QList<int> cellList = m_graph->clique (group);
0984         bitPattern = 0;
0985         for (int n = 0; n < m_groupSize; n++) {
0986             int value = boardValues.at (cellList.at (n)) - 1;
0987             if (value != m_unusable) {
0988                 bitPattern |= (1 << value); // Add bit for each value found.
0989             }
0990         dbo3 "%3d=%2d ", cellList.at (n), value + 1);
0991             index++;
0992         }
0993         // Reverse all the bits, giving values currently not found in the group.
0994         m_requiredGroupValues [group] = bitPattern ^ allValues;
0995     dbo3 "bits %03o\n", m_requiredGroupValues.at (group));
0996     }
0997 
0998     // Set bit-patterns to show that each cell can accept any value.  Bits are
0999     // set to zero as possibilities for each cell are eliminated when solving.
1000     m_validCellValues.fill (allValues, m_boardArea);
1001     for (int i = 0; i < m_boardArea; i++) {
1002         if (boardValues.at (i) == m_unusable) {
1003             // No values are allowed in unusable cells (e.g. in Samurai type).
1004             m_validCellValues [i] = 0;
1005         }
1006         if (boardValues.at (i) != m_vacant) {
1007             // Cell is already filled in.
1008             m_validCellValues [i] = 0;
1009         }
1010     }
1011 
1012     // Now, for each cell, retain bits for values that are required by every
1013     // group to which that cell belongs.  For example, if the row already has 1,
1014     // 2, 3, the column has 3, 4, 5, 6 and the block has 6, 9, then the cell
1015     // can only have 7 or 8, with bit value 192.
1016     index = 0;
1017     for (int group = 0; group < m_nGroups; group++) {
1018     QList<int> cellList = m_graph->clique (group);
1019         for (int n = 0; n < m_order; n++) {
1020         int cell = cellList.at (n);
1021             m_validCellValues [cell] &= m_requiredGroupValues.at (group);
1022             index++;
1023         }
1024     }
1025     dbo2 "Finished setUpValueRequirements()\n");
1026 
1027     dbo3 "allowed:\n");
1028     for (int i = 0; i < m_boardArea; i++) {
1029          dbo3 "'%03o', ", m_validCellValues.at (i));
1030         if ((i + 1) % m_boardSize == 0) dbo3 "\n");
1031     }
1032     dbo3 "needed:\n");
1033     for (int group = 0; group < m_nGroups; group++) {
1034         dbo3 "'%03o', ", m_requiredGroupValues.at (group));
1035         if ((group + 1) % m_order == 0) dbo3 "\n");
1036     }
1037     dbo3 "\n");
1038 }
1039 
1040 void SudokuBoard::updateValueRequirements (BoardContents & boardValues, int cell)
1041 {
1042     // Set a 1-bit for each possible cell-value in this order of Sudoku.
1043     qint32 allValues  = (1 << m_order) - 1;
1044     // Set a complement-mask for this cell's new value.
1045     qint32 bitPattern = (1 << (boardValues.at (cell) - 1)) ^ allValues;
1046     // Show that this cell no longer requires values: it has been filled.
1047     m_validCellValues [cell] = 0;
1048 
1049     // Update the requirements for each group to which this cell belongs.
1050     const QList<int> groupList = m_graph->cliqueList(cell);
1051     for (int group : groupList) {
1052         m_requiredGroupValues [group] &= bitPattern;
1053 
1054     QList<int> cellList = m_graph->clique (group);
1055         for (int n = 0; n < m_order; n++) {
1056         int cell = cellList.at (n);
1057             m_validCellValues [cell] &= bitPattern;
1058         }
1059     }
1060 }
1061 
1062 void SudokuBoard::changeClues (BoardContents & to, int cell, Symmetry type,
1063                                const BoardContents & from)
1064 {
1065     int nSymm = 1;
1066     int indices[8];
1067     nSymm = getSymmetricIndices (m_boardSize, type, cell, indices);
1068     for (int k = 0; k < nSymm; k++) {
1069         cell = indices [k];
1070         to [cell] = from.at (cell);
1071     }
1072 }
1073 
1074 int SudokuBoard::getSymmetricIndices
1075                 (int size, Symmetry type, int index, int * out)
1076 {
1077     int result = 1;
1078     out[0]     = index;
1079     if (type == NONE) {
1080     return result;
1081     }
1082 
1083     int row    = m_graph->cellPosY (index);
1084     int col    = m_graph->cellPosX (index);
1085     int lr     = size - col - 1;        // For left-to-right reflection.
1086     int tb     = size - row - 1;        // For top-to-bottom reflection.
1087 
1088     switch (type) {
1089         case DIAGONAL_1:
1090         // Reflect a copy of the point around two central axes making its
1091         // reflection in the NW-SE diagonal the same as for NE-SW diagonal.
1092             row = tb;
1093             col = lr;
1094             // No break; fall through to case DIAGONAL_2.
1095             [[fallthrough]];
1096         case DIAGONAL_2:
1097         // Reflect (col, row) in the main NW-SE diagonal by swapping coords.
1098             out[1] = m_graph->cellIndex(row, col);
1099             result = (out[1] == out[0]) ? 1 : 2;
1100             break;
1101         case CENTRAL:
1102             out[1] = (size * size) - index - 1;
1103             result = (out[1] == out[0]) ? 1 : 2;
1104             break;
1105     case SPIRAL:
1106         if ((size % 2 != 1) || (row != col) || (col != (size - 1)/2)) {
1107         result = 4;         // This is not the central cell.
1108         out[1] = m_graph->cellIndex(lr,  tb);
1109         out[2] = m_graph->cellIndex(row, lr);
1110         out[3] = m_graph->cellIndex(tb,  col);
1111         }
1112             break;
1113         case FOURWAY:
1114         out[1] = m_graph->cellIndex(row, col);  // Interchange X and Y.
1115         out[2] = m_graph->cellIndex(lr,  row);  // Left-to-right.
1116         out[3] = m_graph->cellIndex(row, lr);   // Interchange X and Y.
1117         out[4] = m_graph->cellIndex(col, tb);   // Top-to-bottom.
1118         out[5] = m_graph->cellIndex(tb,  col);  // Interchange X and Y.
1119         out[6] = m_graph->cellIndex(lr,  tb);   // Both L-R and T-B.
1120         out[7] = m_graph->cellIndex(tb,  lr);   // Interchange X and Y.
1121 
1122         int k;
1123         for (int n = 1; n < 8; n++) {
1124         for (k = 0; k < result; k++) {
1125             if (out[n] == out[k]) {
1126             break;              // Omit duplicates.
1127             }
1128         }
1129         if (k >= result) {
1130             out[result] = out[n];
1131             result++;               // Use unique positions.
1132         }
1133         }
1134             break;
1135         case LEFT_RIGHT:
1136         out[1] = m_graph->cellIndex(lr,  row);
1137             result = (out[1] == out[0]) ? 1 : 2;
1138             break;
1139         default:
1140             break;
1141     }
1142     return result;
1143 }
1144 
1145 #include "moc_sudokuboard.cpp"