1 /***************************************************************************
2  *   Copyright 2007      Francesco Rossi <redsh@email.it>                  *
3  *   Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net>         *
4  *   Copyright 2006-2007 Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
5  *   Copyright 2015      Ian Wadham <iandw.au@gmail.com>                   *
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  *   This program is distributed in the hope that it will be useful,       *
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15  *   GNU General Public License for more details.                          *
16  *                                                                         *
17  *   You should have received a copy of the GNU General Public License     *
18  *   along with this program; if not, write to the                         *
19  *   Free Software Foundation, Inc.,                                       *
20  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
21  ***************************************************************************/
22 
23 #include "ksudokugame.h"
24 #include "ksudoku_logging.h"
25 
26 #include "puzzle.h"
27 
28 
29 #include "globals.h"
30 
31 #include <KMessageBox>
32 #include <KLocalizedString>
33 
34 #include <QList>
35 #include <QElapsedTimer>
36 #include <QTime>
37 
38 class QWidget;
39 
40 namespace ksudoku {
41 
42 /**
43  * @TODO replace m_refCount with QAtomic (in KDE4 version)
44  */
45 class Game::Private : public GameIFace {
46 public:
Private()47 	inline Private() : m_refCount(1) { }
~Private()48 	inline ~Private() override {
49 		delete puzzle;
50 	}
51 public:
ref()52 	inline void ref() { ++m_refCount; }
deref()53 	inline bool deref() { return !--m_refCount; }
54 private:
55 	int m_refCount;
56 
57 public: // The slots of GameIFace
58 	void undo() override;
59 	void redo() override;
60 	void addCheckpoint() override;
61 	void undo2Checkpoint() override;
62 
63 public:
emitModified(bool isModified)64 	inline void emitModified(bool isModified) { Q_EMIT modified(isModified); }
emitCompleted(bool isCorrect,const QTime & required,bool withHelp)65 	inline void emitCompleted(bool isCorrect, const QTime& required, bool withHelp) {
66 		Q_EMIT completed(isCorrect, required, withHelp);
67 	}
emitCellChange(int index)68 	inline void emitCellChange(int index) { Q_EMIT cellChange(index); }
emitFullChange()69 	inline void emitFullChange() { Q_EMIT fullChange(); }
emitCageChange(int cageNumP1,bool showLabel)70 	inline void emitCageChange(int cageNumP1, bool showLabel)
71                                   { Q_EMIT cageChange(cageNumP1, showLabel); }
72 
73 public:
74 	PuzzleState state;
75 
76 	bool hadHelp : 1;
77 	bool wasFinished : 1;
78 
79 	Puzzle* puzzle;
80 	QElapsedTimer time;
81 	int   accumTime;
82 	QUrl url;
83 	QList<HistoryEvent> history;
84 	int historyPos;
85 
86 	QVector<int>  m_cage;
87 	int           m_cageValue;
88 	CageOperator  m_cageOperator;
89 	int           m_currentCageSaved;
90 	QWidget *     m_messageParent;
91 };
92 
undo()93 void Game::Private::undo() {
94 	if(historyPos == 0) return;
95 
96 	HistoryEvent event(history[--historyPos]);
97 	event.undoOn(state);
98 
99 	const QVector<int>& indices = event.cellIndices();
100 	if(indices.count() > 10) {
101 		Q_EMIT fullChange();
102 	} else {
103 		for(int i = 0; i < indices.count(); ++i)
104 			Q_EMIT cellChange(indices[i]);
105 	}
106 	Q_EMIT modified(true);
107 }
108 
redo()109 void Game::Private::redo() {
110 	if(historyPos == history.count()) return;
111 
112 	HistoryEvent event(history[historyPos++]);
113 	event.redoOn(state);
114 
115 	const QVector<int>& indices = event.cellIndices();
116 	if(indices.count() > 10) {
117 		Q_EMIT fullChange();
118 	} else {
119 		for(int i = 0; i < indices.count(); ++i)
120 			Q_EMIT cellChange(indices[i]);
121 	}
122 	Q_EMIT modified(true);
123 }
124 
addCheckpoint()125 void Game::Private::addCheckpoint() {
126 }
127 
undo2Checkpoint()128 void Game::Private::undo2Checkpoint() {
129 }
130 
131 /*
132  * The Game
133  */
134 
Game()135 Game::Game()
136 	: m_private(nullptr)
137 {
138 }
139 
Game(Puzzle * puzzle)140 Game::Game(Puzzle* puzzle)
141 	: m_private(nullptr)
142 {
143 	if(!puzzle) return;
144 
145 	m_private = new Private();
146 
147 	m_private->puzzle = puzzle;
148 
149 	m_private->hadHelp = false;
150 	m_private->wasFinished = false;
151 
152 	m_private->state = PuzzleState(size(), m_private->puzzle->order());
153 	m_private->state.reset();
154 
155 	for(int i = 0; i < size(); i++) {
156 		m_private->state.setValue(i, m_private->puzzle->value(i));
157 		if(value(i) != 0)
158 			m_private->state.setGiven(i, true);
159 	}
160 	m_private->historyPos = 0;
161 
162 	m_private->accumTime = 0;
163 	m_private->time.start();
164 
165 	m_private->m_currentCageSaved = false;
166 }
167 
Game(const Game & game)168 Game::Game(const Game& game)
169 	: m_private(game.m_private)
170 {
171 	if(m_private) m_private->ref();
172 }
173 
~Game()174 Game::~Game()
175 {
176 	if(m_private && m_private->deref()) delete m_private;
177 }
178 
operator =(const Game & game)179 Game& Game::operator=(const Game& game) {
180 	if(m_private == game.m_private) return *this;
181 
182 	if(m_private && m_private->deref()) delete m_private;
183 	m_private = game.m_private;
184 	if(m_private) game.m_private->ref();
185 	return *this;
186 }
187 
188 
189 
simpleCheck() const190 bool Game::simpleCheck() const { // IDW TODO - This does nothing useful now
191                                  //            that connections have gone.
192 	if(!m_private) return false;
193 
194 	qCDebug(KSudokuLog) << "BYPASSED Game::simpleCheck()";
195 	return true; // IDW: disabled rest of test.
196 
197 // IDW test. Eliminated optimized[] arrays and xxxConnection() functions.
198 }
199 
restart()200 void Game::restart() {
201 	while (canUndo()) {
202 		interface()->undo();
203 	}
204 	m_private->history.clear(); // otherwise we could do redo
205 	m_private->wasFinished = false;
206 	m_private->emitModified(true); // e.g. to update undo/redo action state
207 }
208 
order() const209 int Game::order() const {
210 	if(!m_private) return 0;
211 	return m_private->puzzle->order();
212 }
213 
size() const214 int Game::size() const {
215 	if(!m_private) return 0;
216 	return m_private->puzzle->size();
217 }
218 
interface() const219 GameIFace* Game::interface() const {
220 	return m_private;
221 }
222 
puzzle() const223 Puzzle* Game::puzzle() const {
224 	if(!m_private) return nullptr;
225 	return m_private->puzzle;
226 }
227 
setUrl(const QUrl & url)228 void Game::setUrl(const QUrl& url) {
229 	if(!m_private) return;
230 
231 	m_private->url = url;
232 }
233 
getUrl() const234 QUrl Game::getUrl() const {
235 	if(!m_private) return QUrl();
236 	return m_private->url;
237 }
238 
239 
setGiven(int index,bool given)240 void Game::setGiven(int index, bool given) {
241 	if(!m_private) return;
242 
243 	if(given != m_private->state.given(index)) {
244 		if(given) {
245 			doEvent(HistoryEvent(index, CellInfo(GivenValue, m_private->state.value(index))));
246 		} else {
247 			doEvent(HistoryEvent(index, CellInfo(CorrectValue, m_private->state.value(index))));
248 		}
249 		m_private->emitCellChange(index);
250 		m_private->emitModified(true);
251 	}
252 }
253 
setMarker(int index,int val,bool state)254 bool Game::setMarker(int index, int val, bool state) {
255 	if(!m_private) return false;
256 
257 	if(val == 0 || val > m_private->puzzle->order())
258 		return false;
259 
260 	if(m_private->state.given(index))
261 		return false;
262 	int val2 = value(index);
263 	if(val == val2) {
264 		doEvent(HistoryEvent(index, CellInfo()));
265 	} else {
266 		QBitArray markers = m_private->state.markers(index);
267 		markers.detach();
268 		if(val2 != 0) {
269 			markers.setBit(val2 - 1, true);
270 		}
271 		markers.setBit(val - 1, state);
272 		doEvent(HistoryEvent(index, CellInfo(markers)));
273 	}
274 
275 	// almost every time this function will change the cell
276 	m_private->emitCellChange(index);
277 	m_private->emitModified(true);
278 
279 	return true;
280 }
281 
setValue(int index,int val)282 void Game::setValue(int index, int val) {
283 	if(!m_private) return;
284 	// If entering in a puzzle, Mathdoku/KillerSudoku has its own procedure.
285 	if (! m_private->puzzle->hasSolution()) {
286 	    if (addToCage (index, val)) {
287 		return;		// Value went in a Mathdoku/KillerSudoku puzzle.
288 	    }
289 	}
290 	if ((val == 32) || (val == 26)) {	// Delete-action or Qt::Key_0.
291 	    val = 0;				// Clear the cell.
292 	}
293 
294 	// Solve all kinds of puzzles or enter in a Sudoku or Roxdoku puzzle.
295 	if(val > m_private->puzzle->order()) return;
296 
297 	if(m_private->state.given(index)) return;
298 
299 	int oldvalue = value(index);
300 	doEvent(HistoryEvent(index, CellInfo(CorrectValue, val)));
301 
302 	m_private->emitCellChange(index);
303 	m_private->emitModified(true);
304 
305 	if(oldvalue != val)
306 		checkCompleted();
307 }
308 
addToCage(int pos,int val)309 bool Game::addToCage (int pos, int val)
310 {
311 	SKGraph * g = m_private->puzzle->graph();
312 	SudokuType t = g->specificType();
313 	if ((t != Mathdoku) && (t != KillerSudoku)) {
314 	    return false;	// We are not keying in a cage.
315 	}
316 #ifdef MATHDOKUENTRY_LOG
317 	qCDebug(KSudokuLog) << "Game::addToCage: pos" << pos << "action" << val;
318 #endif
319 	if (! m_private->m_currentCageSaved) {	// Start a new cage.
320 	    m_private->m_cage.clear();
321 	    m_private->m_cageValue = 0;
322 	    m_private->m_cageOperator = NoOperator;
323 	}
324 	if ((val != 32) && (! validCell (pos, g))) {
325 	    return true;	// Invalid pos and not deleting: go no further.
326 	}
327 	CageOperator cageOp = m_private->m_cageOperator;
328 	if ((val >= 1) && (val <= 9)) {
329 	    // Append a non-zero digit to the cage-value.
330 	    m_private->m_cageValue = 10 * m_private->m_cageValue + val;
331 	}
332 	else {
333 	    switch (val) {
334 	    case 24:	// Qt::Key_X = multiply.
335 		cageOp = Multiply;
336 		break;
337 	    case 26:	// Qt::Key_0
338 		if (m_private->m_cageValue > 0) {
339 		    // Append a zero to the cage-value.
340 		    m_private->m_cageValue = 10 * m_private->m_cageValue;
341 		}
342 		break;
343 	    case 27:	// Qt::Key_Slash.
344 		cageOp = Divide;
345 		break;
346 	    case 28:	// Qt::Key_Minus.
347 		cageOp = Subtract;
348 		break;
349 	    case 29:	// Qt::Key_Plus.
350 		cageOp = Add;
351 		break;
352 	    case 30:	// Left click or Qt::Key_Space = drop through and
353 		break;	//                               add cell to cage.
354 	    case 31:	// Qt::Key_Return = end cage.
355 		finishCurrentCage (g);
356 		return true;
357 		break;
358 	    case 32:	// Right click or Delete/Bkspace = delete a whole cage.
359 		deleteCageAt (pos, g);
360 		return true;
361 		break;
362 	    default:
363 		return false;
364 		break;
365 	    }
366 	}
367 
368 	// Valid keystroke and position: store and display the current cage.
369 	if (m_private->m_cage.indexOf (pos) < 0) {
370 	    m_private->m_cage.append (pos);	// Add cell to current cage.
371 	}
372 	if (t == KillerSudoku) {
373 	    if (cageOp != NoOperator) {
374 		KMessageBox::information (messageParent(),
375 		    i18n("In Killer Sudoku, the operator is always + or none "
376 			 "and KSudoku automatically sets the correct choice."),
377 		    i18n("Killer Sudoku Cage"), QStringLiteral("KillerCageInfo"));
378 	    }
379 	    // Set the operator to none or Add, depending on the cage-size.
380 	    cageOp = (m_private->m_cage.size() > 1) ?  Add : NoOperator;
381 	}
382 	// TODO - In Killer Sudoku, show the operator during data-entry.
383 	m_private->m_cageOperator = cageOp;
384 
385 	// Change the last cage in the data-model in the SKGraph object.
386 	if (m_private->m_currentCageSaved) {	// If new cage, skip dropping.
387 	    int cageNum = g->cageCount() - 1;
388 #ifdef MATHDOKUENTRY_LOG
389 	    qCDebug(KSudokuLog) << "    DROPPING CAGE" << cageNum
390 		     << "m_currentCageSaved" << m_private->m_currentCageSaved
391 		     << "m_cage" << m_private->m_cage;
392 #endif
393 	    g->dropCage (cageNum);
394 	}
395 	// Add a new cage or replace the previous version of the new cage.
396 	g->addCage (m_private->m_cage,
397 		    m_private->m_cageOperator, m_private->m_cageValue);
398 #ifdef MATHDOKUENTRY_LOG
399 	qCDebug(KSudokuLog) << "    ADDED CAGE" << (g->cageCount() - 1)
400 		 << "value" << m_private->m_cageValue
401 		 << "op" << m_private->m_cageOperator
402 		 << m_private->m_cage;
403 #endif
404 	m_private->m_currentCageSaved = true;
405 
406 	// Re-draw the boundary and label of the cage just added to the graph.
407 	// We always display the label while the cage is being keyed in.
408 	m_private->emitCageChange (g->cageCount(), true);
409 	return true;
410 }
411 
validCell(int pos,SKGraph * g)412 bool Game::validCell (int pos, SKGraph * g)
413 {
414 	// No checks of selected cell needed if it is in the current cage.
415 	if (m_private->m_cage.indexOf (pos) >= 0) {
416 	    return true;
417 	}
418 	// Selected cell must not be already in another cage.
419 	for (int n = 0; n < g->cageCount(); n++) {
420 	    if (g->cage(n).indexOf (pos) >= 0) {
421 		KMessageBox::information (messageParent(),
422 		    i18n("The cell you have selected has already been "
423 			 "used in a cage."),
424 		    i18n("Error in Cage"));
425 		return false;
426 	    }
427 	}
428 	// Cell must adjoin the current cage or be the first cell in it.
429 	int cageSize = m_private->m_cage.size();
430 	if (cageSize > 0) {
431 	    int  ix = g->cellPosX(pos);
432 	    int  iy = g->cellPosY(pos);
433 	    int  max = g->order();
434 	    bool adjoining = false;
435 	    for (int n = 0; n < cageSize; n++) {
436 		int cell = m_private->m_cage.at(n);
437 		int dx = g->cellPosX(cell) - ix;
438 		int dy = g->cellPosY(cell) - iy;
439 		if ((dy == 0) && (((ix > 0) && (dx == -1)) ||
440 				  ((ix < max) && (dx == 1)))) {
441 		    adjoining = true;	// Adjoining to left or right.
442 		    break;
443 		}
444 		if ((dx == 0) && (((iy > 0) && (dy == -1)) ||
445 				  ((iy < max) && (dy == 1)))) {
446 		    adjoining = true;	// Adjoining above or below.
447 		    break;
448 		}
449 	    }
450 	    if (! adjoining) {
451 		KMessageBox::information (messageParent(),
452 		    i18n("The cell you have selected is not next to "
453 			 "any cell in the cage you are creating."),
454 		    i18n("Error in Cage"));
455 		return false;
456 	    }
457 	}
458 	return true;
459 }
460 
finishCurrentCage(SKGraph * g)461 void Game::finishCurrentCage (SKGraph * g)
462 {
463 #ifdef MATHDOKUENTRY_LOG
464 	qCDebug(KSudokuLog) << "END CAGE: value" << m_private->m_cageValue
465 		 << "op" << m_private->m_cageOperator
466 		 << m_private->m_cage;
467 #endif
468 	// If Killer Sudoku and cage-size > 1, force operator to be +.
469 	if ((g->specificType() == KillerSudoku) &&
470 	    (m_private->m_cage.size() > 1)) {
471 	    m_private->m_cageOperator = Add;
472 	}
473 	// Validate the contents of the cage.
474 	if ((! m_private->m_currentCageSaved) ||
475 	    (m_private->m_cage.size() == 0)) {
476 	    KMessageBox::information (messageParent(),
477 		i18n("The cage you wish to complete has no cells in it yet. "
478 		     "Please click on a cell or key in + - / x or a number."),
479 		i18n("Error in Cage"));
480 	    return;		// Invalid - cannot finalise the cage.
481 	}
482 	else if (m_private->m_cageValue == 0) {
483 	    KMessageBox::information (messageParent(),
484 		i18n("The cage you wish to complete has no value yet. "
485 		     "Please key in a number with one or more digits."),
486 		i18n("Error in Cage"));
487 	    return;		// Invalid - cannot finalise the cage.
488 	}
489 	else if ((m_private->m_cage.size() > 1) &&
490 	         (m_private->m_cageOperator == NoOperator)) {
491 	    KMessageBox::information (messageParent(),
492 		i18n("The cage you wish to complete has more than one cell, "
493 		     "but it has no operator yet. Please key in + - / or x."),
494 		i18n("Error in Cage"));
495 	    return;		// Invalid - cannot finalise the cage.
496 	}
497 	else if ((m_private->m_cage.size() == 1) &&
498 	         (m_private->m_cageValue > g->order())) {
499 	    KMessageBox::information (messageParent(),
500 		i18n("The cage you wish to complete has one cell, but its "
501 		     "value is too large. A single-cell cage must have a value "
502 		     "from 1 to %1 in a puzzle of this size.", g->order()),
503 		i18n("Error in Cage"));
504 	    return;		// Invalid - cannot finalise the cage.
505 	}
506 
507 	// Save and display the completed cage.
508 	if (m_private->m_cage.size() == 1) {	// Display digit.
509 	    doEvent(HistoryEvent(m_private->m_cage.first(),
510 		    CellInfo(CorrectValue, m_private->m_cageValue)));
511 	    m_private->emitCellChange(m_private->m_cage.first());
512 	    m_private->emitModified(true);
513 	}
514 	// IDW TODO - Unhighlight the cage that is being entered.
515 	m_private->emitCageChange (g->cageCount(),	// No label in size 1.
516 				   (m_private->m_cage.size() > 1));
517 	// Start a new cage.
518 	m_private->m_currentCageSaved = false;
519 }
520 
deleteCageAt(int pos,SKGraph * g)521 void Game::deleteCageAt (int pos, SKGraph * g)
522 {
523 	int cageNumP1 = g->cageCount();
524 	if (cageNumP1 > 0) {
525 	    // IDW TODO - Hover-hilite the cage that is to be deleted.
526 	    cageNumP1 = 0;
527 	    for (int n = 0; n < g->cageCount(); n++) {
528 		if (g->cage(n).indexOf (pos) >= 0) {
529 		    cageNumP1 = n + 1;		// This cage is to be deleted.
530 		    break;
531 		}
532 	    }
533 	    // If the right-click was on a cage, delete it.
534 	    if (cageNumP1 > 0) {
535 		if(KMessageBox::questionYesNo (messageParent(),
536 		       i18n("Do you wish to delete this cage?"),
537 		       i18n("Delete Cage"), KStandardGuiItem::del(),
538 		       KStandardGuiItem::cancel(), QStringLiteral("CageDelConfirm"))
539 		       == KMessageBox::No) {
540 		    return;
541 		}
542 		if (g->cage(cageNumP1-1).size() == 1) {	// Erase digit.
543 		    // Delete the digit shown in a size-1 cage.
544 		    doEvent(HistoryEvent(pos, CellInfo(CorrectValue, 0)));
545 		    m_private->emitCellChange(pos);
546 		    m_private->emitModified(true);
547 		}
548 		// Erase the cage boundary and label.
549 		m_private->emitCageChange (-cageNumP1, false);
550 		// Remove the cage from the puzzle's graph.
551 #ifdef MATHDOKUENTRY_LOG
552 		qCDebug(KSudokuLog) << "    DROP CAGE" << (cageNumP1 - 1);
553 #endif
554 		g->dropCage (cageNumP1 - 1);
555 		if (m_private->m_cage.indexOf (pos) >= 0) {
556 		    // The current cage was dropped.
557 		    m_private->m_currentCageSaved = false;
558 		}
559 	    }
560 	    else {
561 		KMessageBox::information (messageParent(),
562 		    i18n("The cell you have selected is not in any cage, "
563 			 "so the Delete action will not delete anything."),
564 		    i18n("Delete Cage"), QStringLiteral("CageDelMissed"));
565 	    }
566 	}
567 	else {
568 	    KMessageBox::information (messageParent(),
569 		i18n("The Delete action finds that there are no cages "
570 		     "to delete."), i18n("Delete Cage"));
571 #ifdef MATHDOKUENTRY_LOG
572 	    qCDebug(KSudokuLog) << "NO CAGES TO DELETE.";
573 #endif
574 	}
575 }
576 
allValuesSetAndUsable() const577 bool Game::allValuesSetAndUsable() const {
578 	for (int i = 0; i < size(); i++) {
579 		if (value(i) == 0) {
580 			return false;
581 		}
582 	}
583 
584 	return true;
585 }
586 
checkCompleted()587 void Game::checkCompleted() {
588 	if(!m_private || !m_private->puzzle->hasSolution()) return;
589 
590 	if (!allValuesSetAndUsable()) {
591 		return;
592 	}
593 
594 	for(int i = 0; i < size(); i++) {
595 		if(value(i) != solution(i)) {
596 			m_private->emitCompleted(false, time(), m_private->hadHelp);
597 			return;
598 		}
599 	}
600 	m_private->wasFinished = true;
601 	m_private->emitCompleted(true, time(), m_private->hadHelp);
602 }
603 
giveHint()604 bool Game::giveHint() {
605 	if(!m_private || !m_private->puzzle->hasSolution()) return false;
606 
607 	int moveNum = 0;
608 	int index = 0;
609 	while (true) {
610 		index = m_private->puzzle->hintIndex(moveNum);
611 		if (index < 0) {
612 			return false;	// End of hint-list.
613 		}
614 		if (value(index) == 0) {
615 			break;		// Hint is for a cell not yet filled.
616 		}
617 		moveNum++;
618 	}
619 
620 	m_private->hadHelp = true;
621 
622 	int val = solution(index);
623 	doEvent(HistoryEvent(index, CellInfo(GivenValue, val)));
624 
625 	m_private->emitCellChange(index);
626 	m_private->emitModified(true);
627 
628 	checkCompleted();
629 
630 	return true;
631 }
632 
autoSolve()633 bool Game::autoSolve() {
634 	if(!m_private || !m_private->puzzle->hasSolution()) return false;
635 
636 	m_private->hadHelp = true;
637 
638 	PuzzleState newState(size(), m_private->puzzle->order());
639 	newState.reset();
640 
641 	for(int i = 0; i < size(); ++i) {
642 		int val = solution(i);
643 		newState.setValue(i, val);
644 		newState.setGiven(i, true);
645 	}
646 
647 	doEvent(HistoryEvent(newState));
648 
649 	m_private->emitFullChange();
650 	m_private->emitModified(true);
651 
652 	m_private->wasFinished = true;
653 	m_private->emitCompleted(true, time(), true);
654 
655 	return true;
656 }
657 
658 
value(int index) const659 int Game::value(int index) const {
660 	if(!m_private) return 0;
661 	return m_private->state.value(index);
662 }
663 
solution(int index) const664 int Game::solution(int index) const {
665 	if(!m_private) return 0;
666 	return m_private->puzzle->solution(index);
667 }
668 
given(int index) const669 bool Game::given(int index) const {
670 	if(!m_private) return false;
671 	return m_private->state.given(index);
672 }
673 
marker(int index,int val) const674 bool Game::marker(int index, int val) const {
675 	if(!m_private) return false;
676 	return m_private->state.marker(index, val);
677 }
678 
buttonState(int index) const679 ksudoku::ButtonState Game::buttonState(int index) const {
680 	if(!m_private) return WrongValue;
681 
682 	if(given(index))
683 		return GivenValue;
684 	if(value(index) == 0)
685 		return Marker;
686 	if(value(index) == solution(index))
687 		return CorrectValue;
688 	if(solution(index))
689 		return WrongValue;
690 	return CorrectValue;
691 }
692 
cellInfo(int index) const693 CellInfo Game::cellInfo(int index) const {
694 	if(!m_private)
695 		return CellInfo(WrongValue, 0);
696 
697 	if(given(index))
698 		return CellInfo(GivenValue, value(index));
699 	if(value(index) == 0)
700 		return CellInfo(m_private->state.markers(index));
701 	if(value(index) == solution(index))
702 		return CellInfo(CorrectValue, value(index));
703 	if(solution(index))
704 		return CellInfo(WrongValue, value(index));
705 	return CellInfo(CorrectValue, value(index));
706 }
707 
allValues() const708 const BoardContents Game::allValues() const {
709 	if(!m_private) return BoardContents();
710 
711 	return m_private->state.allValues();
712 }
713 
time() const714 QTime Game::time() const {
715 	if(!m_private) return QTime();
716 	return QTime(0,0).addMSecs(msecsElapsed());
717 }
718 
msecsElapsed() const719 int Game::msecsElapsed() const {
720 	if(!m_private) return 0;
721 	return (m_private->accumTime + m_private->time.elapsed());
722 }
723 
setTime(int msecs) const724 void Game::setTime(int msecs) const {
725 	if(!m_private) return;
726 	m_private->accumTime = msecs;
727 	m_private->time.start();
728 }
729 
730 // History
731 
doEvent(const HistoryEvent & event)732 void Game::doEvent(const HistoryEvent& event) {
733 	if(!m_private) return;
734 
735 	HistoryEvent hisEvent(event);
736 
737 	// Remove events after current history position
738 	m_private->history.erase(m_private->history.begin()+(m_private->historyPos), m_private->history.end());
739 
740 	// Append event
741 	hisEvent.applyTo(m_private->state);
742 	m_private->history.append(hisEvent); // always append after applying
743 	m_private->historyPos++;
744 }
745 
historyLength() const746 int Game::historyLength() const {
747 	if(!m_private) return 0;
748 
749 	return m_private->history.count();
750 }
751 
historyEvent(int i) const752 HistoryEvent Game::historyEvent(int i) const {
753 	if(!m_private || i >= m_private->history.count())
754 		return HistoryEvent();
755 
756 	return m_private->history[i];
757 }
758 
canUndo() const759 bool Game::canUndo() const {
760 	if(!m_private) return false;
761 
762 	return m_private->historyPos != 0;
763 }
764 
canRedo() const765 bool Game::canRedo() const {
766 	if(!m_private) return false;
767 	return m_private->historyPos != m_private->history.count();
768 }
769 
userHadHelp() const770 bool Game::userHadHelp() const {
771 	if(!m_private) return false;
772 	return m_private->hadHelp;
773 }
774 
wasFinished() const775 bool Game::wasFinished() const {
776 	if(!m_private) return false;
777 	return m_private->wasFinished;
778 }
779 
setUserHadHelp(bool hadHelp)780 void Game::setUserHadHelp(bool hadHelp) {
781 	if(!m_private) return;
782 	m_private->hadHelp = hadHelp;
783 }
784 
setMessageParent(QWidget * messageParent)785 void Game::setMessageParent(QWidget * messageParent)
786 {
787 	if (m_private) {
788 	    m_private->m_messageParent = messageParent;
789 	}
790 }
791 
messageParent()792 QWidget * Game::messageParent()
793 {
794 	return (m_private ? m_private->m_messageParent : nullptr);
795 }
796 
797 }
798 
799 
800