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

0001 /****************************************************************************
0002  *    Copyright 2015  Ian Wadham <iandw.au@gmail.com>                       *
0003  *                                                                          *
0004  *    This program is free software; you can redistribute it and/or         *
0005  *    modify it under the terms of the GNU General Public License as        *
0006  *    published by the Free Software Foundation; either version 2 of        *
0007  *    the License, or (at your option) any later version.                   *
0008  *                                                                          *
0009  *    This program is distributed in the hope that it will be useful,       *
0010  *    but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *    GNU General Public License for more details.                          *
0013  *                                                                          *
0014  *    You should have received a copy of the GNU General Public License     *
0015  *    along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0016  ****************************************************************************/
0017 
0018 #include "cagegenerator.h"
0019 #include "ksudoku_logging.h"
0020 #include "skgraph.h"
0021 #include "dlxsolver.h"
0022 
0023 #include <QRandomGenerator>
0024 
0025 // #define MATHDOKU_LOG
0026 
0027 CageGenerator::CageGenerator (const BoardContents & solution)
0028     :
0029     mSolution (solution)
0030 {
0031     mMinSingles = 2;
0032     mMaxSingles = 4;
0033 
0034     mDLXSolver = new DLXSolver (this);  // Auto-deleted by QObject.
0035     mPossibilities = new QList<int>();
0036     mPossibilitiesIndex = new QList<int>();
0037 }
0038 
0039 CageGenerator::~CageGenerator()
0040 {
0041     mPossibilities->clear();
0042     mPossibilitiesIndex->clear();
0043     delete mPossibilities;
0044     delete mPossibilitiesIndex;
0045 }
0046 
0047 int CageGenerator::makeCages (SKGraph * graph, QList<int> * solutionMoves,
0048                               int maxSize, int maxValue,
0049                               bool hideOperators, int maxCombos)
0050 {
0051     Q_UNUSED(maxValue);
0052     // TODO - Use maxValue when OK'ing cages(?).
0053     // TODO - Experiment with mMinSingles and mMaxSingles. Make them parameters?
0054 
0055     QList<int> saveUnusedCells;
0056     QList<int> saveNeighbourFlags;
0057 #ifdef MATHDOKU_LOG
0058     QList<char> usedCells;
0059 #endif
0060 
0061     mMaxCombos = maxCombos;
0062     init (graph, hideOperators);
0063     graph->clearCages();
0064 
0065     mPossibilities->clear();
0066     mPossibilitiesIndex->clear();
0067     mPossibilitiesIndex->append(0);
0068     mSingles = 0;
0069 
0070 #ifdef MATHDOKU_LOG
0071     usedCells.fill ('-', mBoardArea);
0072     qCDebug(KSudokuLog) << "USED CELLS     " << usedCells;
0073 #endif
0074 
0075     QRandomGenerator *randomGenerator = QRandomGenerator::global();
0076 
0077     // TODO - Will probably need to limit the number of size-1 cages and maybe
0078     //        guarantee a minimum number as well.
0079     // TODO - Might need to generate at least one maximum-size cage first..
0080 
0081     /*
0082     1. Select the starting point and size for a cage.
0083 
0084        Top priority for a starting point is a cell that is surrounded on three
0085        or four sides, otherwise the cell is chosen at random from the list of
0086        unused cells.s
0087 
0088        A cell surrounded on four sides must become a single-cell cage, with a
0089        pre-determined value and no operator. Choosing a cell surrounded on three
0090        sides allows the cage to occupy and grow out of tight corners, avoiding
0091        an excess of small and single-cell cages.
0092 
0093        The chosen size is initially 1 (needed to control the difficulty of the
0094        puzzle) and later a random number from 2 to the maximum cage-size. The
0095        maximum cage-size also affects difficulty.
0096 
0097     2. Use the function makeOneCage() to make a cage of the required size.
0098 
0099        The makeOneCage() function keeps adding unused neighbouring cells until
0100        the required size is reached or it runs out of space to grow the cage
0101        further. It updates the lists of used cells and neighbours as it goes.
0102        A neighbour that would otherwise become surrounded on all four sides
0103        is always added to the cage as it grows, but normally the next cell is
0104        chosen randomly.
0105 
0106     3. Use the function setCageTarget() to choose an operator (+*-/), calculate
0107        the cage's value from the cell-values in the puzzle's solution and find
0108        all the possible combinations of values that cells in the cage *might*
0109        have, as seen by the user.
0110 
0111        The possible combinations are used when solving the generated puzzle,
0112        using the DLX algorithm, to check that the puzzle has a unique solution.
0113        Many generated puzzles have multiple solutions and have to be discarded.
0114 
0115     4. Validate the cage, using function isCageOK().
0116 
0117        A cage can be rejected if it might make the puzzle too hard or too easy.
0118        If so, discard the cage, back up and repeat steps 1 to 4.
0119 
0120     5. Repeat steps 1 to 4 until all cells have been assigned to cages.
0121     */
0122     int numFailures = 0;
0123     int maxFailures = 20;
0124 
0125     while (mUnusedCells.count() > 0) {
0126     QList<int>   cage;
0127     CageOperator cageOperator;
0128     int          cageValue;
0129     int          chosenSize;
0130     int          index = -1;
0131     for (const int n : std::as_const(mUnusedCells)) {
0132         switch (mNeighbourFlags.at (n)){
0133         case 7:
0134         case 11:
0135         case 13:
0136         case 14:
0137         index = n;      // Enclosed on three sides: start here.
0138         chosenSize = randomGenerator->bounded(maxSize - 1) + 2;
0139 #ifdef MATHDOKU_LOG
0140         qCDebug(KSudokuLog) << "CHOSE CUL-DE-SAC" << mNeighbourFlags.at (n)
0141                << "at" << index;
0142 #endif
0143         break;
0144         case 15:
0145         index = n;      // Isolated cell: size 1 is forced.
0146         chosenSize = 1;
0147 #ifdef MATHDOKU_LOG
0148         qCDebug(KSudokuLog) << "CHOSE ISOLATED" << mNeighbourFlags.at (n)
0149                << "at" << index;
0150 #endif
0151         break;
0152         default:
0153         break;
0154         }
0155         if (index >= 0) {
0156         break;
0157         }
0158     }
0159     if (index < 0) {        // Pick a starting cell at random.
0160         index = mUnusedCells.at (randomGenerator->bounded(mUnusedCells.count()));
0161         if (mSingles < mMinSingles) {
0162         chosenSize = 1;             // Start with size 1.
0163         mSingles++;
0164 #ifdef MATHDOKU_LOG
0165         qCDebug(KSudokuLog) << "INITIAL SINGLE" << mSingles;
0166 #endif
0167         }
0168         else {
0169         chosenSize = randomGenerator->bounded(maxSize - 1) + 2; // Then avoid size 1.
0170         }
0171     }
0172 #ifdef MATHDOKU_LOG
0173     qCDebug(KSudokuLog) << "chosenSize" << chosenSize << "cell" << index;
0174 #endif
0175 
0176     saveUnusedCells = mUnusedCells;
0177     saveNeighbourFlags = mNeighbourFlags;
0178 #ifdef MATHDOKU_LOG
0179     qCDebug(KSudokuLog) << "CALL makeOneCage: index" << index << "size" << chosenSize;
0180 #endif
0181 
0182     cage = makeOneCage (index, chosenSize);
0183     setCageTarget (cage, cageOperator, cageValue);
0184     if (! cageIsOK (cage, cageOperator, cageValue)) {
0185         mUnusedCells = saveUnusedCells;
0186         mNeighbourFlags = saveNeighbourFlags;
0187 #ifdef MATHDOKU_LOG
0188         qCDebug(KSudokuLog) << "CAGE IS NOT OK - unused" << mUnusedCells;
0189 #endif
0190         numFailures++;
0191         if (numFailures >= maxFailures) {
0192         qCDebug(KSudokuLog) << "makeOneCage() HAD" << numFailures << "failures"
0193                  << "maximum is" << maxFailures;
0194         return 0;   // Too many problems with making cages.
0195         }
0196         continue;
0197     }
0198 
0199     // The cage is OK: add it to the puzzle's layout.
0200     mGraph->addCage (cage, cageOperator, cageValue);
0201 
0202 #ifdef MATHDOKU_LOG
0203     qCDebug(KSudokuLog) << "CAGE" << mGraph->cageCount() << cage;
0204     char tag = 'a' + mGraph->cageCount() - 1;
0205     for (const int cell : std::as_const(cage)) {
0206         usedCells[cell] = tag;
0207     }
0208     qCDebug(KSudokuLog) << "LAYOUT" << tag << usedCells;
0209     for (int row = 0; row < mOrder; row++) {
0210         for (int col = 0; col < mOrder; col++) {
0211         fprintf (stderr, "%c ", usedCells.at (col * mOrder + row));
0212         }
0213         fprintf (stderr, "\n");
0214     }
0215     fprintf (stderr, "\n");
0216 #endif
0217     QList<int> flagsList;
0218     for (const int cell : std::as_const(mUnusedCells)) {
0219         flagsList.append (mNeighbourFlags.at (cell));
0220     }
0221 #ifdef MATHDOKU_LOG
0222     qCDebug(KSudokuLog) << "FLAGS" << flagsList;
0223     qCDebug(KSudokuLog) << "UNUSED" << mUnusedCells;
0224 #endif
0225     }
0226     int nCages = mPossibilitiesIndex->count() - 1;
0227 #ifdef MATHDOKU_LOG
0228     qCDebug(KSudokuLog) << "Cages in mPossibilitiesIndex" << nCages
0229              << "generated cages" << mGraph->cageCount();
0230 #endif
0231     int totCombos = 0;
0232     for (int n = 0; n < nCages; n++) {
0233     int nVals = mPossibilitiesIndex->at (n+1) - mPossibilitiesIndex->at (n);
0234     int size = mGraph->cage (n).size();
0235     int nCombos =  nVals / size;
0236 #ifdef MATHDOKU_LOG
0237     qCDebug(KSudokuLog) << "Cage" << n << "values" << nVals << "size" << size
0238                  << "combos" << nCombos << "target" << mGraph->cageValue(n)
0239          << "op" << mGraph->cageOperator(n)
0240          << "topleft" << mGraph->cageTopLeft(n);
0241 #endif
0242     totCombos += nCombos;
0243     }
0244 #ifdef MATHDOKU_LOG
0245     qCDebug(KSudokuLog) << "TOTAL COMBOS" << totCombos;
0246 #endif
0247 
0248     // Use the DLX solver to check if this puzzle has a unique solution.
0249     int nSolutions = mDLXSolver->solveMathdoku (mGraph, solutionMoves,
0250                                                 mPossibilities,
0251                                                 mPossibilitiesIndex, 2);
0252     if (nSolutions == 0) {
0253 #ifdef MATHDOKU_LOG
0254     qCDebug(KSudokuLog) << "FAILED TO FIND A SOLUTION: nSolutions =" << nSolutions;
0255 #endif
0256     return -1;      // At least one solution must exist.
0257     }
0258     else if (nSolutions > 1) {
0259 #ifdef MATHDOKU_LOG
0260     qCDebug(KSudokuLog) << "NO UNIQUE SOLUTION: nSolutions =" << nSolutions;
0261 #endif
0262     return -1;      // There must only one solution.
0263     }
0264 
0265 #ifdef MATHDOKU_LOG
0266     qCDebug(KSudokuLog) << "UNIQUE SOLUTION FOUND: nSolutions =" << nSolutions;
0267 #endif
0268     return mGraph->cageCount();
0269 }
0270 
0271 int CageGenerator::checkPuzzle (SKGraph * graph, BoardContents & solution,
0272                                 QList<int> * solutionMoves, bool hideOperators)
0273 {
0274 #ifdef MATHDOKU_LOG
0275     qCDebug(KSudokuLog) << "CageGenerator::checkPuzzle(): nCAGES" << graph->cageCount();
0276 #endif
0277 
0278     int result       = 0;
0279     mGraph           = graph;
0280     mOrder           = graph->order();
0281     mBoardArea       = mOrder * mOrder;
0282     mKillerSudoku    = (graph->specificType() == KillerSudoku);
0283     // Only Mathdoku puzzles can have hidden operators as part of the *puzzle*.
0284     // KillerSudoku has + or NoOp and makes the +'s hidden only in the *view*.
0285     mHiddenOperators = mKillerSudoku ? false : hideOperators;
0286     qCDebug(KSudokuLog) << "CHECK PUZZLE: HIDDEN OPERATORS" << mHiddenOperators;
0287 
0288     mPossibilities->clear();
0289     mPossibilitiesIndex->clear();
0290     mPossibilitiesIndex->append(0);
0291 
0292     int nCages = graph->cageCount();
0293     for (int n = 0; n < nCages; n++) {
0294     // Add all the possibilities for each cage.
0295 #ifdef MATHDOKU_LOG
0296     qCDebug(KSudokuLog) << "SET ALL Possibilities: cage size" << graph->cage(n).size()
0297              << "operator" << graph->cageOperator(n) << "value"
0298          << graph->cageValue(n) << "cage" << graph->cage(n);
0299 #endif
0300     setAllPossibilities (graph->cage(n), graph->cage(n).size(),
0301                              graph->cageOperator(n), graph->cageValue(n));
0302         mPossibilitiesIndex->append (mPossibilities->size());
0303 #ifdef MATHDOKU_LOG
0304     int nVals = mPossibilitiesIndex->at (n+1) - mPossibilitiesIndex->at (n);
0305     int size = mGraph->cage (n).size();
0306     int nCombos =  nVals / size;
0307     qCDebug(KSudokuLog) << "Cage" << n << "values" << nVals << "size" << size
0308                  << "combos" << nCombos << "target" << mGraph->cageValue(n)
0309          << "op" << mGraph->cageOperator(n)
0310          << "topleft" << mGraph->cageTopLeft(n);
0311 #endif
0312     }
0313 #ifdef MATHDOKU_LOG
0314     qCDebug(KSudokuLog) << "POSSIBILITIES SET: now check-solve the puzzle.";
0315     qCDebug(KSudokuLog) << "INDEX" << (*mPossibilitiesIndex);
0316     qCDebug(KSudokuLog) << "POSS:" << (*mPossibilities);
0317 #endif
0318     // Use the DLX solver to check if this puzzle has a unique solution.
0319     result = mDLXSolver->solveMathdoku (graph, solutionMoves,
0320                                                mPossibilities,
0321                                                mPossibilitiesIndex, 2);
0322     if (result == 1) {
0323     // If there is a unique solution, retrieve it from the solver.
0324     mDLXSolver->retrieveSolution (solution);
0325     }
0326     return result;
0327 }
0328 
0329 QList<int> CageGenerator::makeOneCage (int seedCell, int requiredSize)
0330 {
0331     QList<int>   cage;
0332     QList<int>   unusedNeighbours;
0333     const int    direction[4] = {N, E, S, W};
0334     const int    increment[4] = {-1, +mOrder, +1, -mOrder};
0335     const int    opposite[4]  = {S, W, N, E};
0336     int          index = seedCell;
0337 
0338     QRandomGenerator *randomGenerator = QRandomGenerator::global();
0339 
0340     cage.clear();
0341     unusedNeighbours.clear();
0342     unusedNeighbours.append (index);
0343 
0344     while (true) {
0345     // Update the chosen cell's neighbours.
0346     int flags = mNeighbourFlags.at (index);
0347     for (int k = 0; k < 4; k++) {
0348         if (flags & direction[k]) {
0349         continue;       // Already flagged.
0350         }
0351         int nb = index + increment[k];
0352         mNeighbourFlags[nb] = mNeighbourFlags[nb] | opposite[k];
0353         if (mUnusedCells.indexOf (nb) >= 0) {
0354         unusedNeighbours.append (nb);
0355         }
0356     }
0357 #ifdef MATHDOKU_LOG
0358     qCDebug(KSudokuLog) << "index" << index << "NEIGHBOURS" << unusedNeighbours;
0359 #endif
0360     mUnusedCells.removeAt (mUnusedCells.indexOf (index));
0361     mNeighbourFlags[index] = TAKEN;
0362     cage.append (index);
0363     if (cage.size() >= requiredSize) {
0364         break;
0365     }
0366 
0367     int unb = unusedNeighbours.indexOf (index);
0368     while (unb >= 0) {
0369         unusedNeighbours.removeAt (unb);
0370         unb = unusedNeighbours.indexOf (index);
0371     }
0372     if (unusedNeighbours.isEmpty()) {
0373         break;
0374     }
0375 
0376     // Pick a neighbour to be added to the cage.
0377     index = -1;
0378     for (const int unb : std::as_const(unusedNeighbours)) {
0379             flags = mNeighbourFlags.at (unb);
0380         if (flags == 15) {
0381         // Choose a cell that has been surrounded and isolated.
0382         index = unb;
0383         break;
0384         }
0385     }
0386     if (index < 0) {
0387         // Otherwise, choose a neighbouring cell at random.
0388         unb = randomGenerator->bounded(unusedNeighbours.count());
0389         index = unusedNeighbours.at (unb);
0390     }
0391     }
0392     return cage;
0393 }
0394 
0395 void CageGenerator::setCageTarget (const QList<int> &cage,
0396                                    CageOperator & cageOperator,
0397                                    int & cageValue)
0398 {
0399     CageOperator op;
0400     int value = 0;
0401     int size  = cage.size();
0402     QList<int> digits;
0403     digits.fill (0, size);
0404 #ifdef MATHDOKU_LOG
0405     qCDebug(KSudokuLog) << "CAGE SIZE" << size << "CONTENTS" << cage;
0406 #endif
0407     for (int n = 0; n < size; n++) {
0408     int digit = mSolution.at (cage.at(n));
0409     digits[n] = digit;
0410 #ifdef MATHDOKU_LOG
0411     qCDebug(KSudokuLog) << "Add cell" << cage.at(n)
0412          << "value" << mSolution.at (cage.at(n))
0413          << (n + 1) << "cells"; // : total" << value;
0414 #endif
0415     }
0416     if (size == 1) {
0417     cageOperator = NoOperator;
0418     cageValue = digits[0];
0419 #ifdef MATHDOKU_LOG
0420     qCDebug(KSudokuLog) << "SINGLE CELL: #" << mSingles << "val" << cageValue;
0421 #endif
0422     return;
0423     }
0424 
0425     int lo = 0;
0426     int hi = 0;
0427     if (mKillerSudoku) {
0428     // Killer Sudoku has an Add operator for every calculated cage.
0429     op = Add;
0430     }
0431     else {
0432     // Mathdoku has a randomly chosen operator for each calculated cage.
0433     int weights[4]      = {50, 30, 15, 15};
0434     CageOperator ops[4] = {Divide, Subtract, Multiply, Add};
0435     if (size != 2) {
0436         weights[0] = weights[1] = 0;
0437     }
0438     else {
0439         lo = qMin (digits[0], digits[1]);
0440         hi = qMax (digits[0], digits[1]);
0441         weights[0] = ((hi % lo) == 0) ? 50 : 0;
0442     }
0443 
0444     int roll = QRandomGenerator::global()->bounded(weights[0]+weights[1]+weights[2]+weights[3]);
0445 #ifdef MATHDOKU_LOG
0446     int wTotal = (weights[0]+weights[1]+weights[2]+weights[3]);
0447     qCDebug(KSudokuLog) << "ROLL" << roll << "VERSUS" << wTotal << "WEIGHTS"
0448          << weights[0] << weights[1] << weights[2] << weights[3];
0449 #endif
0450     int n = 0;
0451     while (n < 4) {
0452         roll = roll - weights[n];
0453         if (roll < 0) {
0454         break;
0455         }
0456         n++;
0457     }
0458     op = ops[n];
0459     }
0460 
0461     switch (op) {
0462     case Divide:
0463     value = hi / lo;
0464     break;
0465     case Subtract:
0466     value = hi - lo;
0467     break;
0468     case Multiply:
0469     value = 1;
0470     for (int i = 0; i < size; i++) {
0471         value = value * digits[i];
0472     }
0473     break;
0474     case Add:
0475     value = 0;
0476     for (int i = 0; i < size; i++) {
0477         value = value + digits[i];
0478     }
0479     break;
0480     default:
0481     break;
0482     }
0483 #ifdef MATHDOKU_LOG
0484     qCDebug(KSudokuLog) << "CHOSE OPERATOR" << op << "value" << value << digits;
0485 #endif
0486     cageOperator = op;
0487     cageValue = value;
0488 }
0489 
0490 bool CageGenerator::cageIsOK (const QList<int> &cage,
0491                               CageOperator cageOperator, int cageValue)
0492 {
0493     // TODO - Is it worth checking for duplicate digits in Mathdoku, before
0494     //        going the whole hog and checking for constraint satisfaction?
0495     // NOTE - The solution, by definition, has to satisfy constraints, even
0496     //        if it does have duplicate digits (ie. those digits must be in
0497     //        different rows/columns/boxes).
0498 
0499     int nDigits = cage.size();
0500 
0501     // In Killer Sudoku, the cage's solution must not contain duplicate digits.
0502     if (mKillerSudoku) {
0503     QList<bool> usedDigits;
0504     usedDigits.fill (false, mOrder + 1);    // Digits' range is 1->order.
0505     for (int n = 0; n < nDigits; n++) {
0506         int digit = mSolution.at (cage.at(n));
0507         if (usedDigits.at (digit)) {
0508 #ifdef MATHDOKU_LOG
0509         qCDebug(KSudokuLog) << "SOLUTION VALUES CONTAIN DUPLICATE" << digit;
0510 #endif
0511         return false;           // Cannot use this cage.
0512         }
0513         usedDigits[digit] = true;
0514     }
0515     }
0516 
0517     // Get all possibilities and keep checking, as we go, that the cage is OK.
0518     bool isOK = true;
0519     setAllPossibilities (cage, nDigits, cageOperator, cageValue);
0520     int numPoss = (mPossibilities->size() - mPossibilitiesIndex->last());
0521 
0522     // There should be some possibilities and not too many (wrt difficulty).
0523     isOK &= (numPoss > 0);
0524     isOK &= ((numPoss / nDigits) <= mMaxCombos);
0525 
0526     if (isOK) {
0527     // Save the possibilities, for use when testing the puzzle solution.
0528         mPossibilitiesIndex->append (mPossibilities->size());
0529     }
0530     else {
0531     // Discard the possibilities: this cage is no good.
0532 #ifdef MATHDOKU_LOG
0533     qCDebug(KSudokuLog) << "CAGE REJECTED: combos" << (numPoss / nDigits)
0534                  << "max" << mMaxCombos << cage << cageValue << cageOperator;
0535 #endif
0536     while (numPoss > 0) {
0537         mPossibilities->removeLast();
0538         numPoss--;
0539         }
0540     }
0541     return isOK;
0542 }
0543 
0544 void CageGenerator::setAllPossibilities (const QList<int> &cage, int nDigits,
0545                                          CageOperator cageOperator,
0546                                          int cageValue)
0547 {
0548     if ((nDigits > 1) && mHiddenOperators && (! mKillerSudoku)) {
0549     // Operators are hidden, so we must consider every possible operator.
0550     if (nDigits == 2) {
0551         setPossibilities (cage, Divide, cageValue);
0552         setPossibilities (cage, Subtract, cageValue);
0553     }
0554         setPossibilities (cage, Add, cageValue);
0555         setPossibilities (cage, Multiply, cageValue);
0556     }
0557     else {
0558     // Operators are visible/fixed, so we can consider fewer possibilities.
0559     setPossibilities (cage, cageOperator, cageValue);
0560     }
0561 }
0562 
0563 void CageGenerator::setPossibilities (const QList<int> &cage,
0564                                       CageOperator cageOperator, int cageValue)
0565 {
0566     // Generate sets of possible solution-values from the range 1 to mOrder.
0567     switch (cageOperator) {
0568     case NoOperator:
0569     mPossibilities->append (cageValue);
0570     break;
0571     case Add:   
0572     case Multiply:
0573     setPossibleAddsOrMultiplies (cage, cageOperator, cageValue);
0574     break;
0575     case Divide:
0576     for (int a = 1; a <= mOrder; a++) {
0577         for (int b = 1; b <= mOrder; b++) {
0578         if ((a == b * cageValue) || (b == a * cageValue)) {
0579             *mPossibilities << a << b;
0580         }
0581         }
0582     }
0583     break;
0584     case Subtract:
0585     for (int a = 1; a <= mOrder; a++) {
0586         for (int b = 1; b <= mOrder; b++) {
0587         if (((a - b) == cageValue) || ((b - a) == cageValue)) {
0588             *mPossibilities << a << b;
0589         }
0590         }
0591     }
0592     break;
0593     }
0594 }
0595 
0596 void CageGenerator::setPossibleAddsOrMultiplies
0597         (const QList<int> &cage, CageOperator cageOperator, int requiredValue)
0598 {
0599     int digits[MaxMathOrder];   // Maximum order of maths-based puzzles == 9.
0600     int maxDigit = mOrder;
0601     int nDigits = cage.size();
0602     int currentValue;
0603     int nTarg = 0;
0604     int nCons = 0;
0605 #ifdef MATHDOKU_LOG
0606     int nDupl = 0;
0607     int nIncons = 0;
0608 #endif
0609     int loopCount = 1;
0610 
0611     // Calculate the number of possible sets of digits in the cage.
0612     for (int n = 0; n < nDigits; n++) {
0613     loopCount = loopCount * maxDigit;
0614     digits[n] = 1;
0615     }
0616 
0617     // Start with a sum or product of all 1's, then check all possibilities.
0618     currentValue = (cageOperator == Add) ? nDigits : 1;
0619     for (int n = 0; n < loopCount; n++) {
0620     if (currentValue == requiredValue) {
0621         nTarg++;
0622 #ifdef MATHDOKU_LOG
0623         qCDebug(KSudokuLog) << "TARGET REACHED" << requiredValue
0624              << "OP" << cageOperator << "DIGITS" << digits;
0625 #endif
0626         bool digitsOK = false;
0627         // In Killer Sudoku, all digits in the cage must be unique.
0628         if (mKillerSudoku) {
0629         // If so, there will be no breaches of Sudoku rules in the
0630         // intersections of rows, columns and blocks with that cage,
0631         // thus there is no need to do the isSelfConsistent() check.
0632         digitsOK = ! hasDuplicates (nDigits, digits);
0633         }
0634         // In Mathdoku, duplicate digits are OK, subject to Sudoku rules.
0635         else {
0636         digitsOK = isSelfConsistent (cage, nDigits, digits);
0637         }
0638         if (digitsOK) {
0639         for (int n = 0; n < nDigits; n++) {
0640             mPossibilities->append (digits[n]);
0641         }
0642         nCons++;
0643         }
0644 #ifdef MATHDOKU_LOG
0645         if (mKillerSudoku) {
0646         nDupl   = digitsOK ? nDupl : nDupl++;
0647         qCDebug(KSudokuLog) << "CONTAINS DUPLICATES: KillerSudoku cage" << digitsOK
0648              << digits << "cage" << cage;
0649         }
0650         else {
0651         nIncons = digitsOK ? nIncons : nIncons++;
0652         qCDebug(KSudokuLog) << "SELF CONSISTENT: Mathdoku cage" << digitsOK
0653              << digits << "cage" << cage;
0654         }
0655 #endif
0656     }
0657 
0658     // Calculate the next set of possible digits (as in an odometer).
0659     for (int d = 0; d < nDigits; d++) {
0660         digits[d]++;
0661         currentValue++;         // Use prev sum, to save time.
0662         if (digits[d] > maxDigit) {     // Carry 1.
0663         digits[d]    -= maxDigit;
0664         currentValue -= maxDigit;
0665         }
0666         else {
0667         break;              // No carry.
0668         }
0669     }
0670 
0671     if (cageOperator == Multiply) {
0672         currentValue = 1;
0673         for (int d = 0; d < nDigits; d++) {
0674         currentValue *= digits[d];
0675         }
0676     }
0677     }
0678 #ifdef MATHDOKU_LOG
0679     qCDebug(KSudokuLog) << loopCount << "possibles" << nTarg << "hit target"
0680          << nDupl << "had duplicate(s)" << nCons << "consistent";
0681 #endif
0682 }
0683 
0684 bool CageGenerator::hasDuplicates (int nDigits, int digits[])
0685 {
0686     int usedDigits = 0;
0687     int mask       = 0;
0688     for (int n = 0; n < nDigits; n++) {
0689     mask = 1 << digits[n];
0690     if (usedDigits & mask) {
0691         return true;
0692     }
0693     usedDigits |= mask;
0694     }
0695     return false;
0696 }
0697 
0698 bool CageGenerator::isSelfConsistent (const QList<int> &cage,
0699                                       int nDigits, int digits[])
0700 {
0701     QList<int> usedGroups;
0702     int mask = 0;
0703     int cell;
0704     usedGroups.fill (0, mGraph->cliqueCount());
0705     for (int n = 0; n < nDigits; n++) {
0706     cell = cage.at (n);
0707     mask = 1 << digits[n];
0708     const QList<int> groupList = mGraph->cliqueList (cell);
0709     for (const int group : groupList) {
0710         if (mask & usedGroups.at (group)) {
0711         return false;
0712         }
0713         usedGroups [group] |= mask;
0714     }
0715     }
0716     return true;
0717 }
0718 
0719 void CageGenerator::init (SKGraph * graph, bool hideOperators)
0720 {
0721     /* INITIAL SETUP FOR THE CageGenerator.
0722 
0723     1. Mark all cells as unused (for cages). This is represented by a list of
0724        unused cells (QList<int>) containing cell-indices.
0725     2. Make a list showing blocked sides (NSWE bitmap) of each cell. A blocked
0726        side means that there is a cell assigned to a cage on that side. Cells
0727        at the edges or corners of the board are set up to have imaginary (dummy)
0728        cages as neighbours.
0729     */
0730     mGraph     = graph;
0731     mOrder     = graph->order();
0732     mBoardArea = mOrder * mOrder;
0733 
0734     // Only Mathdoku puzzles can have hidden operators as part of the *puzzle*.
0735     // KillerSudoku has + or NoOp and makes the +'s hidden only in the *view*.
0736     mKillerSudoku    = (graph->specificType() == KillerSudoku);
0737     mHiddenOperators = mKillerSudoku ? false : hideOperators;
0738 #ifdef MATHDOKU_LOG
0739     qCDebug(KSudokuLog) << "MAKE CAGES init(): HIDDEN OPERATORS" << mHiddenOperators;
0740 #endif
0741 
0742     mUnusedCells.clear();
0743     mNeighbourFlags.clear();
0744 
0745     for (int n = 0; n < mBoardArea; n++) {
0746         mUnusedCells.append (n);
0747 
0748         int col            = graph->cellPosX (n);
0749         int row            = graph->cellPosY (n);
0750         int limit          = mOrder - 1;
0751         int neighbours     = ALONE;
0752 
0753         // Mark cells on the perimeter of the board as having dummy neighbours.
0754         if (row == 0) {
0755             neighbours = neighbours | N; // Cell to the North is unavailable.
0756         }
0757         if (row == limit) {
0758             neighbours = neighbours | S; // Cell to the South is unavailable.
0759         }
0760         if (col == 0) {
0761             neighbours = neighbours | W; // Cell to the West is unavailable.
0762         }
0763         if (col == limit) {
0764             neighbours = neighbours | E; // Cell to the East is unavailable.
0765         }
0766 
0767         mNeighbourFlags.append (neighbours);
0768     }
0769 #ifdef MATHDOKU_LOG
0770     qCDebug(KSudokuLog) << "UNUSED CELLS   " << mUnusedCells;
0771     qCDebug(KSudokuLog) << "NEIGHBOUR-FLAGS" << mNeighbourFlags;
0772 #endif
0773 }
0774 
0775 #include "moc_cagegenerator.cpp"