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"