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"