File indexing completed on 2024-05-19 07:52:02

0001 /*
0002     This file is part of the game 'KJumpingCube'
0003 
0004     SPDX-FileCopyrightText: 1998-2000 Matthias Kiefer <matthias.kiefer@gmx.de>
0005     SPDX-FileCopyrightText: 2012-2013 Ian Wadham <iandw.au@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "game.h"
0011 #include "kjumpingcube_version.h"
0012 #include "ai_main.h"
0013 #include "ai_box.h"
0014 #include "kcubeboxwidget.h"
0015 #include "settingswidget.h"
0016 
0017 #include "kjumpingcube_debug.h"
0018 #include <QFileDialog>
0019 #include <QTemporaryFile>
0020 
0021 #include <KConfigDialog> // IDW test.
0022 #include <KIO/FileCopyJob>
0023 #include <KIO/StatJob>
0024 #include <KJobWidgets>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 
0028 #include "prefs.h"
0029 
0030 #define LARGE_NUMBER 999999
0031 
0032 Game::Game (const int d, KCubeBoxWidget * view, QWidget * parent)
0033    :
0034    QObject ((QObject *) parent),    // Delete Game when window is deleted.
0035    m_activity           (Idle),
0036    m_waitingState       (Nil),
0037    m_waitingToMove      (false),
0038    m_moveNo             (0),        // Game not started.
0039    m_endMoveNo          (LARGE_NUMBER), // Game not finished.
0040    m_interrupting       (false),
0041    m_newSettings        (false),
0042    m_parent             (parent),
0043    m_view               (view),
0044    m_settingsPage       (nullptr),
0045    m_side               (d),
0046    m_currentPlayer      (One),
0047    m_index              (0),
0048    m_fullSpeed          (false),
0049    computerPlOne        (false),
0050    computerPlTwo        (false),
0051    m_pauseForComputer   (false),
0052    m_pauseForStep       (false)
0053 {
0054    qCDebug(KJUMPINGCUBE_LOG) << "CONSTRUCT Game: side" << m_side;
0055    m_box                = new AI_Box  (this, m_side);
0056    m_ai                 = new AI_Main (this, m_side);
0057    m_steps              = new QList<int>;
0058 
0059    connect(m_view, &KCubeBoxWidget::mouseClick, this, &Game::startHumanMove);
0060    connect(m_ai, &AI_Main::done, this, &Game::moveCalculationDone);
0061    connect(m_view, &KCubeBoxWidget::animationDone, this, &Game::animationDone);
0062 }
0063 
0064 Game::~Game()
0065 {
0066    if ((m_activity != Idle) && (m_activity != Aborting)) {
0067       shutdown();
0068    }
0069    delete m_steps;
0070 }
0071 
0072 void Game::gameActions (const int action)
0073 {
0074    qCDebug(KJUMPINGCUBE_LOG) << "GAME ACTION IS" << action;
0075    if ((m_activity != Idle) && (action != BUTTON) && (action != NEW)) {
0076       m_view->showPopup (i18n("Sorry, doing a move..."));
0077       return;
0078    }
0079 
0080    switch (action) {
0081    case NEW:
0082       newGame();
0083       break;
0084    case HINT:
0085       if (! isComputer (m_currentPlayer)) {
0086          KCubeWidget::enableClicks (false);
0087          computeMove();
0088       }
0089       break;
0090    case BUTTON:
0091       buttonClick();
0092       break;
0093    case UNDO:
0094       undo();
0095       break;
0096    case REDO:
0097       redo();
0098       break;
0099    case SAVE:
0100    case SAVE_AS:
0101       saveGame (action == SAVE_AS);
0102       break;
0103    case LOAD:
0104       loadGame();
0105       break;
0106    default:
0107       break;
0108    }
0109 }
0110 
0111 void Game::showWinner()
0112 {
0113    Q_EMIT buttonChange (false, false, i18n("Game over"));
0114    QString s = i18n("The winner is Player %1!", m_currentPlayer);
0115    KMessageBox::information (m_view, s, i18n("Winner"));
0116 }
0117 
0118 void Game::showSettingsDialog (bool show)
0119 {
0120    // Show the Preferences/Settings/Configuration dialog.
0121    KConfigDialog * settings = KConfigDialog::exists (QStringLiteral("settings"));
0122    if (! settings) {
0123       settings = new KConfigDialog (m_parent, QStringLiteral("settings"), Prefs::self());
0124       settings->setFaceType (KPageDialog::Plain);
0125       SettingsWidget * widget = new SettingsWidget (m_parent);
0126       settings->addPage (widget, i18n("General"), QStringLiteral("games-config-options"));
0127       connect(settings, &KConfigDialog::settingsChanged, this, &Game::newSettings);
0128       m_settingsPage = widget;      // Used when reverting/editing settings.
0129    }
0130    if (! show) return;
0131    settings->show();
0132    settings->raise();           // Force the dialog to be in front.
0133 }
0134 
0135 void Game::newSettings()
0136 {
0137    qCDebug(KJUMPINGCUBE_LOG) << "NEW SETTINGS" << m_newSettings << "m_activity" << m_activity
0138             << "size:" << Prefs::cubeDim() << "m_side" << m_side;
0139    loadImmediateSettings();
0140 
0141    m_newSettings = true;
0142    if (m_side != Prefs::cubeDim()) {
0143       QMetaObject::invokeMethod (this, "newGame", Qt::QueuedConnection);
0144       return;
0145    }
0146    // Waiting to move and not Hinting?
0147    else if (m_waitingToMove && (m_activity == Idle)) {
0148       loadPlayerSettings();
0149       m_newSettings = false;
0150       setUpNextTurn();
0151    }
0152    // Else, the remaining settings will be loaded at the next start of a turn.
0153    // They set computer pause on/off and computer player 1 or 2 on/off.
0154 }
0155 
0156 void Game::loadImmediateSettings()
0157 {
0158    qCDebug(KJUMPINGCUBE_LOG) << "GAME LOAD IMMEDIATE SETTINGS entered";
0159 
0160    // Color changes can take place as soon as control returns to the event loop.
0161    // Changes of animation type or speed will take effect next time there is an
0162    // animation step to do, regardless of current activity.
0163    bool reColorCubes = m_view->loadSettings();
0164    m_fullSpeed = Prefs::animationNone();
0165    m_pauseForStep = Prefs::pauseForStep();
0166    if (reColorCubes) {
0167       Q_EMIT playerChanged (m_currentPlayer);   // Re-display status bar icon.
0168    }
0169 
0170    // Choices of computer AIs and skills will take effect next time there is a
0171    // computer move or hint.  They will not affect any calculation in progress.
0172    m_ai->setSkill (Prefs::skill1(), Prefs::kepler1(), Prefs::newton1(),
0173                    Prefs::skill2(), Prefs::kepler2(), Prefs::newton2());
0174 
0175    qCDebug(KJUMPINGCUBE_LOG) << "m_pauseForStep" << m_pauseForStep;
0176    qCDebug(KJUMPINGCUBE_LOG) << "PLAYER 1 settings: skill" << Prefs::skill1()
0177             << "Kepler" << Prefs::kepler1() << "Newton" << Prefs::newton1();
0178    qCDebug(KJUMPINGCUBE_LOG) << "PLAYER 2 settings: skill" << Prefs::skill2()
0179             << "Kepler" << Prefs::kepler2() << "Newton" << Prefs::newton2();
0180 }
0181 
0182 void Game::loadPlayerSettings()
0183 {
0184    qCDebug(KJUMPINGCUBE_LOG) << "GAME LOAD PLAYER SETTINGS entered";
0185    bool oldComputerPlayer   = isComputer (m_currentPlayer);
0186 
0187    m_pauseForComputer       = Prefs::pauseForComputer();
0188    computerPlOne            = Prefs::computerPlayer1();
0189    computerPlTwo            = Prefs::computerPlayer2();
0190 
0191    qCDebug(KJUMPINGCUBE_LOG) << "AI 1" << computerPlOne << "AI 2" << computerPlTwo
0192             << "m_pauseForComputer" << m_pauseForComputer;
0193 
0194    if (isComputer (m_currentPlayer) && (! oldComputerPlayer)) {
0195       qCDebug(KJUMPINGCUBE_LOG) << "New computer player set: must wait.";
0196       m_waitingState = ComputerToMove;  // New player: don't start playing yet.
0197    }
0198 }
0199 
0200 void Game::startHumanMove (int x, int y)
0201 {
0202    int  index = x * m_side + y;
0203    bool humanPlayer = (! isComputer (m_currentPlayer));
0204    qCDebug(KJUMPINGCUBE_LOG) << "CLICK" << x << y << "index" << index;
0205    if (! humanPlayer) {
0206       buttonClick();
0207    }
0208    else if ((m_currentPlayer == m_box->owner(index)) ||
0209        (m_box->owner(index) == Nobody)) {
0210       m_waitingToMove = false;
0211       m_moveNo++;
0212       m_endMoveNo = LARGE_NUMBER;
0213       qCDebug(KJUMPINGCUBE_LOG) << "doMove (" << index;
0214       KCubeWidget::enableClicks (false);
0215       doMove (index);
0216    }
0217 }
0218 
0219 void Game::setUpNextTurn()
0220 {
0221    // Called from newSettings(), new game, load, change player and undo/redo.
0222    if (m_newSettings) {
0223       m_newSettings = false;
0224       loadPlayerSettings();
0225    }
0226    qCDebug(KJUMPINGCUBE_LOG) << "setUpNextTurn" << m_currentPlayer
0227             << computerPlOne << computerPlTwo << "pause" << m_pauseForComputer
0228             << "wait" << m_waitingState << "waiting" << m_waitingToMove;
0229    if (isComputer (m_currentPlayer)) {
0230       // A computer player is to move.
0231       qCDebug(KJUMPINGCUBE_LOG) << "(m_pauseForComputer || (m_waitingState == ComputerToMove))"
0232                << (m_pauseForComputer || (m_waitingState == ComputerToMove));
0233       if (m_pauseForComputer || (m_waitingState == ComputerToMove) ||
0234           (m_moveNo == 0)) {
0235          m_waitingState = ComputerToMove;
0236      m_waitingToMove = true;
0237          if (computerPlOne && computerPlTwo) {
0238             if (m_moveNo == 0) {
0239                Q_EMIT buttonChange (true, false, i18n("Start game"));
0240             }
0241             else {
0242                Q_EMIT buttonChange (true, false, i18n("Continue game"));
0243             }
0244          }
0245          else {
0246              Q_EMIT buttonChange (true, false, i18n("Start computer move"));
0247          }
0248      // Wait for a button-click to show that the user is ready.
0249      qCDebug(KJUMPINGCUBE_LOG) << "COMPUTER MUST WAIT";
0250          KCubeWidget::enableClicks (true);
0251          return;
0252       }
0253       // Start the computer's move.
0254       qCDebug(KJUMPINGCUBE_LOG) << "COMPUTER MUST MOVE";
0255       m_waitingState = Nil;
0256       m_waitingToMove = false;
0257       KCubeWidget::enableClicks (false);
0258       computeMove();
0259    }
0260    else {
0261       // A human player is to move.
0262       qCDebug(KJUMPINGCUBE_LOG) << "HUMAN TO MOVE";
0263       KCubeWidget::enableClicks (true);
0264       m_waitingState = Nil;
0265       m_waitingToMove = true;
0266       if (computerPlOne || computerPlTwo) {
0267          Q_EMIT buttonChange (false, false, i18n("Your turn"));
0268       }
0269       else {
0270          Q_EMIT buttonChange (false, false, i18n("Player %1", m_currentPlayer));
0271       }
0272       // Wait for a click on the cube to be moved.
0273    }
0274 }
0275 
0276 void Game::computeMove()
0277 {
0278 #if AILog > 1
0279    t.start();           // Start timing the AI calculation.
0280 #endif
0281    m_view->setWaitCursor();
0282    m_activity = Computing;
0283    setStopAction();
0284    Q_EMIT setAction (HINT, false);
0285    if (isComputer (m_currentPlayer)) {
0286        Q_EMIT statusMessage (i18n("Computer player %1 is moving", m_currentPlayer), false);
0287    }
0288    m_ai->getMove (m_currentPlayer, m_box);
0289 }
0290 
0291 void Game::moveCalculationDone (int index)
0292 {
0293    // We do not care if we interrupted the computer.  It was probably taking
0294    // too long, so we will use the best move it had so far.
0295 
0296    // We are starting a new game or closing KJumpingCube.  See shutdown().
0297    if (m_activity == Aborting) {
0298       m_activity = Idle;
0299       return;
0300    }
0301 
0302    m_activity = Idle;
0303    if ((index < 0) || (index >= (m_side * m_side))) {
0304       m_view->setNormalCursor();
0305       KMessageBox::error (m_view,
0306                           i18n ("The computer could not find a valid move."));
0307       // IDW TODO - What to do about state values and BUTTON ???
0308       return;
0309    }
0310 
0311 #if AILog > 1
0312    qCDebug(KJUMPINGCUBE_LOG) << "TIME of MOVE" << t.elapsed();
0313    qCDebug(KJUMPINGCUBE_LOG) << "==============================================================";
0314 #endif
0315 
0316    // Blink the cube to be moved (twice).
0317    m_view->startAnimation (false, index);
0318 
0319    m_activity = ShowingMove;
0320    setStopAction();
0321 }
0322 
0323 void Game::showingDone (int index)
0324 {
0325    if (isComputer (m_currentPlayer)) {
0326       m_moveNo++;
0327       m_endMoveNo = LARGE_NUMBER;
0328       qCDebug(KJUMPINGCUBE_LOG) << "m_moveNo" << m_moveNo << "isComputer()" << (isComputer (m_currentPlayer));
0329       doMove (index);           // Animate computer player's move.
0330    }
0331    else {
0332       moveDone();           // Finish Hint animation.
0333       setUpNextTurn();          // Wait: unless player setting changed.
0334    }
0335 }
0336 
0337 void Game::doMove (int index)
0338 {
0339    bool computerMove = ((computerPlOne && m_currentPlayer == One) ||
0340                         (computerPlTwo && m_currentPlayer == Two));
0341 
0342    // Make a copy of the position and player to move for the Undo function.
0343    m_box->copyPosition (m_currentPlayer, computerMove, index);
0344 
0345 #if AILog > 0
0346    if (! computerMove) { // Record a human move in the statistics.
0347       m_ai->postMove (m_currentPlayer, index, m_side);
0348    }
0349 #endif
0350    Q_EMIT setAction (UNDO, true);   // Update Undo and Redo actions.
0351    Q_EMIT setAction (REDO, false);
0352    m_steps->clear();
0353 #if AILog > 1
0354    bool won =
0355 #endif
0356    m_box->doMove (m_currentPlayer, index, nullptr, m_steps);
0357 #if AILog > 1
0358    qCDebug(KJUMPINGCUBE_LOG) << "GAME WON?" << won << "STEPS" << (* m_steps);
0359    // m_box->printBox();
0360 #endif
0361    if (m_steps->count() > 1) {
0362       m_view->setWaitCursor();  //This will be a stoppable animation.
0363    }
0364    m_activity = AnimatingMove;
0365    doStep();
0366 }
0367 
0368 void Game::doStep()
0369 {
0370    // Re-draw all cubes affected by a move, proceeding one step at a time.
0371    int index;
0372    bool startStep = true;
0373    do {
0374       if (! m_steps->isEmpty()) {
0375          index = m_steps->takeFirst();      // Get a cube to be re-drawn.
0376 
0377          // Check if the player wins at this step (no more cubes are re-drawn).
0378          if (index == 0) {
0379             moveDone();
0380         m_endMoveNo = m_moveNo;
0381 #if AILog > 0
0382         qCDebug(KJUMPINGCUBE_LOG) << "\nCALLING dumpStats()";
0383         m_ai->dumpStats();
0384 #endif
0385         showWinner();
0386             return;
0387          }
0388 
0389          // Update the view of a cube, either immediately or via animation.
0390          startStep = (index > 0);       // + -> increment, - -> expand.
0391          index = startStep ? (index - 1) : (-index - 1);
0392          int value = m_view->cubeValue (index);     // Pre-update in view.
0393          int max   = m_box->maxValue (index);
0394          if (startStep) {               // Add 1 and take.
0395         m_view->displayCube (index, m_currentPlayer, value + 1);
0396             if ((value >= max) && (! m_fullSpeed)) {
0397                m_view->highlightCube (index, true);
0398             }
0399          }
0400          else if (m_fullSpeed) {            // Decrease immediately.
0401         m_view->displayCube (index, m_currentPlayer, value - max);
0402         m_view->highlightCube (index, false);   // Maybe user hit Stop.
0403          }
0404          else {                     // Animate cascade step.
0405             if (m_pauseForStep && (m_waitingState != WaitingToStep)) {
0406                // Pause: return the step to the list and wait for a buttonClick.
0407                m_steps->prepend (-index - 1);
0408                m_waitingState = WaitingToStep;
0409                Q_EMIT buttonChange (true, false, i18n("Show next step"));
0410                return;
0411             }
0412         // Now set the button up and start the animation.
0413             setStopAction();
0414             m_view->startAnimation (true, index);
0415          }
0416       }
0417       else {
0418          // Views of the cubes at all steps of the move have been updated.
0419          moveDone();
0420          changePlayer();
0421          return;
0422       }
0423    } while (startStep || m_fullSpeed);
0424 }
0425 
0426 void Game::stepAnimationDone (int index)
0427 {
0428    // Finished a move step.  Decrease the cube that expanded and display it.
0429    int value = m_view->cubeValue (index);
0430    int max   = m_box->maxValue (index);
0431    m_view->displayCube (index, m_currentPlayer, value - max);
0432 
0433    doStep();                // Do next animation step (if any).
0434 }
0435 
0436 void Game::moveDone()
0437 {
0438    // Called after non-animated move, animated move, end of game or hint action.
0439    m_view->setNormalCursor();
0440    Q_EMIT statusMessage (QLatin1String(""), false); // Clear the status bar.
0441    m_activity = Idle;
0442    setAction (HINT, true);
0443    m_fullSpeed = Prefs::animationNone();
0444    m_view->hidePopup();
0445    if (m_interrupting) {
0446       m_interrupting = false;
0447       m_waitingState = ComputerToMove;
0448    }
0449 }
0450 
0451 Player Game::changePlayer()
0452 {
0453    m_currentPlayer = (m_currentPlayer == One) ? Two : One;
0454    Q_EMIT playerChanged (m_currentPlayer);
0455    setUpNextTurn();
0456    return m_currentPlayer;
0457 }
0458 
0459 void Game::buttonClick()
0460 {
0461    qCDebug(KJUMPINGCUBE_LOG) << "BUTTON CLICK seen: waiting" << m_waitingToMove
0462             << "m_activity" << m_activity << "m_waitingState" << m_waitingState;
0463    if (m_waitingState == Nil) {     // Button is red: stop an activity.
0464       if ((! m_pauseForComputer) && (! m_interrupting) &&
0465           (computerPlOne && computerPlTwo)) {
0466          m_interrupting = true;     // Interrupt a non-stop AI v AI game.
0467      m_view->showPopup (i18n("Finishing move..."));
0468      setStopAction();       // Change to text for current activity.
0469       }
0470       else if (m_activity == Computing) {
0471          m_ai->stop();          // Stop calculating a move or hint.
0472          m_activity = Stopping;
0473       }
0474       else if (m_activity == ShowingMove) {
0475      int index = m_view->killAnimation();
0476          showingDone (index);       // Stop showing where a move or hint is.
0477      m_view->highlightCube (index, false);
0478       }
0479       else if (m_activity == AnimatingMove) {
0480      int index = m_view->killAnimation();
0481          m_fullSpeed = true;        // Go to end of move right now, skipping
0482          stepAnimationDone (index); // all later steps of animation.
0483       }
0484    }
0485    else {               // Button is green: start an activity.
0486       switch (m_waitingState) {
0487       case WaitingToStep:
0488      doStep();          // Start next animation step.
0489          break;
0490       case ComputerToMove:
0491          computeMove();         // Start next computer move.
0492          break;
0493       default:
0494          break;
0495       }
0496       m_waitingState = Nil;
0497    }
0498 }
0499 
0500 void Game::setStopAction()
0501 {
0502    // Red button setting for non-stop AI v. AI game.
0503    if ((! m_pauseForComputer) && (! m_interrupting) &&
0504        (computerPlOne && computerPlTwo)) {
0505       if (m_activity == Computing) {        // Starting AI v. AI move.
0506          Q_EMIT buttonChange (true, true, i18n("Interrupt game"));
0507       }
0508       return;                   // Continuing AI v. AI move.
0509    }
0510    // Red button settings for AI v. human, two human players or pausing game.
0511    if (m_activity == Computing) {       // Calculating hint or AI move.
0512       Q_EMIT buttonChange (true, true, i18n("Stop computing"));
0513    }
0514    else if (m_activity == ShowingMove) {    // Showing hint or AI move.
0515       Q_EMIT buttonChange (true, true, i18n("Stop showing move"));
0516    }
0517    else if (m_activity == AnimatingMove) {  // Animating AI or human move.
0518       Q_EMIT buttonChange (true, true, i18n("Stop animation"));
0519    }
0520 }
0521 
0522 /* ************************************************************************** */
0523 /*                           STANDARD GAME ACTIONS                            */
0524 /* ************************************************************************** */
0525 
0526 void Game::newGame()
0527 {
0528    qCDebug(KJUMPINGCUBE_LOG) << "NEW GAME entered: waiting" << m_waitingToMove
0529             << "won?" << (m_moveNo >= m_endMoveNo);
0530    if (newGameOK()) {
0531       qCDebug(KJUMPINGCUBE_LOG) << "QDEBUG: newGameOK() =" << true;
0532       shutdown();           // Stop the current move (if any).
0533       m_view->setNormalCursor();
0534       m_view->hidePopup();
0535       loadImmediateSettings();
0536       loadPlayerSettings();
0537       m_newSettings = false;
0538       qCDebug(KJUMPINGCUBE_LOG) << "newGame() loadSettings DONE: waiting" << m_waitingToMove
0539                << "won?" << (m_moveNo >= m_endMoveNo)
0540                << "move" << m_moveNo << m_endMoveNo;
0541       qCDebug(KJUMPINGCUBE_LOG) << "setDim (" << Prefs::cubeDim() << ") m_side" << m_side;
0542       setDim (Prefs::cubeDim());
0543       qCDebug(KJUMPINGCUBE_LOG) << "Entering reset();";
0544       reset();              // Clear cubebox, initialise states.
0545       Q_EMIT setAction (UNDO, false);
0546       Q_EMIT setAction (REDO, false);
0547       Q_EMIT statusMessage (i18n("New Game"), false);
0548       m_moveNo = 0;
0549       m_endMoveNo = LARGE_NUMBER;
0550       setUpNextTurn();
0551    }
0552    else qCDebug(KJUMPINGCUBE_LOG) << "QDEBUG: newGameOK() =" << false;
0553 }
0554 
0555 void Game::saveGame (bool saveAs)
0556 {
0557    if (saveAs || m_gameURL.isEmpty()) {
0558       bool isOtherUrlWanted = false;
0559       QUrl url;
0560 
0561       do {
0562          url = QFileDialog::getSaveFileUrl (m_view, QString(), m_gameURL, QStringLiteral("*.kjc"), nullptr, QFileDialog::DontConfirmOverwrite);
0563 
0564          if (url.isEmpty())
0565             return;
0566 
0567          // check filename
0568          if (! url.fileName().endsWith(QStringLiteral(".kjc"))) {
0569             url.setPath(url.path() + QLatin1String(".kjc"));
0570          }
0571 
0572          KIO::StatJob* statJob = KIO::stat(url, KIO::StatJob::DestinationSide, KIO::StatNoDetails);
0573          KJobWidgets::setWindow(statJob, m_view);
0574          if (statJob->exec()) {
0575             QString mes=i18n("The file %1 already exists.\n"
0576                "Do you want to overwrite it?", url.url());
0577             const int result = KMessageBox::warningContinueCancel
0578                (m_view, mes, QString(), KStandardGuiItem::overwrite());
0579             // TODO: instead of default "Cancel" use "Select Another URL" or similar
0580             isOtherUrlWanted = (result == KMessageBox::Cancel);
0581          }
0582       } while (isOtherUrlWanted);
0583 
0584       m_gameURL = url;
0585    }
0586 
0587    QTemporaryFile tempFile;
0588    tempFile.open();
0589    KConfig config (tempFile.fileName(), KConfig::SimpleConfig);
0590    KConfigGroup main (&config, QStringLiteral("KJumpingCube"));
0591    main.writeEntry ("Version", KJUMPINGCUBE_VERSION_STRING);
0592    KConfigGroup game (&config, QStringLiteral("Game"));
0593    saveProperties (game);
0594    config.sync();
0595 
0596    KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(tempFile.fileName()), m_gameURL, -1, KIO::Overwrite);
0597    KJobWidgets::setWindow(job, m_view);
0598    job->exec();
0599    if (! job->error() ) {
0600       Q_EMIT statusMessage (i18n("Game saved as %1", m_gameURL.url()), false);
0601    }
0602    else {
0603       KMessageBox::error (m_view, i18n("There was an error in saving file\n%1",
0604                                        m_gameURL.url()));
0605    }
0606 }
0607 
0608 void Game::loadGame()
0609 {
0610    bool fileOk=true;
0611    QUrl url;
0612 
0613    do {
0614       url = QFileDialog::getOpenFileUrl (m_view, QString(), m_gameURL, QStringLiteral("*.kjc"));
0615       if (url.isEmpty())
0616          return;
0617       KIO::StatJob* statJob = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatNoDetails);
0618 
0619       KJobWidgets::setWindow(statJob, m_view);
0620       if (! statJob->exec()) {
0621          QString mes = i18n("The file %1 does not exist!", url.url());
0622          KMessageBox::error (m_view, mes);
0623          fileOk = false;
0624       }
0625    } while (! fileOk);
0626 
0627    QTemporaryFile tempFile;
0628    tempFile.open();
0629    KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tempFile.fileName()), -1, KIO::Overwrite);
0630    KJobWidgets::setWindow(job, m_view);
0631    job->exec();
0632    if (! job->error() ) {
0633       KConfig config( tempFile.fileName(), KConfig::SimpleConfig);
0634       KConfigGroup main (&config, QStringLiteral("KJumpingCube"));
0635       if (! main.hasKey ("Version")) {
0636          QString mes = i18n("The file %1 is not a KJumpingCube gamefile!",
0637                             url.url());
0638          KMessageBox::error (m_view, mes);
0639          return;
0640       }
0641 
0642       m_gameURL = url;
0643       KConfigGroup game (&config, QStringLiteral("Game"));
0644       readProperties (game);
0645 
0646       Q_EMIT setAction (UNDO, false);
0647    }
0648    else
0649       KMessageBox::error (m_view, i18n("There was an error loading file\n%1",
0650                                        url.url()));
0651 }
0652 
0653 void Game::undo()
0654 {
0655    bool moreToUndo = undoRedo (-1);
0656    Q_EMIT setAction (UNDO, moreToUndo);
0657    Q_EMIT setAction (REDO, true);
0658 }
0659 
0660 void Game::redo()
0661 {
0662    bool moreToRedo = undoRedo (+1);
0663    Q_EMIT setAction (REDO, moreToRedo);
0664    Q_EMIT setAction (UNDO, true);
0665 }
0666 
0667 bool Game::newGameOK()
0668 {
0669    if ((m_moveNo == 0) || (m_moveNo >= m_endMoveNo)) {
0670       // OK: game finished or not yet started.  Settings might have changed.
0671       return true;
0672    }
0673 
0674    // Check if it is OK to abandon the current game.
0675    QString query;
0676    if (m_side != Prefs::cubeDim()) {
0677       query = i18n("You have changed the size setting of the game and "
0678                    "that requires a new game to start.\n\n"
0679                    "Do you wish to abandon the current game or continue "
0680                    "playing and restore the previous size setting?");
0681    }
0682    else {
0683       query = i18n("You have requested a new game, but "
0684                    "there is already a game in progress.\n\n"
0685                    "Do you wish to abandon the current game?");
0686    }
0687    qCDebug(KJUMPINGCUBE_LOG) << "QUERY:" << query;
0688    int reply = KMessageBox::questionTwoActions (m_view,
0689                                            query, i18n("New Game?"),
0690                                            KGuiItem (i18n("Abandon Game")),
0691                                            KGuiItem (i18n("Continue Game")));
0692    if (reply == KMessageBox::PrimaryAction) {
0693       qCDebug(KJUMPINGCUBE_LOG) << "ABANDON GAME";
0694       return true;          // Start a new game.
0695    }
0696    if (m_side != Prefs::cubeDim()) {
0697       // Restore the setting: also the dialog-box copy if it has been created.
0698       qCDebug(KJUMPINGCUBE_LOG) << "Reset size" << Prefs::cubeDim() << "back to" << m_side;
0699       Prefs::setCubeDim (m_side);
0700       if (m_settingsPage) {
0701           m_settingsPage->kcfg_CubeDim->setValue (m_side);
0702       }
0703       Prefs::self()->save();
0704    }
0705    qCDebug(KJUMPINGCUBE_LOG) << "CONTINUE GAME";
0706    return false;            // Continue the current game.
0707 }
0708 
0709 void Game::reset()
0710 {
0711    m_view->reset();
0712    m_box->clear();
0713 
0714    m_fullSpeed = Prefs::animationNone();    // Animate cascade moves?
0715 
0716    m_currentPlayer = One;
0717 
0718    m_waitingState   = computerPlOne ? ComputerToMove : Nil;
0719    qCDebug(KJUMPINGCUBE_LOG) << "RESET: activity" << m_activity << "wait" << m_waitingState;
0720 
0721 #if AILog > 0
0722    m_ai->startStats();
0723 #endif
0724 }
0725 
0726 bool Game::undoRedo (int change)
0727 {
0728    Player oldPlayer = m_currentPlayer;
0729 
0730    bool isAI = false;
0731    int  index = 0;
0732    bool moreToDo = (change < 0) ?
0733       m_box->undoPosition (m_currentPlayer, isAI, index) :
0734       m_box->redoPosition (m_currentPlayer, isAI, index);
0735 
0736    // Update the cube display after an undo or redo.
0737    for (int n = 0; n < (m_side * m_side); n++) {
0738       m_view->displayCube (n, m_box->owner (n), m_box->value (n));
0739       m_view->highlightCube (n, false);
0740    }
0741    m_view->timedCubeHighlight (index);      // Show which cube was moved.
0742 
0743    m_moveNo = m_moveNo + change;
0744    if (m_moveNo < m_endMoveNo) {
0745       if (oldPlayer != m_currentPlayer) {
0746          Q_EMIT playerChanged (m_currentPlayer);
0747       }
0748       m_waitingState = isComputer (m_currentPlayer) ? ComputerToMove
0749                                                     : m_waitingState;
0750       setUpNextTurn();
0751    }
0752    else {                   // End of game: show winner.
0753       moreToDo = false;
0754       m_currentPlayer = oldPlayer;
0755       showWinner();
0756    }
0757    return moreToDo;
0758 }
0759 
0760 void Game::setDim (int d)
0761 {
0762    if (d != m_side) {
0763       shutdown();
0764       delete m_box;
0765       m_box   = new AI_Box (this, d);
0766       qCDebug(KJUMPINGCUBE_LOG) << "AI_Box CONSTRUCTED by Game::setDim()";
0767       m_side  = d;
0768       m_view->setDim (d);
0769    }
0770 }
0771 
0772 void Game::shutdown()
0773 {
0774    // Shut down gracefully, avoiding a possible crash when the user hits Quit.
0775    m_view->killAnimation(); // Stop animation immediately (if active).
0776    if (m_activity == Computing) {
0777       m_ai->stop();     // Stop AI ASAP (moveCalculationDone() => Idle).
0778       m_activity = Aborting;
0779    }
0780    else if (m_activity == Stopping) {
0781       m_activity = Aborting;
0782    }
0783    else if (m_activity != Aborting) {
0784       m_activity = Idle;    // In case it was ShowingMove or AnimatingMove.
0785    }
0786 }
0787 
0788 void Game::saveProperties (KConfigGroup & config)
0789 {
0790    // Save the current player.
0791    config.writeEntry ("onTurn", (int) m_currentPlayer);
0792 
0793    QStringList list;
0794    QString owner, value, key;
0795 
0796    // Save the position currently on the board.
0797    for (int x = 0; x < m_side; x++) {
0798      for (int y = 0; y < m_side; y++) {
0799         key = QString::asprintf ("%u,%u", x, y);
0800     int index = x * m_side + y;
0801     owner = QString::asprintf ("%u", m_box->owner (index));
0802     value = QString::asprintf ("%u", m_box->value (index));
0803     list.append (owner);
0804     list.append (value);
0805     config.writeEntry (key, list);
0806 
0807     list.clear();
0808      }
0809    }
0810 
0811    // Save the game and player settings.
0812    config.writeEntry ("CubeDim",          m_side);
0813    config.writeEntry ("PauseForComputer", m_pauseForComputer ? 1 : 0);
0814    config.writeEntry ("ComputerPlayer1",  computerPlOne);
0815    config.writeEntry ("ComputerPlayer2",  computerPlTwo);
0816    config.writeEntry ("Kepler1",          Prefs::kepler1());
0817    config.writeEntry ("Kepler2",          Prefs::kepler2());
0818    config.writeEntry ("Newton1",          Prefs::newton1());
0819    config.writeEntry ("Newton2",          Prefs::newton2());
0820    config.writeEntry ("Skill1",           Prefs::skill1());
0821    config.writeEntry ("Skill2",           Prefs::skill2());
0822 }
0823 
0824 void Game::readProperties (const KConfigGroup& config)
0825 {
0826   QStringList list;
0827   QString     key;
0828   int         owner, value, maxValue;
0829 
0830   // Dimension must be 3 to 15 (see definition in ai_box.h).
0831   int cubeDim = config.readEntry ("CubeDim", minSide);
0832   if ((cubeDim < minSide) || (cubeDim > maxSide)) {
0833      KMessageBox::error (m_view, i18n("The file's cube box size is outside "
0834                                     "the range %1 to %2. It will be set to %1.",
0835                                     minSide, maxSide));
0836      cubeDim = 3;
0837   }
0838 
0839   m_side = 1;                   // Create a new cube box.
0840   setDim (cubeDim);
0841   reset();      // IDW TODO - NEEDED? Is newGame() init VALID here?
0842 
0843   for (int x = 0; x < m_side; x++) {
0844     for (int y = 0; y < m_side; y++) {
0845     key = QString::asprintf ("%u,%u", x, y);
0846     list = config.readEntry (key, QStringList());
0847     // List length must be 2, owner must be 0-2, value >= 1 and <= max().
0848     if (list.count() < 2) {
0849         KMessageBox::error (m_view, i18n("Missing input line for cube %1.", key));
0850         owner = 0;
0851         value = 1;
0852     }
0853     else {
0854         owner = list.at(0).toInt();
0855         value = list.at(1).toInt();
0856     }
0857     if ((owner < 0) || (owner > 2)) {
0858         KMessageBox::error (m_view, i18n("Owner of cube %1 is outside the "
0859                                            "range 0 to 2.", key));
0860         owner = 0;
0861     }
0862     int index = x * m_side + y;
0863     maxValue = (owner == 0) ? 1 : m_box->maxValue (index);
0864     if ((value < 1) || (value > maxValue)) {
0865         KMessageBox::error (m_view, i18n("Value of cube %1 is outside the "
0866                                            "range 1 to %2.", key, maxValue));
0867         value = maxValue;
0868     }
0869     m_view->displayCube (index, (Player) owner, value);
0870     m_box->setOwner (index, (Player) owner);
0871     m_box->setValue (index, value);
0872 
0873     list.clear();
0874     }
0875   }
0876 
0877    // Set current player - must be 1 or 2.
0878    int onTurn = config.readEntry ("onTurn", 1);
0879    if ((onTurn < 1) || (onTurn > 2)) {
0880        KMessageBox::error (m_view, i18n("Current player is neither 1 nor 2."));
0881        onTurn = 1;
0882    }
0883    m_currentPlayer = (Player) onTurn;
0884    Q_EMIT playerChanged (m_currentPlayer);
0885 
0886    // Restore the game and player settings.
0887    loadSavedSettings (config);
0888    Prefs::self()->save();
0889    setUpNextTurn();
0890 }
0891 
0892 void Game::loadSavedSettings (const KConfigGroup& config)
0893 {
0894    showSettingsDialog (false);  // Load the settings dialog but do not show it.
0895    if (m_side != Prefs::cubeDim()) {
0896       // Update the size setting for the loaded game.
0897       Prefs::setCubeDim (m_side);
0898       m_settingsPage->kcfg_CubeDim->setValue (m_side);
0899    }
0900 
0901    int pause = config.readEntry ("PauseForComputer", -1);
0902    if (pause < 0) {     // Older files will not contain more settings,
0903       return;           // so keep the existing settings.
0904    }
0905 
0906    // Load the PauseForComputer and player settings.
0907    bool boolValue = pause > 0 ? true : false;
0908    Prefs::setPauseForComputer (boolValue);
0909    m_settingsPage->kcfg_PauseForComputer->setChecked (boolValue);
0910    m_pauseForComputer = boolValue;
0911 
0912    boolValue = config.readEntry ("ComputerPlayer1", false);
0913    Prefs::setComputerPlayer1 (boolValue);
0914    m_settingsPage->kcfg_ComputerPlayer1->setChecked (boolValue);
0915    computerPlOne = boolValue;
0916 
0917    boolValue = config.readEntry ("ComputerPlayer2", true);
0918    Prefs::setComputerPlayer2 (boolValue);
0919    m_settingsPage->kcfg_ComputerPlayer2->setChecked (boolValue);
0920    computerPlTwo = boolValue;
0921 
0922    boolValue = config.readEntry ("Kepler1", true);
0923    Prefs::setKepler1 (boolValue);
0924    m_settingsPage->kcfg_Kepler1->setChecked (boolValue);
0925 
0926    boolValue = config.readEntry ("Kepler2", true);
0927    Prefs::setKepler2 (boolValue);
0928    m_settingsPage->kcfg_Kepler2->setChecked (boolValue);
0929 
0930    boolValue = config.readEntry ("Newton1", false);
0931    Prefs::setNewton1 (boolValue);
0932    m_settingsPage->kcfg_Newton1->setChecked (boolValue);
0933 
0934    boolValue = config.readEntry ("Newton2", false);
0935    Prefs::setNewton2 (boolValue);
0936    m_settingsPage->kcfg_Newton2->setChecked (boolValue);
0937 
0938    int intValue = config.readEntry ("Skill1", 2);
0939    Prefs::setSkill1 (intValue);
0940    m_settingsPage->kcfg_Skill1->setValue (intValue);
0941 
0942    intValue = config.readEntry ("Skill2", 2);
0943    Prefs::setSkill2 (intValue);
0944    m_settingsPage->kcfg_Skill2->setValue (intValue);
0945 
0946    m_ai->setSkill (Prefs::skill1(), Prefs::kepler1(), Prefs::newton1(),
0947                    Prefs::skill2(), Prefs::kepler2(), Prefs::newton2());
0948 }
0949 
0950 bool Game::isComputer (Player player) const
0951 {
0952    if (player == One)
0953       return computerPlOne;
0954    else
0955       return computerPlTwo;
0956 }
0957 
0958 void Game::animationDone (int index)
0959 {
0960    if (m_activity == ShowingMove) {
0961       showingDone (index);
0962    }
0963    else {
0964       stepAnimationDone (index);
0965    }
0966 }
0967 
0968 #include "moc_game.cpp"