/* ******************************************************************* ******************************************************************* * * * KREVERSI * * ******************************************************************* * * A Reversi (or sometimes called Othello) game * ******************************************************************* * * created 1997 by Mario Weilguni * ******************************************************************* * * This file is part of the KDE project "KREVERSI" * * KREVERSI is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KREVERSI is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KREVERSI; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ******************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Score.h" #include "kreversi.h" // Automatically generated headers #include "prefs.h" #include "settings.h" #include "kreversi.moc" // ================================================================ // class KReversi #ifndef PICDATA #define PICDATA(x) \ TDEGlobal::dirs()->findResource("appdata", TQString("pics/") + x) #endif KReversi::KReversi() : KZoomMainWindow(10, 300, 5, "kreversi"), m_gameOver(false) { TQWidget *w; TQGridLayout *top; KNotifyClient::startDaemon(); // The game. m_game = new QReversiGame(); m_cheating = false; m_gameOver = false; m_humanColor = Black; // The Engine m_engine = new Engine(); setStrength(1); // The visual stuff w = new TQWidget(this); setCentralWidget(w); top = new TQGridLayout(w, 2, 2); // The reversi game view. m_gameView = new QReversiGameView(w, m_game); top->addMultiCellWidget(m_gameView, 0, 1, 0, 0); // Populate the GUI. createTDEActions(); addWidget(m_gameView); // Connect the signals from the game with slots of the view // // The only part of the view that is left in this class is the // indicator of whose turn it is in the status bar. The rest is // in the game view. connect(m_game, TQ_SIGNAL(sig_newGame()), this, TQ_SLOT(showTurn())); connect(m_game, TQ_SIGNAL(sig_move(uint, Move&)), this, TQ_SLOT(handleMove(uint, Move&))); // Calls showTurn(). connect(m_game, TQ_SIGNAL(sig_update()), this, TQ_SLOT(showTurn())); connect(m_game, TQ_SIGNAL(sig_gameOver()), this, TQ_SLOT(slotGameOver())); // Signal that is sent when the user clicks on the board. connect(m_gameView, TQ_SIGNAL(signalSquareClicked(int, int)), this, TQ_SLOT(slotSquareClicked(int, int))); loadSettings(); setupGUI(); init("popup"); m_gameView->start(); slotNewGame(); } KReversi::~KReversi() { delete m_game; delete m_engine; } // Create all TDEActions used in KReversi. // void KReversi::createTDEActions() { // Standard Game Actions. KStdGameAction::gameNew(this, TQ_SLOT(slotNewGame()), actionCollection(), "game_new"); KStdGameAction::load(this, TQ_SLOT(slotOpenGame()), actionCollection()); KStdGameAction::save(this, TQ_SLOT(slotSave()), actionCollection()); KStdGameAction::quit(this, TQ_SLOT(close()), actionCollection()); KStdGameAction::hint(this, TQ_SLOT(slotHint()), actionCollection(), "game_hint"); KStdGameAction::undo(this, TQ_SLOT(slotUndo()), actionCollection(), "game_undo"); // Non-standard Game Actions: Stop, Continue, Switch sides stopAction = new TDEAction(i18n("&Stop Thinking"), "game_stop", TQt::Key_Escape, this, TQ_SLOT(slotInterrupt()), actionCollection(), "game_stop"); continueAction = new TDEAction(i18n("&Continue Thinking"), "reload", 0, this, TQ_SLOT(slotContinue()), actionCollection(), "game_continue"); new TDEAction(i18n("S&witch Sides"), 0, 0, this, TQ_SLOT(slotSwitchSides()), actionCollection(), "game_switch_sides"); // Some more standard game actions: Highscores, Settings. KStdGameAction::highscores(this, TQ_SLOT(showHighScoreDialog()), actionCollection()); KStdAction::preferences(this, TQ_SLOT(slotEditSettings()), actionCollection()); // Actions for the view(s). showLastMoveAction = new TDEToggleAction(i18n("Show Last Move"), "lastmoves", 0, this, TQ_SLOT(slotShowLastMove()), actionCollection(), "show_last_move"); showLegalMovesAction = new TDEToggleAction(i18n("Show Legal Moves"), "legalmoves", 0, this, TQ_SLOT(slotShowLegalMoves()), actionCollection(), "show_legal_moves"); } // ---------------------------------------------------------------- // Methods for the engine // Set the strength for the engine. We keep track of the lowest // strength that was used during a game and if the user wins, this is // the strength that we will store in the highscore file. void KReversi::setStrength(uint strength) { // FIXME: 7 should be MAXSTRENGTH or something similar. Q_ASSERT( 1 <= strength && strength <= 7 ); strength = TQMAX(TQMIN(strength, 7), 1); m_engine->setStrength(strength); if (m_lowestStrength < strength) m_lowestStrength = strength; KExtHighscore::setGameType(m_lowestStrength-1); } // ---------------------------------------------------------------- // Slots for TDEActions // A slot that is called when the user wants a new game. // void KReversi::slotNewGame() { // If already playing, ask the player if he wants to abort the old game. if ( isPlaying() ) { if (KMessageBox ::warningYesNo(0, i18n("You are already running an unfinished game. " "If you abort the old game to start a new one, " "the old game will be registered as a loss in " "the highscore file.\n" "What do you want to do?"), i18n("Abort Current Game?"), i18n("Abort Old Game"), i18n("Continue Old Game")) == KMessageBox::No) return; KExtHighscore::submitScore(KExtHighscore::Lost, this); } m_gameOver = false; m_cheating = false; m_game->newGame(); m_competitiveGame = Prefs::competitiveGameChoice(); m_lowestStrength = strength(); //kdDebug() << "Competitive: " << m_competitiveGame << endl; // Set the state to waiting for the humans move. setState(Ready); // Black always makes first move. if (m_humanColor == White) computerMakeMove(); } // Open an earlier saved game from the config file. // // FIXME: Should give a choice to load from an ordinary file (SGF?) // void KReversi::slotOpenGame() { TDEConfig *config = kapp->config(); config->setGroup("Savegame"); if (loadGame(config)) Prefs::setSkill(m_engine->strength()); m_gameView->setHumanColor(humanColor()); } // Save a game to the config file. // // FIXME: Should give a choice to save as an ordinary file (SGF?) // void KReversi::slotSave() { TDEConfig *config = kapp->config(); config->setGroup("Savegame"); saveGame(config); KMessageBox::information(this, i18n("Game saved.")); } void KReversi::slotHint() { Move move; if (state() != Ready) return; setState(Thinking); move = m_engine->computeMove(m_game, m_competitiveGame); setState(Hint); m_gameView->showHint(move); setState(Ready); } // Takes back last set of moves // void KReversi::slotUndo() { if (state() != Ready) return; // Can't undo anything if no moves are made. if (m_game->moveNumber() == 0) return; // Undo all moves of the same color as the last one. Color last_color = m_game->lastMove().color(); while (m_game->moveNumber() != 0 && last_color == m_game->lastMove().color()) { m_game->undoMove(); m_gameView->removeMove(m_game->moveNumber()); } // Take back one more move. if (m_game->moveNumber() > 0) { m_game->undoMove(); m_gameView->removeMove(m_game->moveNumber()); // FIXME: Call some method in m_gameView. m_gameView->setCurrentMove(m_game->moveNumber() - 1); } if (m_game->toMove() == computerColor()) { // Must repaint so that the new move is not shown before the old // one is removed on the screen. m_gameView->repaint(); computerMakeMove(); } else m_gameView->update(); } // Interrupt thinking of game engine. // void KReversi::slotInterrupt() { m_engine->setInterrupt(TRUE); // Indicate that the computer was interrupted. showTurn(); } // Continues a move if it was interrupted earlier. // void KReversi::slotContinue() { if (interrupted()) computerMakeMove(); } // Turn on or off showing of legal moves in the board view. void KReversi::slotShowLastMove() { m_gameView->setShowLastMove(showLastMoveAction->isChecked()); } // Turn on or off showing of legal moves in the board view. void KReversi::slotShowLegalMoves() { m_gameView->setShowLegalMoves(showLegalMovesAction->isChecked()); } void KReversi::slotSwitchSides() { if (state() != Ready) return; if (interrupted()) { KMessageBox::information(this, i18n("You cannot switch sides in the middle of the computer's move."), i18n("Notice")); return; } // It's ok to change sides before the first move. if (m_game->moveNumber() != 0) { int res = KMessageBox::warningContinueCancel(this, i18n("If you switch side, your score will not be added to the highscores."), TQString(), TQString(), "switch_side_warning"); if ( res==KMessageBox::Cancel ) return; m_cheating = true; } m_humanColor = opponent(m_humanColor); // Update the human color in the window. m_gameView->setHumanColor(m_humanColor); kapp->processEvents(); computerMakeMove(); } // ---------------------------------------------------------------- // Slots for the game IO // Handle mouse clicks. // void KReversi::slotSquareClicked(int row, int col) { // Can't move when it is the computers turn. if ( interrupted() ) { illegalMove(); return; } if (state() == Ready) humanMakeMove(row, col); else if (state() == Hint) { m_gameView->quitHint(); setState(Ready); } else illegalMove(); } // ---------------------------------------------------------------- // Slots for the game view // Show the move in the move view. // FIXME: Move this to the gameview. void KReversi::handleMove(uint /*moveno*/, Move& /*move*/) { showTurn(); } // A slot that is called when it is time to show whose turn it is. // void KReversi::showTurn() { showTurn(m_game->toMove()); } void KReversi::showTurn(Color color) { // If we are not playing, do nothing. if (m_gameOver) return; if (color == humanColor()) statusBar()->message(i18n("Your turn")); else if (color == computerColor()) { TQString message = i18n("Computer's turn"); // We can't use the interrupted() test here since we might be in a // middle state when called from slotInterrupt(). if (m_state == Thinking) message += i18n(" (interrupted)"); statusBar()->message(message); } else statusBar()->clear(); } // A slot that is called when the game ends. // void KReversi::slotGameOver() { uint black = m_game->score(Black); uint white = m_game->score(White); setState(Ready); if (black > white) showGameOver(Black); else if (black < white) showGameOver(White); else showGameOver(Nobody); showTurn(Nobody); } // ---------------------------------------------------------------- // Private methods // Handle the humans move. // void KReversi::humanMakeMove(int row, int col) { if (state() != Ready) return; Color color = m_game->toMove(); // Create a move from the mouse click and see if it is legal. // If it is, then make a human move. Move move(color, col + 1, row + 1); if (m_game->moveIsLegal(move)) { // Do the move. The view is automatically updated. m_game->doMove(move); if (!m_game->moveIsAtAllPossible()) { setState(Ready); slotGameOver(); return; } if (color != m_game->toMove()) computerMakeMove(); } else illegalMove(); } // Make a computer move. // void KReversi::computerMakeMove() { MoveList moves; // Check if the computer can move. Color color = m_game->toMove(); Color opponent = ::opponent(color); if (!m_game->moveIsPossible(color)) return; // Make computer moves until the human can play or until the game is over. setState(Thinking); do { Move move; if (!m_game->moveIsAtAllPossible()) { setState(Ready); slotGameOver(); return; } move = m_engine->computeMove(m_game, m_competitiveGame); if (move.x() == -1) { setState(Ready); return; } usleep(300000); // Pretend we have to think hard. // Do the move on the board. The view is automatically updated. //playSound("click.wav"); m_game->doMove(move); } while (!m_game->moveIsPossible(opponent)); setState(Ready); if (!m_game->moveIsAtAllPossible()) { slotGameOver(); return; } } // Handle an attempt to make an illegal move by the human. void KReversi::illegalMove() { KNotifyClient::event(winId(), "illegal_move", i18n("Illegal move")); } // Show things when the game is over. // void KReversi::showGameOver(Color color) { // If the game already was over, do nothing. if (m_gameOver) return; statusBar()->message(i18n("End of game")); // Get the scores. uint human = m_game->score(humanColor()); uint computer = m_game->score(computerColor()); KExtHighscore::Score score; score.setScore(m_game->score(humanColor())); // Show the winner in a messagebox. if ( color == Nobody ) { KNotifyClient::event(winId(), "draw", i18n("Draw!")); TQString s = i18n("Game is drawn!\n\nYou : %1\nComputer: %2") .arg(human).arg(computer); KMessageBox::information(this, s, i18n("Game Ended")); score.setType(KExtHighscore::Draw); } else if ( humanColor() == color ) { KNotifyClient::event(winId(), "won", i18n("Game won!")); TQString s = i18n("Congratulations, you have won!\n\nYou : %1\nComputer: %2") .arg(human).arg(computer); KMessageBox::information(this, s, i18n("Game Ended")); score.setType(KExtHighscore::Won); } else { KNotifyClient::event(winId(), "lost", i18n("Game lost!")); TQString s = i18n("You have lost the game!\n\nYou : %1\nComputer: %2") .arg(human).arg(computer); KMessageBox::information(this, s, i18n("Game Ended")); score.setType(KExtHighscore::Lost); } // Store the result in the highscore file if no cheating was done, // and only if the game was competitive. if (!m_cheating && m_competitiveGame) { KExtHighscore::submitScore(score, this); } m_gameOver = true; } // Saves the game in the config file. // // Only one game at a time can be saved. // void KReversi::saveGame(TDEConfig *config) { // Stop thinking. slotInterrupt(); // Write the data to the config file. config->writeEntry("State", state()); config->writeEntry("Strength", strength()); config->writeEntry("Competitive", (int) m_competitiveGame); config->writeEntry("HumanColor", (int) m_humanColor); // Write the moves of the game to the config object. This object // saves itself all at once so we don't have to write the moves // to the file ourselves. config->writeEntry("NumberOfMoves", m_game->moveNumber()); for (uint i = 0; i < m_game->moveNumber(); i++) { Move move = m_game->move(i); TQString moveString; TQString idx; moveString.sprintf("%d %d %d", move.x(), move.y(), (int) move.color()); idx.sprintf("Move_%d", i + 1); config->writeEntry(idx, moveString); } // Actually write the data to file. config->sync(); // Continue with the move if applicable. slotContinue(); } // Loads the game. Only one game at a time can be saved. bool KReversi::loadGame(TDEConfig *config) { slotInterrupt(); // stop thinking uint nmoves = config->readNumEntry("NumberOfMoves", 0); if (nmoves==0) return false; m_game->newGame(); uint movenumber = 1; while (nmoves--) { // Read one move. TQString idx; idx.sprintf("Move_%d", movenumber++); TQStringList s = config->readListEntry(idx, ' '); uint x = (*s.at(0)).toUInt(); uint y = (*s.at(1)).toUInt(); Color color = (Color)(*s.at(2)).toInt(); Move move(color, x, y); m_game->doMove(move); } m_humanColor = (Color) config->readNumEntry("HumanColor"); m_competitiveGame = (bool) config->readNumEntry("Competitive"); m_gameView->updateBoard(TRUE); setState(State(config->readNumEntry("State"))); setStrength(config->readNumEntry("Strength", 1)); if (interrupted()) slotContinue(); else { // Computer makes first move. if (m_humanColor != m_game->toMove()) computerMakeMove(); } return true; } // ---------------------------------------------------------------- void KReversi::saveProperties(TDEConfig *c) { saveGame(c); } void KReversi::readProperties(TDEConfig *config) { loadGame(config); m_gameOver = false; m_cheating = false; // FIXME: Is this true? It isn't saved. } void KReversi::showHighScoreDialog() { KExtHighscore::show(this); } void KReversi::slotEditSettings() { // If we are already editing the settings, then do nothing. if (TDEConfigDialog::showDialog("settings")) return; TDEConfigDialog *dialog = new TDEConfigDialog(this, "settings", Prefs::self(), KDialogBase::Swallow); Settings *general = new Settings(0, "General"); dialog->addPage(general, i18n("General"), "package_settings"); connect(dialog, TQ_SIGNAL(settingsChanged()), this, TQ_SLOT(loadSettings())); dialog->show(); } void KReversi::configureNotifications() { KNotifyDialog::configure(this); } void KReversi::loadSettings() { m_humanColor = (Color) Prefs::humanColor(); setStrength(Prefs::skill()); // m_competitiveGame is set at the start of a game and can only be // downgraded during the game, never upgraded. if ( !Prefs::competitiveGameChoice() ) m_competitiveGame = false; m_gameView->loadSettings(); // Update the color of the human and the computer. m_gameView->setHumanColor(humanColor()); } bool KReversi::isPlaying() const { return ( m_game->moveNumber() != 0 && !m_gameOver ); } void KReversi::setState(State newState) { m_state = newState; if (m_state == Thinking){ kapp->setOverrideCursor(waitCursor); stopAction->setEnabled(true); } else { kapp->restoreOverrideCursor(); stopAction->setEnabled(false); } continueAction->setEnabled(interrupted()); } bool KReversi::queryExit() { if ( isPlaying() ) KExtHighscore::submitScore(KExtHighscore::Lost, this); return KZoomMainWindow::queryExit(); } void KReversi::writeZoomSetting(uint zoom) { Prefs::setZoom(zoom); Prefs::writeConfig(); } uint KReversi::readZoomSetting() const { return Prefs::zoom(); } void KReversi::writeMenubarVisibleSetting(bool visible) { Prefs::setMenubarVisible(visible); Prefs::writeConfig(); } bool KReversi::menubarVisibleSetting() const { return Prefs::menubarVisible(); }