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

0001 /***************************************************************************
0002  *   Copyright 2007      Francesco Rossi <redsh@email.it>                  *
0003  *   Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net>         *
0004  *   Copyright 2006-2007 Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
0005  *   Copyright 2015      Ian Wadham <iandw.au@gmail.com>                   *
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  *   This program is distributed in the hope that it will be useful,       *
0013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0015  *   GNU General Public License for more details.                          *
0016  *                                                                         *
0017  *   You should have received a copy of the GNU General Public License     *
0018  *   along with this program; if not, write to the                         *
0019  *   Free Software Foundation, Inc.,                                       *
0020  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0021  ***************************************************************************/
0022 
0023 #include "ksudokugame.h"
0024 #include "ksudoku_logging.h"
0025 
0026 #include "puzzle.h"
0027 
0028 
0029 #include "globals.h"
0030 
0031 #include <KMessageBox>
0032 #include <KLocalizedString>
0033 
0034 #include <QList>
0035 #include <QElapsedTimer>
0036 #include <QTime>
0037 
0038 class QWidget;
0039 
0040 namespace ksudoku {
0041     
0042 /**
0043  * @TODO replace m_refCount with QAtomic (in KDE4 version)
0044  */
0045 class Game::Private : public GameIFace {
0046 public:
0047     inline Private() : m_refCount(1) { }
0048     inline ~Private() override {
0049         delete puzzle;
0050     }
0051 public:
0052     inline void ref() { ++m_refCount; }
0053     inline bool deref() { return !--m_refCount; }
0054 private:
0055     int m_refCount;
0056     
0057 public: // The slots of GameIFace
0058     void undo() override;
0059     void redo() override;
0060     void addCheckpoint() override;
0061     void undo2Checkpoint() override;
0062     
0063 public:
0064     inline void emitModified(bool isModified) { Q_EMIT modified(isModified); }
0065     inline void emitCompleted(bool isCorrect, const QTime& required, bool withHelp) {
0066         Q_EMIT completed(isCorrect, required, withHelp);
0067     }
0068     inline void emitCellChange(int index) { Q_EMIT cellChange(index); }
0069     inline void emitFullChange() { Q_EMIT fullChange(); }
0070     inline void emitCageChange(int cageNumP1, bool showLabel)
0071                                   { Q_EMIT cageChange(cageNumP1, showLabel); }
0072     
0073 public:
0074     PuzzleState state;
0075     
0076     bool hadHelp : 1;
0077     bool wasFinished : 1;
0078     
0079     Puzzle* puzzle;
0080     QElapsedTimer time;
0081     int   accumTime;
0082     QUrl url;
0083     QList<HistoryEvent> history;
0084     int historyPos;
0085 
0086     QList<int>    m_cage;
0087     int           m_cageValue;
0088     CageOperator  m_cageOperator;
0089     int           m_currentCageSaved;
0090     QWidget *     m_messageParent;
0091 };
0092 
0093 void Game::Private::undo() {
0094     if(historyPos == 0) return;
0095     
0096     HistoryEvent event(history[--historyPos]);
0097     event.undoOn(state);
0098     
0099     const QList<int>& indices = event.cellIndices();
0100     if(indices.count() > 10) {
0101         Q_EMIT fullChange();
0102     } else {
0103         for(int i = 0; i < indices.count(); ++i)
0104             Q_EMIT cellChange(indices[i]);
0105     }
0106     Q_EMIT modified(true);
0107 }
0108 
0109 void Game::Private::redo() {
0110     if(historyPos == history.count()) return;
0111     
0112     HistoryEvent event(history[historyPos++]);
0113     event.redoOn(state);
0114 
0115     const QList<int>& indices = event.cellIndices();
0116     if(indices.count() > 10) {
0117         Q_EMIT fullChange();
0118     } else {
0119         for(int i = 0; i < indices.count(); ++i)
0120             Q_EMIT cellChange(indices[i]);
0121     }
0122     Q_EMIT modified(true);
0123 }
0124 
0125 void Game::Private::addCheckpoint() {
0126 }
0127 
0128 void Game::Private::undo2Checkpoint() {
0129 }
0130 
0131 /*
0132  * The Game
0133  */
0134 
0135 Game::Game()
0136     : m_private(nullptr)
0137 {
0138 }
0139 
0140 Game::Game(Puzzle* puzzle)
0141     : m_private(nullptr)
0142 {
0143     if(!puzzle) return;
0144 
0145     m_private = new Private();
0146     
0147     m_private->puzzle = puzzle;
0148     
0149     m_private->hadHelp = false;
0150     m_private->wasFinished = false;
0151     
0152     m_private->state = PuzzleState(size(), m_private->puzzle->order());
0153     m_private->state.reset();
0154 
0155     for(int i = 0; i < size(); i++) {
0156         m_private->state.setValue(i, m_private->puzzle->value(i));
0157         if(value(i) != 0)
0158             m_private->state.setGiven(i, true);
0159     }
0160     m_private->historyPos = 0;
0161     
0162     m_private->accumTime = 0;
0163     m_private->time.start();
0164 
0165     m_private->m_currentCageSaved = false;
0166 }
0167 
0168 Game::Game(const Game& game)
0169     : m_private(game.m_private)
0170 {
0171     if(m_private) m_private->ref();
0172 }
0173 
0174 Game::~Game()
0175 {
0176     if(m_private && m_private->deref()) delete m_private;
0177 }
0178 
0179 Game& Game::operator=(const Game& game) {
0180     if(m_private == game.m_private) return *this;
0181     
0182     if(m_private && m_private->deref()) delete m_private;
0183     m_private = game.m_private;
0184     if(m_private) game.m_private->ref();
0185     return *this;
0186 }
0187 
0188 
0189 
0190 bool Game::simpleCheck() const { // IDW TODO - This does nothing useful now
0191                                  //            that connections have gone.
0192     if(!m_private) return false;
0193 
0194     qCDebug(KSudokuLog) << "BYPASSED Game::simpleCheck()";
0195     return true; // IDW: disabled rest of test.
0196 
0197 // IDW test. Eliminated optimized[] arrays and xxxConnection() functions.
0198 }
0199 
0200 void Game::restart() {
0201     while (canUndo()) {
0202         interface()->undo();
0203     }
0204     m_private->history.clear(); // otherwise we could do redo
0205     m_private->wasFinished = false;
0206     m_private->emitModified(true); // e.g. to update undo/redo action state
0207 }
0208 
0209 int Game::order() const {
0210     if(!m_private) return 0;
0211     return m_private->puzzle->order();
0212 }
0213 
0214 int Game::size() const {
0215     if(!m_private) return 0;
0216     return m_private->puzzle->size();
0217 }
0218 
0219 GameIFace* Game::interface() const {
0220     return m_private;
0221 }
0222 
0223 Puzzle* Game::puzzle() const {
0224     if(!m_private) return nullptr;
0225     return m_private->puzzle;
0226 }
0227 
0228 void Game::setUrl(const QUrl& url) {
0229     if(!m_private) return;
0230     
0231     m_private->url = url;
0232 }
0233 
0234 QUrl Game::getUrl() const {
0235     if(!m_private) return QUrl();
0236     return m_private->url;
0237 }
0238 
0239 
0240 void Game::setGiven(int index, bool given) {
0241     if(!m_private) return;
0242     
0243     if(given != m_private->state.given(index)) {
0244         if(given) {
0245             doEvent(HistoryEvent(index, CellInfo(GivenValue, m_private->state.value(index))));
0246         } else {
0247             doEvent(HistoryEvent(index, CellInfo(CorrectValue, m_private->state.value(index))));
0248         }
0249         m_private->emitCellChange(index);
0250         m_private->emitModified(true);
0251     }
0252 }
0253 
0254 bool Game::setMarker(int index, int val, bool state) {
0255     if(!m_private) return false;
0256     
0257     if(val == 0 || val > m_private->puzzle->order())
0258         return false;
0259 
0260     if(m_private->state.given(index))
0261         return false;
0262     int val2 = value(index);
0263     if(val == val2) {
0264         doEvent(HistoryEvent(index, CellInfo()));
0265     } else {
0266         QBitArray markers = m_private->state.markers(index);
0267         markers.detach();
0268         if(val2 != 0) {
0269             markers.setBit(val2 - 1, true);
0270         }
0271         markers.setBit(val - 1, state);
0272         doEvent(HistoryEvent(index, CellInfo(markers)));
0273     }
0274     
0275     // almost every time this function will change the cell
0276     m_private->emitCellChange(index);
0277     m_private->emitModified(true);
0278     
0279     return true;
0280 }
0281 
0282 void Game::setValue(int index, int val) {
0283     if(!m_private) return;
0284     // If entering in a puzzle, Mathdoku/KillerSudoku has its own procedure.
0285     if (! m_private->puzzle->hasSolution()) {
0286         if (addToCage (index, val)) {
0287         return;     // Value went in a Mathdoku/KillerSudoku puzzle.
0288         }
0289     }
0290     if ((val == 32) || (val == 26)) {   // Delete-action or Qt::Key_0.
0291         val = 0;                // Clear the cell.
0292     }
0293 
0294     // Solve all kinds of puzzles or enter in a Sudoku or Roxdoku puzzle.
0295     if(val > m_private->puzzle->order()) return;
0296     
0297     if(m_private->state.given(index)) return;
0298     
0299     int oldvalue = value(index);
0300     doEvent(HistoryEvent(index, CellInfo(CorrectValue, val)));
0301     
0302     m_private->emitCellChange(index);
0303     m_private->emitModified(true);
0304 
0305     if(oldvalue != val)
0306         checkCompleted();
0307 }
0308 
0309 bool Game::addToCage (int pos, int val)
0310 {
0311     SKGraph * g = m_private->puzzle->graph();
0312     SudokuType t = g->specificType();
0313     if ((t != Mathdoku) && (t != KillerSudoku)) {
0314         return false;   // We are not keying in a cage.
0315     }
0316 #ifdef MATHDOKUENTRY_LOG
0317     qCDebug(KSudokuLog) << "Game::addToCage: pos" << pos << "action" << val;
0318 #endif
0319     if (! m_private->m_currentCageSaved) {  // Start a new cage.
0320         m_private->m_cage.clear();
0321         m_private->m_cageValue = 0;
0322         m_private->m_cageOperator = NoOperator;
0323     }
0324     if ((val != 32) && (! validCell (pos, g))) {
0325         return true;    // Invalid pos and not deleting: go no further.
0326     }
0327     CageOperator cageOp = m_private->m_cageOperator;
0328     if ((val >= 1) && (val <= 9)) {
0329         // Append a non-zero digit to the cage-value.
0330         m_private->m_cageValue = 10 * m_private->m_cageValue + val;
0331     }
0332     else {
0333         switch (val) {
0334         case 24:    // Qt::Key_X = multiply.
0335         cageOp = Multiply;
0336         break;
0337         case 26:    // Qt::Key_0
0338         if (m_private->m_cageValue > 0) {
0339             // Append a zero to the cage-value.
0340             m_private->m_cageValue = 10 * m_private->m_cageValue;
0341         }
0342         break;
0343         case 27:    // Qt::Key_Slash.
0344         cageOp = Divide;
0345         break;
0346         case 28:    // Qt::Key_Minus.
0347         cageOp = Subtract;
0348         break;
0349         case 29:    // Qt::Key_Plus.
0350         cageOp = Add;
0351         break;
0352         case 30:    // Left click or Qt::Key_Space = drop through and
0353         break;  //                               add cell to cage.
0354         case 31:    // Qt::Key_Return = end cage.
0355         finishCurrentCage (g);
0356         return true;
0357         break;
0358         case 32:    // Right click or Delete/Bkspace = delete a whole cage.
0359         deleteCageAt (pos, g);
0360         return true;
0361         break;
0362         default:
0363         return false;
0364         break;
0365         }
0366     }
0367 
0368     // Valid keystroke and position: store and display the current cage.
0369     if (m_private->m_cage.indexOf (pos) < 0) {
0370         m_private->m_cage.append (pos); // Add cell to current cage.
0371     }
0372     if (t == KillerSudoku) {
0373         if (cageOp != NoOperator) {
0374         KMessageBox::information (messageParent(),
0375             i18n("In Killer Sudoku, the operator is always + or none "
0376              "and KSudoku automatically sets the correct choice."),
0377             i18n("Killer Sudoku Cage"), QStringLiteral("KillerCageInfo"));
0378         }
0379         // Set the operator to none or Add, depending on the cage-size.
0380         cageOp = (m_private->m_cage.size() > 1) ?  Add : NoOperator;
0381     }
0382     // TODO - In Killer Sudoku, show the operator during data-entry.
0383     m_private->m_cageOperator = cageOp;
0384 
0385     // Change the last cage in the data-model in the SKGraph object.
0386     if (m_private->m_currentCageSaved) {    // If new cage, skip dropping.
0387         int cageNum = g->cageCount() - 1;
0388 #ifdef MATHDOKUENTRY_LOG
0389         qCDebug(KSudokuLog) << "    DROPPING CAGE" << cageNum
0390              << "m_currentCageSaved" << m_private->m_currentCageSaved
0391              << "m_cage" << m_private->m_cage;
0392 #endif
0393         g->dropCage (cageNum);
0394     }
0395     // Add a new cage or replace the previous version of the new cage.
0396     g->addCage (m_private->m_cage,
0397             m_private->m_cageOperator, m_private->m_cageValue);
0398 #ifdef MATHDOKUENTRY_LOG
0399     qCDebug(KSudokuLog) << "    ADDED CAGE" << (g->cageCount() - 1)
0400          << "value" << m_private->m_cageValue
0401          << "op" << m_private->m_cageOperator
0402          << m_private->m_cage;
0403 #endif
0404     m_private->m_currentCageSaved = true;
0405 
0406     // Re-draw the boundary and label of the cage just added to the graph.
0407     // We always display the label while the cage is being keyed in.
0408     m_private->emitCageChange (g->cageCount(), true);
0409     return true;
0410 }
0411 
0412 bool Game::validCell (int pos, SKGraph * g)
0413 {
0414     // No checks of selected cell needed if it is in the current cage.
0415     if (m_private->m_cage.indexOf (pos) >= 0) {
0416         return true;
0417     }
0418     // Selected cell must not be already in another cage.
0419     for (int n = 0; n < g->cageCount(); n++) {
0420         if (g->cage(n).indexOf (pos) >= 0) {
0421         KMessageBox::information (messageParent(),
0422             i18n("The cell you have selected has already been "
0423              "used in a cage."),
0424             i18n("Error in Cage"));
0425         return false;
0426         }
0427     }
0428     // Cell must adjoin the current cage or be the first cell in it.
0429     int cageSize = m_private->m_cage.size();
0430     if (cageSize > 0) {
0431         int  ix = g->cellPosX(pos);
0432         int  iy = g->cellPosY(pos);
0433         int  max = g->order();
0434         bool adjoining = false;
0435         for (int n = 0; n < cageSize; n++) {
0436         int cell = m_private->m_cage.at(n);
0437         int dx = g->cellPosX(cell) - ix;
0438         int dy = g->cellPosY(cell) - iy;
0439         if ((dy == 0) && (((ix > 0) && (dx == -1)) ||
0440                   ((ix < max) && (dx == 1)))) {
0441             adjoining = true;   // Adjoining to left or right.
0442             break;
0443         }
0444         if ((dx == 0) && (((iy > 0) && (dy == -1)) ||
0445                   ((iy < max) && (dy == 1)))) {
0446             adjoining = true;   // Adjoining above or below.
0447             break;
0448         }
0449         }
0450         if (! adjoining) {
0451         KMessageBox::information (messageParent(),
0452             i18n("The cell you have selected is not next to "
0453              "any cell in the cage you are creating."),
0454             i18n("Error in Cage"));
0455         return false;
0456         }
0457     }
0458     return true;
0459 }
0460 
0461 void Game::finishCurrentCage (SKGraph * g)
0462 {
0463 #ifdef MATHDOKUENTRY_LOG
0464     qCDebug(KSudokuLog) << "END CAGE: value" << m_private->m_cageValue
0465          << "op" << m_private->m_cageOperator
0466          << m_private->m_cage;
0467 #endif
0468     // If Killer Sudoku and cage-size > 1, force operator to be +.
0469     if ((g->specificType() == KillerSudoku) &&
0470         (m_private->m_cage.size() > 1)) {
0471         m_private->m_cageOperator = Add;
0472     }
0473     // Validate the contents of the cage.
0474     if ((! m_private->m_currentCageSaved) ||
0475         (m_private->m_cage.size() == 0)) {
0476         KMessageBox::information (messageParent(),
0477         i18n("The cage you wish to complete has no cells in it yet. "
0478              "Please click on a cell or key in + - / x or a number."),
0479         i18n("Error in Cage"));
0480         return;     // Invalid - cannot finalise the cage.
0481     }
0482     else if (m_private->m_cageValue == 0) {
0483         KMessageBox::information (messageParent(),
0484         i18n("The cage you wish to complete has no value yet. "
0485              "Please key in a number with one or more digits."),
0486         i18n("Error in Cage"));
0487         return;     // Invalid - cannot finalise the cage.
0488     }
0489     else if ((m_private->m_cage.size() > 1) &&
0490              (m_private->m_cageOperator == NoOperator)) {
0491         KMessageBox::information (messageParent(),
0492         i18n("The cage you wish to complete has more than one cell, "
0493              "but it has no operator yet. Please key in + - / or x."),
0494         i18n("Error in Cage"));
0495         return;     // Invalid - cannot finalise the cage.
0496     }
0497     else if ((m_private->m_cage.size() == 1) &&
0498              (m_private->m_cageValue > g->order())) {
0499         KMessageBox::information (messageParent(),
0500         i18n("The cage you wish to complete has one cell, but its "
0501              "value is too large. A single-cell cage must have a value "
0502              "from 1 to %1 in a puzzle of this size.", g->order()),
0503         i18n("Error in Cage"));
0504         return;     // Invalid - cannot finalise the cage.
0505     }
0506 
0507     // Save and display the completed cage.
0508     if (m_private->m_cage.size() == 1) {    // Display digit.
0509         doEvent(HistoryEvent(m_private->m_cage.first(),
0510             CellInfo(CorrectValue, m_private->m_cageValue)));
0511         m_private->emitCellChange(m_private->m_cage.first());
0512         m_private->emitModified(true);
0513     }
0514     // IDW TODO - Unhighlight the cage that is being entered.
0515     m_private->emitCageChange (g->cageCount(),  // No label in size 1.
0516                    (m_private->m_cage.size() > 1));
0517     // Start a new cage.
0518     m_private->m_currentCageSaved = false;
0519 }
0520 
0521 void Game::deleteCageAt (int pos, SKGraph * g)
0522 {
0523     int cageNumP1 = g->cageCount();
0524     if (cageNumP1 > 0) {
0525         // IDW TODO - Hover-hilite the cage that is to be deleted.
0526         cageNumP1 = 0;
0527         for (int n = 0; n < g->cageCount(); n++) {
0528         if (g->cage(n).indexOf (pos) >= 0) {
0529             cageNumP1 = n + 1;      // This cage is to be deleted.
0530             break;
0531         }
0532         }
0533         // If the right-click was on a cage, delete it.
0534         if (cageNumP1 > 0) {
0535         if(KMessageBox::questionTwoActions (messageParent(),
0536                i18n("Do you wish to delete this cage?"),
0537                i18n("Delete Cage"), KStandardGuiItem::del(),
0538                KStandardGuiItem::cancel(), QStringLiteral("CageDelConfirm"))
0539                == KMessageBox::SecondaryAction) {
0540             return;
0541         }
0542         if (g->cage(cageNumP1-1).size() == 1) { // Erase digit.
0543             // Delete the digit shown in a size-1 cage.
0544             doEvent(HistoryEvent(pos, CellInfo(CorrectValue, 0)));
0545             m_private->emitCellChange(pos);
0546             m_private->emitModified(true);
0547         }
0548         // Erase the cage boundary and label.
0549         m_private->emitCageChange (-cageNumP1, false);
0550         // Remove the cage from the puzzle's graph.
0551 #ifdef MATHDOKUENTRY_LOG
0552         qCDebug(KSudokuLog) << "    DROP CAGE" << (cageNumP1 - 1);
0553 #endif
0554         g->dropCage (cageNumP1 - 1);
0555         if (m_private->m_cage.indexOf (pos) >= 0) {
0556             // The current cage was dropped.
0557             m_private->m_currentCageSaved = false;
0558         }
0559         }
0560         else {
0561         KMessageBox::information (messageParent(),
0562             i18n("The cell you have selected is not in any cage, "
0563              "so the Delete action will not delete anything."),
0564             i18n("Delete Cage"), QStringLiteral("CageDelMissed"));
0565         }
0566     }
0567     else {
0568         KMessageBox::information (messageParent(),
0569         i18n("The Delete action finds that there are no cages "
0570              "to delete."), i18n("Delete Cage"));
0571 #ifdef MATHDOKUENTRY_LOG
0572         qCDebug(KSudokuLog) << "NO CAGES TO DELETE.";
0573 #endif
0574     }
0575 }
0576 
0577 bool Game::allValuesSetAndUsable() const {
0578     for (int i = 0; i < size(); i++) {
0579         if (value(i) == 0) {
0580             return false;
0581         }
0582     }
0583 
0584     return true;
0585 }
0586 
0587 void Game::checkCompleted() {
0588     if(!m_private || !m_private->puzzle->hasSolution()) return;
0589 
0590     if (!allValuesSetAndUsable()) {
0591         return;
0592     }
0593 
0594     for(int i = 0; i < size(); i++) {
0595         if(value(i) != solution(i)) {
0596             m_private->emitCompleted(false, time(), m_private->hadHelp);
0597             return;
0598         }
0599     }
0600     m_private->wasFinished = true;
0601     m_private->emitCompleted(true, time(), m_private->hadHelp);
0602 }
0603 
0604 bool Game::giveHint() {
0605     if(!m_private || !m_private->puzzle->hasSolution()) return false;
0606     
0607     int moveNum = 0;
0608     int index = 0;
0609     while (true) {
0610         index = m_private->puzzle->hintIndex(moveNum);
0611         if (index < 0) {
0612             return false;   // End of hint-list.
0613         }
0614         if (value(index) == 0) {
0615             break;      // Hint is for a cell not yet filled.
0616         }
0617         moveNum++;
0618     }
0619 
0620     m_private->hadHelp = true;
0621     
0622     int val = solution(index);
0623     doEvent(HistoryEvent(index, CellInfo(GivenValue, val)));
0624     
0625     m_private->emitCellChange(index);
0626     m_private->emitModified(true);
0627     
0628     checkCompleted();
0629     
0630     return true;
0631 }
0632 
0633 bool Game::autoSolve() {
0634     if(!m_private || !m_private->puzzle->hasSolution()) return false;
0635     
0636     m_private->hadHelp = true;
0637     
0638     PuzzleState newState(size(), m_private->puzzle->order());
0639     newState.reset();
0640     
0641     for(int i = 0; i < size(); ++i) {
0642         int val = solution(i);
0643         newState.setValue(i, val);
0644         newState.setGiven(i, true);
0645     }
0646     
0647     doEvent(HistoryEvent(newState));
0648     
0649     m_private->emitFullChange();
0650     m_private->emitModified(true);
0651 
0652     m_private->wasFinished = true;
0653     m_private->emitCompleted(true, time(), true);
0654     
0655     return true;
0656 }
0657 
0658 
0659 int Game::value(int index) const {
0660     if(!m_private) return 0;
0661     return m_private->state.value(index);
0662 }
0663 
0664 int Game::solution(int index) const {
0665     if(!m_private) return 0;
0666     return m_private->puzzle->solution(index);
0667 }
0668 
0669 bool Game::given(int index) const {
0670     if(!m_private) return false;
0671     return m_private->state.given(index);
0672 }
0673 
0674 bool Game::marker(int index, int val) const {
0675     if(!m_private) return false;
0676     return m_private->state.marker(index, val);
0677 }
0678 
0679 ksudoku::ButtonState Game::buttonState(int index) const {
0680     if(!m_private) return WrongValue;
0681     
0682     if(given(index))
0683         return GivenValue;
0684     if(value(index) == 0)
0685         return Marker;
0686     if(value(index) == solution(index))
0687         return CorrectValue;
0688     if(solution(index))
0689         return WrongValue;
0690     return CorrectValue;
0691 }
0692 
0693 CellInfo Game::cellInfo(int index) const {
0694     if(!m_private)
0695         return CellInfo(WrongValue, 0);
0696                 
0697     if(given(index))
0698         return CellInfo(GivenValue, value(index));
0699     if(value(index) == 0)
0700         return CellInfo(m_private->state.markers(index));
0701     if(value(index) == solution(index))
0702         return CellInfo(CorrectValue, value(index));
0703     if(solution(index))
0704         return CellInfo(WrongValue, value(index));
0705     return CellInfo(CorrectValue, value(index));
0706 }
0707 
0708 const BoardContents Game::allValues() const {
0709     if(!m_private) return BoardContents();
0710     
0711     return m_private->state.allValues();
0712 }
0713 
0714 QTime Game::time() const {
0715     if(!m_private) return QTime();
0716     return QTime(0,0).addMSecs(msecsElapsed());
0717 }
0718 
0719 int Game::msecsElapsed() const {
0720     if(!m_private) return 0;
0721     return (m_private->accumTime + m_private->time.elapsed());
0722 }
0723 
0724 void Game::setTime(int msecs) const {
0725     if(!m_private) return;
0726     m_private->accumTime = msecs;
0727     m_private->time.start();
0728 }
0729 
0730 // History
0731 
0732 void Game::doEvent(const HistoryEvent& event) {
0733     if(!m_private) return;
0734     
0735     HistoryEvent hisEvent(event);
0736     
0737     // Remove events after current history position
0738     m_private->history.erase(m_private->history.begin()+(m_private->historyPos), m_private->history.end());
0739     
0740     // Append event
0741     hisEvent.applyTo(m_private->state);
0742     m_private->history.append(hisEvent); // always append after applying
0743     m_private->historyPos++;
0744 }
0745 
0746 int Game::historyLength() const {
0747     if(!m_private) return 0;
0748     
0749     return m_private->history.count();
0750 }
0751 
0752 HistoryEvent Game::historyEvent(int i) const {
0753     if(!m_private || i >= m_private->history.count())
0754         return HistoryEvent();
0755     
0756     return m_private->history[i];
0757 }
0758 
0759 bool Game::canUndo() const {
0760     if(!m_private) return false;
0761     
0762     return m_private->historyPos != 0;
0763 }
0764 
0765 bool Game::canRedo() const {
0766     if(!m_private) return false;
0767     return m_private->historyPos != m_private->history.count();
0768 }
0769 
0770 bool Game::userHadHelp() const {
0771     if(!m_private) return false;
0772     return m_private->hadHelp;
0773 }
0774 
0775 bool Game::wasFinished() const {
0776     if(!m_private) return false;
0777     return m_private->wasFinished;
0778 }
0779 
0780 void Game::setUserHadHelp(bool hadHelp) {
0781     if(!m_private) return;
0782     m_private->hadHelp = hadHelp;
0783 }
0784 
0785 void Game::setMessageParent(QWidget * messageParent)
0786 {
0787     if (m_private) {
0788         m_private->m_messageParent = messageParent;
0789     }
0790 }
0791 
0792 QWidget * Game::messageParent()
0793 {
0794     return (m_private ? m_private->m_messageParent : nullptr);
0795 }
0796 
0797 }
0798 
0799 #include "moc_ksudokugame.cpp"