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 | |
42 | Game::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 | |
74 | Game::~Game() |
75 | { |
76 | if ((m_activity != Idle) && (m_activity != Aborting)) { |
77 | shutdown(); |
78 | } |
79 | delete m_steps; |
80 | } |
81 | |
82 | void 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 | |
121 | void 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 | |
128 | void 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 | |
145 | void 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 | |
166 | void 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 | |
192 | void 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 | |
210 | void 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 | |
229 | void 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 | |
286 | void 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 | |
302 | void 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 | |
334 | void 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 | |
348 | void 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 | |
376 | void 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 | |
434 | void 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 | |
444 | void 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 | |
459 | Player Game::changePlayer() |
460 | { |
461 | m_currentPlayer = (m_currentPlayer == One) ? Two : One; |
462 | emit playerChanged (m_currentPlayer); |
463 | setUpNextTurn(); |
464 | return m_currentPlayer; |
465 | } |
466 | |
467 | void 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 | |
508 | void 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 | |
534 | void 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 | |
563 | void 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 | |
613 | void 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 | |
653 | void Game::undo() |
654 | { |
655 | bool moreToUndo = undoRedo (-1); |
656 | emit setAction (UNDO, moreToUndo); |
657 | emit setAction (REDO, true); |
658 | } |
659 | |
660 | void Game::redo() |
661 | { |
662 | bool moreToRedo = undoRedo (+1); |
663 | emit setAction (REDO, moreToRedo); |
664 | emit setAction (UNDO, true); |
665 | } |
666 | |
667 | bool 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 | |
708 | void 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 | |
725 | bool 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 | |
759 | void 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 | |
771 | void 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 | |
787 | void 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 | |
823 | void 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 | |
893 | void 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 | |
951 | bool Game::isComputer (Player player) const |
952 | { |
953 | if (player == One) |
954 | return computerPlOne; |
955 | else |
956 | return computerPlTwo; |
957 | } |
958 | |
959 | void 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 | |