You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kreversi/kreversi.cpp

842 lines
20 KiB

/* Yo Emacs, this is -*- C++ -*-
*******************************************************************
*******************************************************************
*
*
* KREVERSI
*
*
*******************************************************************
*
* A Reversi (or sometimes called Othello) game
*
*******************************************************************
*
* created 1997 by Mario Weilguni <mweilguni@sime.com>
*
*******************************************************************
*
* 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 <unistd.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqlistbox.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <kstatusbar.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kaction.h>
#include <kstdgameaction.h>
#include <kkeydialog.h>
#include <kconfigdialog.h>
#include <knotifyclient.h>
#include <knotifydialog.h>
#include <kexthighscore.h>
#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) \
KGlobal::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.
createKActions();
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, TQT_SIGNAL(sig_newGame()), TQT_TQOBJECT(this), TQT_SLOT(showTurn()));
connect(m_game, TQT_SIGNAL(sig_move(uint, Move&)),
TQT_TQOBJECT(this), TQT_SLOT(handleMove(uint, Move&))); // Calls showTurn().
connect(m_game, TQT_SIGNAL(sig_update()), TQT_TQOBJECT(this), TQT_SLOT(showTurn()));
connect(m_game, TQT_SIGNAL(sig_gameOver()), TQT_TQOBJECT(this), TQT_SLOT(slotGameOver()));
// Signal that is sent when the user clicks on the board.
connect(m_gameView, TQT_SIGNAL(signalSquareClicked(int, int)),
TQT_TQOBJECT(this), TQT_SLOT(slotSquareClicked(int, int)));
loadSettings();
setupGUI();
init("popup");
m_gameView->start();
slotNewGame();
}
KReversi::~KReversi()
{
delete m_game;
delete m_engine;
}
// Create all KActions used in KReversi.
//
void KReversi::createKActions()
{
// Standard Game Actions.
KStdGameAction::gameNew(TQT_TQOBJECT(this), TQT_SLOT(slotNewGame()), actionCollection(),
"game_new");
KStdGameAction::load(TQT_TQOBJECT(this), TQT_SLOT(slotOpenGame()), actionCollection());
KStdGameAction::save(TQT_TQOBJECT(this), TQT_SLOT(slotSave()), actionCollection());
KStdGameAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), actionCollection());
KStdGameAction::hint(TQT_TQOBJECT(this), TQT_SLOT(slotHint()), actionCollection(),
"game_hint");
KStdGameAction::undo(TQT_TQOBJECT(this), TQT_SLOT(slotUndo()), actionCollection(),
"game_undo");
// Non-standard Game Actions: Stop, Continue, Switch sides
stopAction = new KAction(i18n("&Stop Thinking"), "game_stop", TQt::Key_Escape,
TQT_TQOBJECT(this), TQT_SLOT(slotInterrupt()), actionCollection(),
"game_stop");
continueAction = new KAction(i18n("&Continue Thinking"), "reload", 0,
TQT_TQOBJECT(this), TQT_SLOT(slotContinue()), actionCollection(),
"game_continue");
new KAction(i18n("S&witch Sides"), 0, 0,
TQT_TQOBJECT(this), TQT_SLOT(slotSwitchSides()), actionCollection(),
"game_switch_sides");
// Some more standard game actions: Highscores, Settings.
KStdGameAction::highscores(TQT_TQOBJECT(this), TQT_SLOT(showHighScoreDialog()), actionCollection());
KStdAction::preferences(TQT_TQOBJECT(this), TQT_SLOT(slotEditSettings()), actionCollection());
// Actions for the view(s).
showLastMoveAction = new KToggleAction(i18n("Show Last Move"), "lastmoves", 0,
TQT_TQOBJECT(this), TQT_SLOT(slotShowLastMove()),
actionCollection(),
"show_last_move");
showLegalMovesAction = new KToggleAction(i18n("Show Legal Moves"), "legalmoves", 0,
TQT_TQOBJECT(this), TQT_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 KActions
// 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()
{
KConfig *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()
{
KConfig *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 tqrepaint so that the new move is not shown before the old
// one is removed on the screen.
m_gameView->tqrepaint();
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")
.tqarg(human).tqarg(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")
.tqarg(human).tqarg(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")
.tqarg(human).tqarg(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(KConfig *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(KConfig *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(KConfig *c)
{
saveGame(c);
}
void KReversi::readProperties(KConfig *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 (KConfigDialog::showDialog("settings"))
return;
KConfigDialog *dialog = new KConfigDialog(this, "settings", Prefs::self(),
KDialogBase::Swallow);
Settings *general = new Settings(0, "General");
dialog->addPage(general, i18n("General"), "package_settings");
connect(dialog, TQT_SIGNAL(settingsChanged()), TQT_TQOBJECT(this), TQT_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();
}