1/* ****************************************************************************
2 This file is part of the game 'KJumpingCube'
3
4 Copyright (C) 1998-2000 by Matthias Kiefer <matthias.kiefer@gmx.de>
5 Copyright (C) 2012-2013 by 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 Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20**************************************************************************** */
21
22#include "game.h"
23#include "version.h"
24#include "ai_main.h"
25#include "ai_box.h"
26#include "kcubeboxwidget.h"
27#include "settingswidget.h"
28
29#include <KConfigDialog> // IDW test.
30
31#include <KLocalizedString>
32#include <KMessageBox>
33#include <KFileDialog>
34#include <KTemporaryFile>
35#include <kio/netaccess.h>
36#include <QTimer>
37
38#include "prefs.h"
39
40#define LARGE_NUMBER 999999
41
42Game::Game (const int d, KCubeBoxWidget * view, QWidget * parent)
43 :
44 QObject ((QObject *) parent), // Delete Game when window is deleted.
45 m_activity (Idle),
46 m_waitingState (Nil),
47 m_waitingToMove (false),
48 m_moveNo (0), // Game not started.
49 m_endMoveNo (LARGE_NUMBER), // Game not finished.
50 m_interrupting (false),
51 m_newSettings (false),
52 m_parent (parent),
53 m_view (view),
54 m_settingsPage (0),
55 m_side (d),
56 m_currentPlayer (One),
57 m_index (0),
58 m_fullSpeed (false),
59 computerPlOne (false),
60 computerPlTwo (false),
61 m_pauseForComputer (false),
62 m_pauseForStep (false)
63{
64 qDebug() << "CONSTRUCT Game: side" << m_side;
65 m_box = new AI_Box (this, m_side);
66 m_ai = new AI_Main (this, m_side);
67 m_steps = new QList<int>;
68
69 connect (m_view, SIGNAL(mouseClick(int,int)), SLOT(startHumanMove(int,int)));
70 connect (m_ai, SIGNAL(done(int)), SLOT(moveCalculationDone(int)));
71 connect (m_view, SIGNAL(animationDone(int)), SLOT(animationDone(int)));
72}
73
74Game::~Game()
75{
76 if ((m_activity != Idle) && (m_activity != Aborting)) {
77 shutdown();
78 }
79 delete m_steps;
80}
81
82void Game::gameActions (const int action)
83{
84 qDebug() << "GAME ACTION IS" << action;
85 if ((m_activity != Idle) && (action != BUTTON) && (action != NEW)) {
86 m_view->showPopup (i18n("Sorry, doing a move..."));
87 return;
88 }
89
90 switch (action) {
91 case NEW:
92 newGame();
93 break;
94 case HINT:
95 if (! isComputer (m_currentPlayer)) {
96 KCubeWidget::enableClicks (false);
97 computeMove();
98 }
99 break;
100 case BUTTON:
101 buttonClick();
102 break;
103 case UNDO:
104 undo();
105 break;
106 case REDO:
107 redo();
108 break;
109 case SAVE:
110 case SAVE_AS:
111 saveGame (action == SAVE_AS);
112 break;
113 case LOAD:
114 loadGame();
115 break;
116 default:
117 break;
118 }
119}
120
121void Game::showWinner()
122{
123 emit buttonChange (false, false, i18n("Game over"));
124 QString s = i18n("The winner is Player %1!", m_currentPlayer);
125 KMessageBox::information (m_view, s, i18n("Winner"));
126}
127
128void Game::showSettingsDialog (bool show)
129{
130 // Show the Preferences/Settings/Configuration dialog.
131 KConfigDialog * settings = KConfigDialog::exists ("settings");
132 if (! settings) {
133 settings = new KConfigDialog (m_parent, "settings", Prefs::self());
134 settings->setFaceType (KPageDialog::Plain);
135 SettingsWidget * widget = new SettingsWidget (m_parent);
136 settings->addPage (widget, i18n("General"), "games-config-options");
137 connect (settings, SIGNAL(settingsChanged(QString)), SLOT(newSettings()));
138 m_settingsPage = widget; // Used when reverting/editing settings.
139 }
140 if (! show) return;
141 settings->show();
142 settings->raise(); // Force the dialog to be in front.
143}
144
145void Game::newSettings()
146{
147 qDebug() << "NEW SETTINGS" << m_newSettings << "m_activity" << m_activity
148 << "size:" << Prefs::cubeDim() << "m_side" << m_side;
149 loadImmediateSettings();
150
151 m_newSettings = true;
152 if (m_side != Prefs::cubeDim()) {
153 QMetaObject::invokeMethod (this, "newGame", Qt::QueuedConnection);
154 return;
155 }
156 // Waiting to move and not Hinting?
157 else if (m_waitingToMove && (m_activity == Idle)) {
158 loadPlayerSettings();
159 m_newSettings = false;
160 setUpNextTurn();
161 }
162 // Else, the remaining settings will be loaded at the next start of a turn.
163 // They set computer pause on/off and computer player 1 or 2 on/off.
164}
165
166void Game::loadImmediateSettings()
167{
168 qDebug() << "GAME LOAD IMMEDIATE SETTINGS entered";
169
170 // Color changes can take place as soon as control returns to the event loop.
171 // Changes of animation type or speed will take effect next time there is an
172 // animation step to do, regardless of current activity.
173 bool reColorCubes = m_view->loadSettings();
174 m_fullSpeed = Prefs::animationNone();
175 m_pauseForStep = Prefs::pauseForStep();
176 if (reColorCubes) {
177 emit playerChanged (m_currentPlayer); // Re-display status bar icon.
178 }
179
180 // Choices of computer AIs and skills will take effect next time there is a
181 // computer move or hint. They will not affect any calculation in progress.
182 m_ai->setSkill (Prefs::skill1(), Prefs::kepler1(), Prefs::newton1(),
183 Prefs::skill2(), Prefs::kepler2(), Prefs::newton2());
184
185 qDebug() << "m_pauseForStep" << m_pauseForStep;
186 qDebug() << "PLAYER 1 settings: skill" << Prefs::skill1()
187 << "Kepler" << Prefs::kepler1() << "Newton" << Prefs::newton1();
188 qDebug() << "PLAYER 2 settings: skill" << Prefs::skill2()
189 << "Kepler" << Prefs::kepler2() << "Newton" << Prefs::newton2();
190}
191
192void Game::loadPlayerSettings()
193{
194 qDebug() << "GAME LOAD PLAYER SETTINGS entered";
195 bool oldComputerPlayer = isComputer (m_currentPlayer);
196
197 m_pauseForComputer = Prefs::pauseForComputer();
198 computerPlOne = Prefs::computerPlayer1();
199 computerPlTwo = Prefs::computerPlayer2();
200
201 qDebug() << "AI 1" << computerPlOne << "AI 2" << computerPlTwo
202 << "m_pauseForComputer" << m_pauseForComputer;
203
204 if (isComputer (m_currentPlayer) && (! oldComputerPlayer)) {
205 qDebug() << "New computer player set: must wait.";
206 m_waitingState = ComputerToMove; // New player: don't start playing yet.
207 }
208}
209
210void Game::startHumanMove (int x, int y)
211{
212 int index = x * m_side + y;
213 bool humanPlayer = (! isComputer (m_currentPlayer));
214 qDebug() << "CLICK" << x << y << "index" << index;
215 if (! humanPlayer) {
216 buttonClick();
217 }
218 else if (humanPlayer && ((m_currentPlayer == m_box->owner(index)) ||
219 (m_box->owner(index) == Nobody))) {
220 m_waitingToMove = false;
221 m_moveNo++;
222 m_endMoveNo = LARGE_NUMBER;
223 qDebug() << "doMove (" << index;
224 KCubeWidget::enableClicks (false);
225 doMove (index);
226 }
227}
228
229void Game::setUpNextTurn()
230{
231 // Called from newSettings(), new game, load, change player and undo/redo.
232 if (m_newSettings) {
233 m_newSettings = false;
234 loadPlayerSettings();
235 }
236 qDebug() << "setUpNextTurn" << m_currentPlayer
237 << computerPlOne << computerPlTwo << "pause" << m_pauseForComputer
238 << "wait" << m_waitingState << "waiting" << m_waitingToMove;
239 if (isComputer (m_currentPlayer)) {
240 // A computer player is to move.
241 qDebug() << "(m_pauseForComputer || (m_waitingState == ComputerToMove))"
242 << (m_pauseForComputer || (m_waitingState == ComputerToMove));
243 if (m_pauseForComputer || (m_waitingState == ComputerToMove) ||
244 (m_moveNo == 0)) {
245 m_waitingState = ComputerToMove;
246 m_waitingToMove = true;
247 if (computerPlOne && computerPlTwo) {
248 if (m_moveNo == 0) {
249 emit buttonChange (true, false, i18n("Start game"));
250 }
251 else {
252 emit buttonChange (true, false, i18n("Continue game"));
253 }
254 }
255 else {
256 emit buttonChange (true, false, i18n("Start computer move"));
257 }
258 // Wait for a button-click to show that the user is ready.
259 qDebug() << "COMPUTER MUST WAIT";
260 KCubeWidget::enableClicks (true);
261 return;
262 }
263 // Start the computer's move.
264 qDebug() << "COMPUTER MUST MOVE";
265 m_waitingState = Nil;
266 m_waitingToMove = false;
267 KCubeWidget::enableClicks (false);
268 computeMove();
269 }
270 else {
271 // A human player is to move.
272 qDebug() << "HUMAN TO MOVE";
273 KCubeWidget::enableClicks (true);
274 m_waitingState = Nil;
275 m_waitingToMove = true;
276 if (computerPlOne || computerPlTwo) {
277 emit buttonChange (false, false, i18n("Your turn"));
278 }
279 else {
280 emit buttonChange (false, false, i18n("Player %1", m_currentPlayer));
281 }
282 // Wait for a click on the cube to be moved.
283 }
284}
285
286void Game::computeMove()
287{
288#if AILog > 1
289 t.start(); // Start timing the AI calculation.
290#endif
291 m_view->setWaitCursor();
292 m_activity = Computing;
293 setStopAction();
294 emit setAction (HINT, false);
295 if (isComputer (m_currentPlayer)) {
296 emit statusMessage (i18n("Computer player %1 is moving")
297 .arg(m_currentPlayer), false);
298 }
299 m_ai->getMove (m_currentPlayer, m_box);
300}
301
302void Game::moveCalculationDone (int index)
303{
304 // We do not care if we interrupted the computer. It was probably taking
305 // too long, so we will use the best move it had so far.
306
307 // We are starting a new game or closing KJumpingCube. See shutdown().
308 if (m_activity == Aborting) {
309 m_activity = Idle;
310 return;
311 }
312
313 m_activity = Idle;
314 if ((index < 0) || (index >= (m_side * m_side))) {
315 m_view->setNormalCursor();
316 KMessageBox::sorry (m_view,
317 i18n ("The computer could not find a valid move."));
318 // IDW TODO - What to do about state values and BUTTON ???
319 return;
320 }
321
322#if AILog > 1
323 qDebug() << "TIME of MOVE" << t.elapsed();
324 qDebug() << "==============================================================";
325#endif
326
327 // Blink the cube to be moved (twice).
328 m_view->startAnimation (false, index);
329
330 m_activity = ShowingMove;
331 setStopAction();
332}
333
334void Game::showingDone (int index)
335{
336 if (isComputer (m_currentPlayer)) {
337 m_moveNo++;
338 m_endMoveNo = LARGE_NUMBER;
339 qDebug() << "m_moveNo" << m_moveNo << "isComputer()" << (isComputer (m_currentPlayer));
340 doMove (index); // Animate computer player's move.
341 }
342 else {
343 moveDone(); // Finish Hint animation.
344 setUpNextTurn(); // Wait: unless player setting changed.
345 }
346}
347
348void Game::doMove (int index)
349{
350 bool computerMove = ((computerPlOne && m_currentPlayer == One) ||
351 (computerPlTwo && m_currentPlayer == Two));
352
353 // Make a copy of the position and player to move for the Undo function.
354 m_box->copyPosition (m_currentPlayer, computerMove, index);
355
356#if AILog > 0
357 if (! computerMove) { // Record a human move in the statistics.
358 m_ai->postMove (m_currentPlayer, index, m_side);
359 }
360#endif
361 emit setAction (UNDO, true); // Update Undo and Redo actions.
362 emit setAction (REDO, false);
363 m_steps->clear();
364 bool won = m_box->doMove (m_currentPlayer, index, 0, m_steps);
365#if AILog > 1
366 qDebug() << "GAME WON?" << won << "STEPS" << (* m_steps);
367 // m_box->printBox();
368#endif
369 if (m_steps->count() > 1) {
370 m_view->setWaitCursor(); //This will be a stoppable animation.
371 }
372 m_activity = AnimatingMove;
373 doStep();
374}
375
376void Game::doStep()
377{
378 // Re-draw all cubes affected by a move, proceeding one step at a time.
379 int index;
380 bool startStep = true;
381 do {
382 if (! m_steps->isEmpty()) {
383 index = m_steps->takeFirst(); // Get a cube to be re-drawn.
384
385 // Check if the player wins at this step (no more cubes are re-drawn).
386 if (index == 0) {
387 moveDone();
388 m_endMoveNo = m_moveNo;
389#if AILog > 0
390 qDebug() << "\nCALLING dumpStats()";
391 m_ai->dumpStats();
392#endif
393 showWinner();
394 return;
395 }
396
397 // Update the view of a cube, either immediately or via animation.
398 startStep = (index > 0); // + -> increment, - -> expand.
399 index = startStep ? (index - 1) : (-index - 1);
400 int value = m_view->cubeValue (index); // Pre-update in view.
401 int max = m_box->maxValue (index);
402 if (startStep) { // Add 1 and take.
403 m_view->displayCube (index, m_currentPlayer, value + 1);
404 if ((value >= max) && (! m_fullSpeed)) {
405 m_view->highlightCube (index, true);
406 }
407 }
408 else if (m_fullSpeed) { // Decrease immediately.
409 m_view->displayCube (index, m_currentPlayer, value - max);
410 m_view->highlightCube (index, false); // Maybe user hit Stop.
411 }
412 else { // Animate cascade step.
413 if (m_pauseForStep && (m_waitingState != WaitingToStep)) {
414 // Pause: return the step to the list and wait for a buttonClick.
415 m_steps->prepend (-index - 1);
416 m_waitingState = WaitingToStep;
417 emit buttonChange (true, false, i18n("Show next step"));
418 return;
419 }
420 // Now set the button up and start the animation.
421 setStopAction();
422 m_view->startAnimation (true, index);
423 }
424 }
425 else {
426 // Views of the cubes at all steps of the move have been updated.
427 moveDone();
428 changePlayer();
429 return;
430 }
431 } while (startStep || m_fullSpeed);
432}
433
434void Game::stepAnimationDone (int index)
435{
436 // Finished a move step. Decrease the cube that expanded and display it.
437 int value = m_view->cubeValue (index);
438 int max = m_box->maxValue (index);
439 m_view->displayCube (index, m_currentPlayer, value - max);
440
441 doStep(); // Do next animation step (if any).
442}
443
444void Game::moveDone()
445{
446 // Called after non-animated move, animated move, end of game or hint action.
447 m_view->setNormalCursor();
448 emit statusMessage (QString(""), false); // Clear the status bar.
449 m_activity = Idle;
450 setAction (HINT, true);
451 m_fullSpeed = Prefs::animationNone();
452 m_view->hidePopup();
453 if (m_interrupting) {
454 m_interrupting = false;
455 m_waitingState = ComputerToMove;
456 }
457}
458
459Player Game::changePlayer()
460{
461 m_currentPlayer = (m_currentPlayer == One) ? Two : One;
462 emit playerChanged (m_currentPlayer);
463 setUpNextTurn();
464 return m_currentPlayer;
465}
466
467void Game::buttonClick()
468{
469 qDebug() << "BUTTON CLICK seen: waiting" << m_waitingToMove
470 << "m_activity" << m_activity << "m_waitingState" << m_waitingState;
471 if (m_waitingState == Nil) { // Button is red: stop an activity.
472 if ((! m_pauseForComputer) && (! m_interrupting) &&
473 (computerPlOne && computerPlTwo)) {
474 m_interrupting = true; // Interrupt a non-stop AI v AI game.
475 m_view->showPopup (i18n("Finishing move..."));
476 setStopAction(); // Change to text for current activity.
477 }
478 else if (m_activity == Computing) {
479 m_ai->stop(); // Stop calculating a move or hint.
480 m_activity = Stopping;
481 }
482 else if (m_activity == ShowingMove) {
483 int index = m_view->killAnimation();
484 showingDone (index); // Stop showing where a move or hint is.
485 m_view->highlightCube (index, false);
486 }
487 else if (m_activity == AnimatingMove) {
488 int index = m_view->killAnimation();
489 m_fullSpeed = true; // Go to end of move right now, skipping
490 stepAnimationDone (index); // all later steps of animation.
491 }
492 }
493 else { // Button is green: start an activity.
494 switch (m_waitingState) {
495 case WaitingToStep:
496 doStep(); // Start next animation step.
497 break;
498 case ComputerToMove:
499 computeMove(); // Start next computer move.
500 break;
501 default:
502 break;
503 }
504 m_waitingState = Nil;
505 }
506}
507
508void Game::setStopAction()
509{
510 // Red button setting for non-stop AI v. AI game.
511 if ((! m_pauseForComputer) && (! m_interrupting) &&
512 (computerPlOne && computerPlTwo)) {
513 if (m_activity == Computing) { // Starting AI v. AI move.
514 emit buttonChange (true, true, i18n("Interrupt game"));
515 }
516 return; // Continuing AI v. AI move.
517 }
518 // Red button settings for AI v. human, two human players or pausing game.
519 if (m_activity == Computing) { // Calculating hint or AI move.
520 emit buttonChange (true, true, i18n("Stop computing"));
521 }
522 else if (m_activity == ShowingMove) { // Showing hint or AI move.
523 emit buttonChange (true, true, i18n("Stop showing move"));
524 }
525 else if (m_activity == AnimatingMove) { // Animating AI or human move.
526 emit buttonChange (true, true, i18n("Stop animation"));
527 }
528}
529
530/* ************************************************************************** */
531/* STANDARD GAME ACTIONS */
532/* ************************************************************************** */
533
534void Game::newGame()
535{
536 qDebug() << "NEW GAME entered: waiting" << m_waitingToMove
537 << "won?" << (m_moveNo >= m_endMoveNo);
538 if (newGameOK()) {
539 qDebug() << "QDEBUG: newGameOK() =" << true;
540 shutdown(); // Stop the current move (if any).
541 m_view->setNormalCursor();
542 m_view->hidePopup();
543 loadImmediateSettings();
544 loadPlayerSettings();
545 m_newSettings = false;
546 qDebug() << "newGame() loadSettings DONE: waiting" << m_waitingToMove
547 << "won?" << (m_moveNo >= m_endMoveNo)
548 << "move" << m_moveNo << m_endMoveNo;
549 qDebug() << "setDim (" << Prefs::cubeDim() << ") m_side" << m_side;
550 setDim (Prefs::cubeDim());
551 qDebug() << "Entering reset();";
552 reset(); // Clear cubebox, initialise states.
553 emit setAction (UNDO, false);
554 emit setAction (REDO, false);
555 emit statusMessage (i18n("New Game"), false);
556 m_moveNo = 0;
557 m_endMoveNo = LARGE_NUMBER;
558 setUpNextTurn();
559 }
560 else qDebug() << "QDEBUG: newGameOK() =" << false;
561}
562
563void Game::saveGame (bool saveAs)
564{
565 if (saveAs || m_gameURL.isEmpty()) {
566 int result=0;
567 KUrl url;
568
569 do {
570 url = KFileDialog::getSaveUrl (m_gameURL.url(), "*.kjc", m_view, 0);
571
572 if (url.isEmpty())
573 return;
574
575 // check filename
576 QRegExp pattern ("*.kjc", Qt::CaseSensitive, QRegExp::Wildcard);
577 if (! pattern.exactMatch (url.fileName())) {
578 url.setFileName (url.fileName()+".kjc");
579 }
580
581 if (KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide,
582 m_view)) {
583 QString mes=i18n("The file %1 exists.\n"
584 "Do you want to overwrite it?", url.url());
585 result = KMessageBox::warningContinueCancel
586 (m_view, mes, QString(), KGuiItem(i18n("Overwrite")));
587 if (result == KMessageBox::Cancel)
588 return;
589 }
590 } while (result == KMessageBox::No);
591
592 m_gameURL = url;
593 }
594
595 KTemporaryFile tempFile;
596 tempFile.open();
597 KConfig config (tempFile.fileName(), KConfig::SimpleConfig);
598 KConfigGroup main (&config, "KJumpingCube");
599 main.writeEntry ("Version", KJC_VERSION);
600 KConfigGroup game (&config, "Game");
601 saveProperties (game);
602 config.sync();
603
604 if (KIO::NetAccess::upload (tempFile.fileName(), m_gameURL, m_view)) {
605 emit statusMessage (i18n("Game saved as %1", m_gameURL.url()), false);
606 }
607 else {
608 KMessageBox::sorry (m_view, i18n("There was an error in saving file\n%1",
609 m_gameURL.url()));
610 }
611}
612
613void Game::loadGame()
614{
615 bool fileOk=true;
616 KUrl url;
617
618 do {
619 url = KFileDialog::getOpenUrl (m_gameURL.url(), "*.kjc", m_view, 0);
620 if (url.isEmpty())
621 return;
622 if (! KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, m_view)) {
623 QString mes = i18n("The file %1 does not exist!", url.url());
624 KMessageBox::sorry (m_view, mes);
625 fileOk = false;
626 }
627 } while (! fileOk);
628
629 QString tempFile;
630 if (KIO::NetAccess::download (url, tempFile, m_view)) {
631 KConfig config( tempFile, KConfig::SimpleConfig);
632 KConfigGroup main (&config, "KJumpingCube");
633 if (! main.hasKey ("Version")) {
634 QString mes = i18n("The file %1 is not a KJumpingCube gamefile!",
635 url.url());
636 KMessageBox::sorry (m_view, mes);
637 return;
638 }
639
640 m_gameURL = url;
641 KConfigGroup game (&config, "Game");
642 readProperties (game);
643
644 emit setAction (UNDO, false);
645
646 KIO::NetAccess::removeTempFile (tempFile);
647 }
648 else
649 KMessageBox::sorry (m_view, i18n("There was an error loading file\n%1",
650 url.url()));
651}
652
653void Game::undo()
654{
655 bool moreToUndo = undoRedo (-1);
656 emit setAction (UNDO, moreToUndo);
657 emit setAction (REDO, true);
658}
659
660void Game::redo()
661{
662 bool moreToRedo = undoRedo (+1);
663 emit setAction (REDO, moreToRedo);
664 emit setAction (UNDO, true);
665}
666
667bool Game::newGameOK()
668{
669 if ((m_moveNo == 0) || (m_moveNo >= m_endMoveNo)) {
670 // OK: game finished or not yet started. Settings might have changed.
671 return true;
672 }
673
674 // Check if it is OK to abandon the current game.
675 QString query;
676 if (m_side != Prefs::cubeDim()) {
677 query = i18n("You have changed the size setting of the game and "
678 "that requires a new game to start.\n\n"
679 "Do you wish to abandon the current game or continue "
680 "playing and restore the previous size setting?");
681 }
682 else {
683 query = i18n("You have requested a new game, but "
684 "there is already a game in progress.\n\n"
685 "Do you wish to abandon the current game?");
686 }
687 qDebug() << "QUERY:" << query;
688 int reply = KMessageBox::questionYesNo (m_view, query, i18n("New Game?"),
689 KGuiItem (i18n("Abandon Game")),
690 KGuiItem (i18n("Continue Game")));
691 if (reply == KMessageBox::Yes) {
692 qDebug() << "ABANDON GAME";
693 return true; // Start a new game.
694 }
695 if (m_side != Prefs::cubeDim()) {
696 // Restore the setting: also the dialog-box copy if it has been created.
697 qDebug() << "Reset size" << Prefs::cubeDim() << "back to" << m_side;
698 Prefs::setCubeDim (m_side);
699 if (m_settingsPage) {
700 m_settingsPage->kcfg_CubeDim->setValue (m_side);
701 }
702 Prefs::self()->writeConfig();
703 }
704 qDebug() << "CONTINUE GAME";
705 return false; // Continue the current game.
706}
707
708void Game::reset()
709{
710 m_view->reset();
711 m_box->clear();
712
713 m_fullSpeed = Prefs::animationNone(); // Animate cascade moves?
714
715 m_currentPlayer = One;
716
717 m_waitingState = computerPlOne ? ComputerToMove : Nil;
718 qDebug() << "RESET: activity" << m_activity << "wait" << m_waitingState;
719
720#if AILog > 0
721 m_ai->startStats();
722#endif
723}
724
725bool Game::undoRedo (int change)
726{
727 Player oldPlayer = m_currentPlayer;
728
729 bool isAI = false;
730 int index = 0;
731 bool moreToDo = (change < 0) ?
732 m_box->undoPosition (m_currentPlayer, isAI, index) :
733 m_box->redoPosition (m_currentPlayer, isAI, index);
734
735 // Update the cube display after an undo or redo.
736 for (int n = 0; n < (m_side * m_side); n++) {
737 m_view->displayCube (n, m_box->owner (n), m_box->value (n));
738 m_view->highlightCube (n, false);
739 }
740 m_view->timedCubeHighlight (index); // Show which cube was moved.
741
742 m_moveNo = m_moveNo + change;
743 if (m_moveNo < m_endMoveNo) {
744 if (oldPlayer != m_currentPlayer) {
745 emit playerChanged (m_currentPlayer);
746 }
747 m_waitingState = isComputer (m_currentPlayer) ? ComputerToMove
748 : m_waitingState;
749 setUpNextTurn();
750 }
751 else { // End of game: show winner.
752 moreToDo = false;
753 m_currentPlayer = oldPlayer;
754 showWinner();
755 }
756 return moreToDo;
757}
758
759void Game::setDim (int d)
760{
761 if (d != m_side) {
762 shutdown();
763 delete m_box;
764 m_box = new AI_Box (this, d);
765 qDebug() << "AI_Box CONSTRUCTED by Game::setDim()";
766 m_side = d;
767 m_view->setDim (d);
768 }
769}
770
771void Game::shutdown()
772{
773 // Shut down gracefully, avoiding a possible crash when the user hits Quit.
774 m_view->killAnimation(); // Stop animation immediately (if active).
775 if (m_activity == Computing) {
776 m_ai->stop(); // Stop AI ASAP (moveCalculationDone() => Idle).
777 m_activity = Aborting;
778 }
779 else if (m_activity == Stopping) {
780 m_activity = Aborting;
781 }
782 else if (m_activity != Aborting) {
783 m_activity = Idle; // In case it was ShowingMove or AnimatingMove.
784 }
785}
786
787void Game::saveProperties (KConfigGroup & config)
788{
789 // Save the current player.
790 config.writeEntry ("onTurn", (int) m_currentPlayer);
791
792 QStringList list;
793 QString owner, value, key;
794
795 // Save the position currently on the board.
796 for (int x = 0; x < m_side; x++) {
797 for (int y = 0; y < m_side; y++) {
798 key.sprintf ("%u,%u", x, y);
799 int index = x * m_side + y;
800 owner.sprintf ("%u", m_box->owner (index));
801 value.sprintf ("%u", m_box->value (index));
802 list.append (owner.toAscii());
803 list.append (value.toAscii());
804 config.writeEntry (key, list);
805
806 list.clear();
807 }
808 }
809
810 // Save the game and player settings.
811 config.writeEntry ("CubeDim", m_side);
812 config.writeEntry ("PauseForComputer", m_pauseForComputer ? 1 : 0);
813 config.writeEntry ("ComputerPlayer1", computerPlOne);
814 config.writeEntry ("ComputerPlayer2", computerPlTwo);
815 config.writeEntry ("Kepler1", Prefs::kepler1());
816 config.writeEntry ("Kepler2", Prefs::kepler2());
817 config.writeEntry ("Newton1", Prefs::newton1());
818 config.writeEntry ("Newton2", Prefs::newton2());
819 config.writeEntry ("Skill1", Prefs::skill1());
820 config.writeEntry ("Skill2", Prefs::skill2());
821}
822
823void Game::readProperties (const KConfigGroup& config)
824{
825 QStringList list;
826 QString key;
827 int owner, value, maxValue;
828
829 // Dimension must be 3 to 15 (see definition in ai_box.h).
830 int cubeDim = config.readEntry ("CubeDim", minSide);
831 if ((cubeDim < minSide) || (cubeDim > maxSide)) {
832 KMessageBox::sorry (m_view, i18n("The file's cube box size is outside "
833 "the range %1 to %2. It will be set to %1.")
834 .arg(minSide).arg(maxSide));
835 cubeDim = 3;
836 }
837
838 m_side = 1; // Create a new cube box.
839 setDim (cubeDim);
840 reset(); // IDW TODO - NEEDED? Is newGame() init VALID here?
841
842 for (int x = 0; x < m_side; x++) {
843 for (int y = 0; y < m_side; y++) {
844 key.sprintf ("%u,%u", x, y);
845 list = config.readEntry (key, QStringList());
846 // List length must be 2, owner must be 0-2, value >= 1 and <= max().
847 if (list.count() < 2) {
848 KMessageBox::sorry (m_view, i18n("Missing input line for cube %1.")
849 .arg(key));
850 owner = 0;
851 value = 1;
852 }
853 else {
854 owner = list.at(0).toInt();
855 value = list.at(1).toInt();
856 }
857 if ((owner < 0) || (owner > 2)) {
858 KMessageBox::sorry (m_view, i18n("Owner of cube %1 is outside the "
859 "range 0 to 2.").arg(key));
860 owner = 0;
861 }
862 int index = x * m_side + y;
863 maxValue = (owner == 0) ? 1 : m_box->maxValue (index);
864 if ((value < 1) || (value > maxValue)) {
865 KMessageBox::sorry (m_view, i18n("Value of cube %1 is outside the "
866 "range 1 to %2.")
867 .arg(key).arg(maxValue));
868 value = maxValue;
869 }
870 m_view->displayCube (index, (Player) owner, value);
871 m_box->setOwner (index, (Player) owner);
872 m_box->setValue (index, value);
873
874 list.clear();
875 }
876 }
877
878 // Set current player - must be 1 or 2.
879 int onTurn = config.readEntry ("onTurn", 1);
880 if ((onTurn < 1) || (onTurn > 2)) {
881 KMessageBox::sorry (m_view, i18n("Current player is neither 1 nor 2."));
882 onTurn = 1;
883 }
884 m_currentPlayer = (Player) onTurn;
885 emit playerChanged (m_currentPlayer);
886
887 // Restore the game and player settings.
888 loadSavedSettings (config);
889 Prefs::self()->writeConfig();
890 setUpNextTurn();
891}
892
893void Game::loadSavedSettings (const KConfigGroup& config)
894{
895 showSettingsDialog (false); // Load the settings dialog but do not show it.
896 if (m_side != Prefs::cubeDim()) {
897 // Update the size setting for the loaded game.
898 Prefs::setCubeDim (m_side);
899 m_settingsPage->kcfg_CubeDim->setValue (m_side);
900 }
901
902 int pause = config.readEntry ("PauseForComputer", -1);
903 if (pause < 0) { // Older files will not contain more settings,
904 return; // so keep the existing settings.
905 }
906
907 // Load the PauseForComputer and player settings.
908 bool boolValue = pause > 0 ? true : false;
909 Prefs::setPauseForComputer (boolValue);
910 m_settingsPage->kcfg_PauseForComputer->setChecked (boolValue);
911 m_pauseForComputer = boolValue;
912
913 boolValue = config.readEntry ("ComputerPlayer1", false);
914 Prefs::setComputerPlayer1 (boolValue);
915 m_settingsPage->kcfg_ComputerPlayer1->setChecked (boolValue);
916 computerPlOne = boolValue;
917
918 boolValue = config.readEntry ("ComputerPlayer2", true);
919 Prefs::setComputerPlayer2 (boolValue);
920 m_settingsPage->kcfg_ComputerPlayer2->setChecked (boolValue);
921 computerPlTwo = boolValue;
922
923 boolValue = config.readEntry ("Kepler1", true);
924 Prefs::setKepler1 (boolValue);
925 m_settingsPage->kcfg_Kepler1->setChecked (boolValue);
926
927 boolValue = config.readEntry ("Kepler2", true);
928 Prefs::setKepler2 (boolValue);
929 m_settingsPage->kcfg_Kepler2->setChecked (boolValue);
930
931 boolValue = config.readEntry ("Newton1", false);
932 Prefs::setNewton1 (boolValue);
933 m_settingsPage->kcfg_Newton1->setChecked (boolValue);
934
935 boolValue = config.readEntry ("Newton2", false);
936 Prefs::setNewton2 (boolValue);
937 m_settingsPage->kcfg_Newton2->setChecked (boolValue);
938
939 int intValue = config.readEntry ("Skill1", 2);
940 Prefs::setSkill1 (intValue);
941 m_settingsPage->kcfg_Skill1->setValue (intValue);
942
943 intValue = config.readEntry ("Skill2", 2);
944 Prefs::setSkill2 (intValue);
945 m_settingsPage->kcfg_Skill2->setValue (intValue);
946
947 m_ai->setSkill (Prefs::skill1(), Prefs::kepler1(), Prefs::newton1(),
948 Prefs::skill2(), Prefs::kepler2(), Prefs::newton2());
949}
950
951bool Game::isComputer (Player player) const
952{
953 if (player == One)
954 return computerPlOne;
955 else
956 return computerPlTwo;
957}
958
959void Game::animationDone (int index)
960{
961 if (m_activity == ShowingMove) {
962 showingDone (index);
963 }
964 else {
965 stepAnimationDone (index);
966 }
967}
968
969#include "game.moc"
970