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"