diff --git a/src/gamemanager.cpp b/src/gamemanager.cpp index c7847aa..8d97a4a 100644 --- a/src/gamemanager.cpp +++ b/src/gamemanager.cpp @@ -1,914 +1,917 @@ /*************************************************************************** File : gamemanager.cpp Project : Knights Description : Game manager -------------------------------------------------------------------- Copyright : (C) 2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2009-2011 by Miha Čančula (miha@noughmad.eu) ***************************************************************************/ /*************************************************************************** * * * This program 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 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "gamemanager.h" #include "proto/protocol.h" #include "rules/chessrules.h" #include "externalcontrol.h" #include "settings.h" #include "difficultydialog.h" #include "knightsdebug.h" #include #include #ifdef HAVE_SPEECH #include #endif using namespace Knights; const int TimerInterval = 100; const int LineLimit = 80; // Maximum characters per line for PGN format void Offer::accept() const { Manager::self()->setOfferResult(id, AcceptOffer); } void Offer::decline() const { Manager::self()->setOfferResult(id, DeclineOffer); } class Knights::GameManagerPrivate { public: GameManagerPrivate(); Color activePlayer; bool running; bool gameStarted; bool gameOverInProcess; int timer; TimeControl whiteTimeControl; TimeControl blackTimeControl; QStack moveHistory; QStack moveUndoStack; Rules* rules; QMap offers; QSet usedOfferIds; #ifdef HAVE_SPEECH QTextToSpeech* speech; #endif ExternalControl* extControl; QString filename; Color winner; bool initComplete; int nextOfferId(); }; GameManagerPrivate::GameManagerPrivate() : activePlayer(NoColor), running(false), gameStarted(false), gameOverInProcess(false), timer(0), rules(0), #ifdef HAVE_SPEECH speech(0), #endif extControl(0) { } int GameManagerPrivate::nextOfferId() { int i = usedOfferIds.size() + 1; while (usedOfferIds.contains(i)) ++i; return i; } Q_GLOBAL_STATIC(Manager, instance) Manager* Manager::self() { return instance; } Manager::Manager(QObject* parent) : QObject(parent), d_ptr(new GameManagerPrivate) { #ifdef HAVE_SPEECH Q_D(GameManager); d->speech = new QTextToSpeech(); #endif } Manager::~Manager() { #ifdef HAVE_SPEECH Q_D(GameManager); delete d->speech; #endif delete d_ptr; } void Manager::startTime() { Q_D(GameManager); if ( !d->running ) { d->timer = startTimer ( TimerInterval ); d->running = true; } } void Manager::stopTime() { Q_D(GameManager); if ( d->running ) { killTimer(d->timer); d->running = false; } } void Manager::setTimeRunning(bool running) { if ( running ) startTime(); else stopTime(); } void Manager::setCurrentTime(Color color, const QTime& time) { Q_D(GameManager); switch ( color ) { case White: d->whiteTimeControl.currentTime = time; break; case Black: d->blackTimeControl.currentTime = time; break; default: return; } emit timeChanged ( color, time ); } void Manager::timerEvent(QTimerEvent* ) { Q_D(GameManager); QTime time; switch ( d->activePlayer ) { case White: if ( d->whiteTimeControl.currentTime == QTime(0, 0, 0, TimerInterval) ) gameOver(Black); d->whiteTimeControl.currentTime = d->whiteTimeControl.currentTime.addMSecs ( -TimerInterval ); time = d->whiteTimeControl.currentTime; break; case Black: if ( d->blackTimeControl.currentTime == QTime(0, 0, 0, TimerInterval) ) gameOver(White); d->blackTimeControl.currentTime = d->blackTimeControl.currentTime.addMSecs ( -TimerInterval ); time = d->blackTimeControl.currentTime; break; default: time = QTime(); break; } emit timeChanged(d->activePlayer, time); } void Manager::addMoveToHistory(const Move& move) { Q_D(GameManager); if ( d->moveHistory.isEmpty() ) emit undoPossible(true); d->moveHistory << move; if ( !d->moveUndoStack.isEmpty() ) emit redoPossible(false); d->moveUndoStack.clear(); emit historyChanged(); } Move Manager::nextUndoMove() { Q_D(GameManager); Move m = d->moveHistory.pop(); if ( m.pieceData().first == White ) d->whiteTimeControl.currentTime = m.time(); else d->blackTimeControl.currentTime = m.time(); emit timeChanged ( m.pieceData().first, m.time() ); if ( d->moveHistory.isEmpty() ) emit undoPossible(false); if ( d->moveUndoStack.isEmpty() ) emit redoPossible(true); d->moveUndoStack.push( m ); emit historyChanged(); Move ret = m.reverse(); qCDebug(LOG_KNIGHTS) << m << ret; ret.setFlag ( Move::Forced, true ); return ret; } Move Manager::nextRedoMove() { Q_D(GameManager); Move m = d->moveUndoStack.pop(); if ( d->moveUndoStack.isEmpty() ) emit redoPossible(false); if ( d->moveHistory.isEmpty() ) emit undoPossible(true); d->moveHistory << m; m.setFlag ( Move::Forced, true ); emit historyChanged(); return m; } void Manager::setActivePlayer(Color player) { Q_D(GameManager); d->activePlayer = player; } void Manager::changeActivePlayer() { setActivePlayer ( oppositeColor ( activePlayer() ) ); emit activePlayerChanged ( activePlayer() ); } Color Manager::activePlayer() const { Q_D(const GameManager); return d->activePlayer; } void Manager::initialize() { Q_D(GameManager); d->gameStarted = false; d->initComplete = false; d->running = false; d->moveHistory.clear(); d->activePlayer = White; d->whiteTimeControl.currentTime = d->whiteTimeControl.baseTime; d->blackTimeControl.currentTime = d->blackTimeControl.baseTime; QList protocols; Protocol::white()->setTimeControl(d->whiteTimeControl); Protocol::black()->setTimeControl(d->blackTimeControl); connect ( Protocol::white(), &Protocol::pieceMoved, this, &Manager::moveByProtocol ); connect ( Protocol::white(), &Protocol::initSuccesful, this, &Manager::protocolInitSuccesful, Qt::QueuedConnection ); connect ( Protocol::white(), &Protocol::gameOver, this, &Manager::gameOver ); connect ( Protocol::black(), &Protocol::pieceMoved, this, &Manager::moveByProtocol ); connect ( Protocol::black(), &Protocol::initSuccesful, this, &Manager::protocolInitSuccesful, Qt::QueuedConnection ); connect ( Protocol::black(), &Protocol::gameOver, this, &Manager::gameOver ); Protocol::white()->init(); Protocol::black()->init(); d->extControl = new ExternalControl(this); } void Manager::pause(bool pause) { Offer o; o.action = pause ? ActionPause : ActionResume; o.id = qrand(); o.player = NoColor; sendOffer(o); } /** * Sets the time control parameters in the same format as XBoard's @c level command works * @param color specifis to which player this setting will apply. If @p color is NoColor then both player use this setting. * @param moves the number of moves to be completed before @p baseTime runs out. * Setting this to 0 causes the timing to be incremental only * @param baseTime the time in minutes in which the player has to complete @p moves moves, or finish the game if @p moves is zero. * @param increment the time in seconds that is added to the player's clock for his every move. */ void Manager::setTimeControl(Color color, const TimeControl& control) { Q_D(GameManager); if ( color == White ) d->whiteTimeControl = control; else if ( color == Black ) d->blackTimeControl = control; else { qCDebug(LOG_KNIGHTS) << "Setting time control for both colors"; d->blackTimeControl = control; d->whiteTimeControl = control; } } TimeControl Manager::timeControl(Color color) const { Q_D(const GameManager); if ( color == White ) return d->whiteTimeControl; else if ( color == Black ) return d->blackTimeControl; else { // FICS protocol needs the time control parameters even before the color is determined // It only supports equal time for both players, so it doesn't matter which one we return return d->whiteTimeControl; } } QTime Manager::timeLimit(Color color) { return timeControl(color).baseTime; } bool Manager::timeControlEnabled(Color color) const { TimeControl tc = timeControl(color); // For a time to be valid, either the base time or increment must be greater than 0 if ( tc.baseTime.isValid() || tc.increment > 0 ) return true; return false; } void Manager::undo() { sendPendingMove(); Q_D(const GameManager); Offer o; o.action = ActionUndo; // We always undo moves until it's local player's turn again. if ( Protocol::byColor(d->activePlayer)->isLocal() && !Protocol::byColor(oppositeColor(d->activePlayer))->isLocal() ) o.numberOfMoves = 2; else o.numberOfMoves = 1; o.numberOfMoves = qMin ( o.numberOfMoves, d->moveHistory.size() ); o.player = local()->color(); sendOffer(o); } void Manager::redo() { sendPendingMove(); Move m = nextRedoMove(); Protocol::white()->move ( m ); Protocol::black()->move ( m ); emit pieceMoved ( m ); changeActivePlayer(); } void Manager::adjourn() { sendOffer(ActionAdjourn); } void Manager::abort() { sendOffer(ActionAbort); } void Manager::offerDraw() { sendOffer(ActionDraw); } void Manager::resign() { - sendOffer(ActionResign); + Q_D(const GameManager); + if (d->activePlayer == Knights::White) + gameOver(Knights::Black); + else + gameOver(Knights::White); } bool Manager::isRunning() { Q_D(const GameManager); return d->running; } void Manager::moveByProtocol(const Move& move) { Q_D(GameManager); if ( sender() != Protocol::byColor ( d->activePlayer ) || !d->gameStarted ) { qCDebug(LOG_KNIGHTS) << "Move by the non-active player" << move; // Ignore duplicates and/or moves by the inactive player return; } processMove(move); } void Manager::protocolInitSuccesful() { Q_D(GameManager); if ( Protocol::white() && Protocol::black() ) { if ( !d->gameStarted && Protocol::white()->isReady() && Protocol::black()->isReady() ) { if ( Protocol::white()->isLocal() && Protocol::black()->isLocal() ) { Protocol::white()->setPlayerName ( i18nc ( "The player of this color", "White" ) ); Protocol::black()->setPlayerName ( i18nc ( "The player of this color", "Black" ) ); } if (!d->initComplete) { d->initComplete = true; emit initComplete(); } } } } void Manager::startGame() { Q_D(GameManager); Q_ASSERT(!d->gameStarted); setRules(new ChessRules); if ( Protocol::white()->supportedFeatures() & Protocol::AdjustDifficulty || Protocol::white()->supportedFeatures() & Protocol::AdjustDifficulty) levelChanged(Kg::difficulty()->currentLevel()); Protocol::white()->startGame(); Protocol::black()->startGame(); d->gameStarted = true; emit historyChanged(); } void Manager::gameOver(Color winner) { Q_D(GameManager); if (!d->gameStarted) return; if (!d->gameOverInProcess) { d->gameOverInProcess = true; sendPendingMove(); } stopTime(); Protocol::white()->setWinner(winner); Protocol::black()->setWinner(winner); emit winnerNotify(winner); reset(); d->gameOverInProcess = false; - } void Manager::reset() { Q_D(GameManager); if (d->gameStarted) { if (!d->gameOverInProcess) { sendPendingMove(); stopTime(); } Protocol::white()->deleteLater(); Protocol::black()->deleteLater(); if (d->rules) { delete d->rules; d->rules = 0; } d->gameStarted = false; } //don't clear the move history here, //it should be possible to study the history after the game ended. d->moveUndoStack.clear(); emit undoPossible( false ); emit redoPossible( false ); d->offers.clear(); d->usedOfferIds.clear(); d->winner = NoColor; if ( d->extControl ) { delete d->extControl; d->extControl = 0; } } Rules* Manager::rules() const { Q_D(const GameManager); return d->rules; } void Manager::setRules(Rules* rules) { Q_D(GameManager); if (d->rules) delete d->rules; d->rules = rules; } void Manager::sendOffer(GameAction action, Color player, int id) { Offer o; o.action = action; o.player = player; o.id = id; sendOffer(o); } void Manager::sendOffer(const Offer& offer) { Q_D(GameManager); Offer o = offer; if ( offer.player == NoColor ) o.player = local()->color(); if (o.id == 0) o.id = d->nextOfferId(); QString name = Protocol::byColor(o.player)->playerName(); if ( o.text.isEmpty() ) { switch ( offer.action ) { case ActionDraw: o.text = i18n("%1 offers you a draw", name); break; case ActionUndo: o.text = i18np("%2 would like to take back a half move", "%2 would like to take back %1 half moves", o.numberOfMoves, name); break; case ActionAdjourn: o.text = i18n("%1 would like to adjourn the game", name); break; case ActionAbort: o.text = i18n("%1 would like to abort the game", name); break; default: break; } } d->offers.insert ( o.id, o ); d->usedOfferIds << o.id; Protocol* opp = Protocol::byColor( oppositeColor(o.player) ); // Only display a notification if only one player is local. if ( opp->isLocal() && !Protocol::byColor(o.player)->isLocal() ) emit notification(o); else opp->makeOffer(o); } void Manager::setOfferResult(int id, OfferAction result) { Q_D(GameManager); if ( result == AcceptOffer ) { Protocol::byColor(d->offers[id].player)->acceptOffer(d->offers[id]); switch ( d->offers[id].action ) { case ActionUndo: for ( int i = 0; i < d->offers[id].numberOfMoves; ++i ) { emit pieceMoved ( nextUndoMove() ); changeActivePlayer(); } break; case ActionDraw: Protocol::white()->setWinner(NoColor); Protocol::black()->setWinner(NoColor); break; case ActionAbort: gameOver(NoColor); break; case ActionPause: stopTime(); break; case ActionResume: startTime(); break; default: break; } } else if ( result == DeclineOffer ) Protocol::byColor(d->offers[id].player)->declineOffer(d->offers[id]); d->offers.remove(id); } Protocol* Manager::local() { Q_D(const GameManager); if ( Protocol::byColor(d->activePlayer)->isLocal() ) return Protocol::byColor(d->activePlayer); if ( Protocol::byColor(oppositeColor(d->activePlayer))->isLocal() ) return Protocol::byColor(oppositeColor(d->activePlayer)); qCWarning(LOG_KNIGHTS) << "No local protocols, trying a computer"; if ( Protocol::byColor(d->activePlayer)->isComputer() ) return Protocol::byColor(d->activePlayer); if ( Protocol::byColor(oppositeColor(d->activePlayer))->isComputer() ) return Protocol::byColor(oppositeColor(d->activePlayer)); qCWarning(LOG_KNIGHTS) << "No local or computer protocols, returning 0"; return 0; } bool Manager::canRedo() const { Q_D(const GameManager); return !d->moveUndoStack.isEmpty(); } void Manager::sendPendingMove() { if ( pendingMove.isValid() && isGameActive() ) { Q_D(GameManager); Protocol::byColor ( oppositeColor ( d->activePlayer ) )->move ( pendingMove ); emit pieceMoved ( pendingMove ); rules()->moveMade ( pendingMove ); if ( Settings::speakOpponentsMoves() && !Protocol::byColor(d->activePlayer)->isLocal() && Protocol::byColor(oppositeColor(d->activePlayer))->isLocal() ) { QString toSpeak; QString name = Protocol::byColor(d->activePlayer)->playerName(); if ( pendingMove.flag(Move::Castle) ) { if ( pendingMove.to().first == 3 ) { toSpeak = i18nc("string to be spoken when the opponent castles queenside", "%1 castles queenside", name); } else { toSpeak = i18nc("string to be spoken when the opponent castles queenside", "%1 castles kingside", name); } } else { toSpeak = i18nc("string to be spoken when the opponent makes a normal move", "%1 to %2", pieceTypeName ( pendingMove.pieceData().second ), pendingMove.to().string() ); } #ifdef HAVE_SPEECH qCDebug(LOG_KNIGHTS) << toSpeak; d->speech->say(toSpeak); if ( pendingMove.flag(Move::Check) ) { if ( d->rules->hasLegalMoves ( oppositeColor( d->activePlayer ) ) ) d->speech->say ( i18nc( "Your king is under attack", "Check" ) ); else d->speech->say ( i18nc( "Your king is dead", "Checkmate" ) ); } #endif /* HAVE_SPEECH */ } pendingMove = Move(); Color winner = rules()->winner(); if ( winner != NoColor || !rules()->hasLegalMoves ( oppositeColor( d->activePlayer ) ) ) { qCDebug(LOG_KNIGHTS) << "Winner: " << winner; if (!d->gameOverInProcess) { d->gameOverInProcess = true; gameOver(winner); } } int moveNumber; int secondsAdded = 0; switch ( d->activePlayer ) { case White: moveNumber = ( d->moveHistory.size() + 1 ) / 2; if ( moveNumber > 1 ) secondsAdded += d->whiteTimeControl.increment; if ( d->whiteTimeControl.moves > 0 && ( moveNumber % d->whiteTimeControl.moves ) == 0 ) secondsAdded += QTime().secsTo( d->whiteTimeControl.baseTime ); if ( secondsAdded != 0 ) setCurrentTime ( White, d->whiteTimeControl.currentTime.addSecs ( secondsAdded ) ); break; case Black: moveNumber = d->moveHistory.size() / 2; if ( moveNumber > 1 ) secondsAdded += d->blackTimeControl.increment; if ( d->blackTimeControl.moves > 0 && ( moveNumber % d->blackTimeControl.moves ) == 0 ) secondsAdded += QTime().secsTo ( d->blackTimeControl.baseTime ); if ( secondsAdded != 0 ) setCurrentTime ( Black, d->blackTimeControl.currentTime.addSecs ( secondsAdded ) ); break; default: break; } changeActivePlayer(); } } void Manager::moveByBoard(const Move& move) { processMove(move); } void Manager::moveByExternalControl(const Knights::Move& move) { Q_D(GameManager); if ( Settings::allowExternalControl() && Protocol::byColor(d->activePlayer)->isLocal() ) processMove(move); } void Manager::processMove(const Move& move) { sendPendingMove(); Q_D(const GameManager); Move m = move; if ( activePlayer() == White ) m.setTime ( d->whiteTimeControl.currentTime ); else m.setTime ( d->blackTimeControl.currentTime ); d->rules->checkSpecialFlags ( &m, d->activePlayer ); if ( m.flag(Move::Illegal) && !m.flag(Move::Forced) ) return; addMoveToHistory ( m ); if ( d->moveHistory.size() == 2 && timeControlEnabled(d->activePlayer) ) startTime(); pendingMove = m; if ( Protocol::byColor(d->activePlayer)->isComputer() ) QTimer::singleShot ( Settings::computerDelay(), this, SLOT(sendPendingMove()) ); else sendPendingMove(); } bool Manager::isGameActive() const { Q_D(const GameManager); return d->gameStarted; } bool Manager::canLocalMove() const { Q_D(const GameManager); if ( !d->gameStarted ) return false; if ( d->running || d->moveHistory.size() < 2 || !timeControlEnabled(NoColor) ) return Protocol::byColor(d->activePlayer)->isLocal(); return false; } void Manager::levelChanged ( const KgDifficultyLevel* level ) { qCDebug(LOG_KNIGHTS); int depth = 0; int size = 32; switch ( level->standardLevel() ) { case KgDifficultyLevel::VeryEasy: depth = 1; break; case KgDifficultyLevel::Easy: depth = 3; break; case KgDifficultyLevel::Medium: depth = 8; break; case KgDifficultyLevel::Hard: depth = 16; break; case KgDifficultyLevel::VeryHard: depth = 32; break; case KgDifficultyLevel::Custom: { QPointer dlg = new DifficultyDialog(); if ( dlg->exec() == QDialog::Accepted ) { depth = dlg->searchDepth(); size = dlg->memorySize(); } else return; } break; default: break; } Protocol* p = Protocol::white(); if (p && p->supportedFeatures() & Protocol::AdjustDifficulty) p->setDifficulty(depth, size); p = Protocol::black(); if (p && p->supportedFeatures() & Protocol::AdjustDifficulty) p->setDifficulty(depth, size); } void Manager::loadGameHistoryFrom(const QString& filename) { qCDebug(LOG_KNIGHTS) << filename; QFile file(filename); if ( !file.open(QIODevice::ReadOnly) ) return; QRegExp tagPairExp = QRegExp(QLatin1String( "\\[(.*)\\s\\\"(.*)\\\"\\]" )); while ( file.bytesAvailable() > 0 ) { QByteArray line = file.readLine(); if ( tagPairExp.indexIn ( QLatin1String(line) ) > -1 ) { // Parse a tag pair QString key = tagPairExp.cap(1); QString value = tagPairExp.cap(2); if ( key == QLatin1String("White") ) Protocol::white()->setPlayerName ( value ); else if ( key == QLatin1String("Black") ) Protocol::black()->setPlayerName ( value ); else if ( key == QLatin1String("TimeControl") ) { // TODO, optional: Parse TimeControl Tag } } else { // Parse a line of moves foreach ( const QByteArray& str, line.trimmed().split(' ') ) { if ( !str.trimmed().isEmpty() && !str.contains('.') && !str.contains("1-0") && !str.contains("0-1") && !str.contains("1/2-1/2") && !str.contains('*') ) { // Only move numbers contain dots, not move data itself // We also exclude the game termination markers (results) qCDebug(LOG_KNIGHTS) << "Read move" << str; Move m; if (str.contains("O-O-O") || str.contains("o-o-o") || str.contains("0-0-0")) m = Move::castling(Move::QueenSide, activePlayer()); else if (str.contains("O-O") || str.contains("o-o") || str.contains("0-0")) m = Move::castling(Move::KingSide, activePlayer()); else m = Move ( QLatin1String(str) ); m.setFlag ( Move::Forced, true ); processMove ( m ); } } } } emit playerNameChanged(); } void Manager::saveGameHistoryAs(const QString& filename) { Q_D(GameManager); d->filename = filename; QFile file ( d->filename ); file.open(QIODevice::WriteOnly); QTextStream stream ( &file ); // Write the player tags first // Standard Tag Roster: Event, Site, Date, Round, White, Black, Result stream << "[Event \"Casual Game\"]" << endl; stream << "[Site \"?\"]" << endl; stream << "[Date \"" << QDate::currentDate().toString( QLatin1String("yyyy.MM.dd") ) << "\"]" << endl; stream << "[Round \"-\"]" << endl; stream << "[White \"" << Protocol::white()->playerName() << "\"]" << endl; stream << "[Black \"" << Protocol::black()->playerName() << "\"]" << endl; QByteArray result; if ( d->running ) result += '*'; else { switch ( d->winner ) { case White: result = "1-0"; break; case Black: result = "0-1"; break; default: result = "1/2-1/2"; break; } } stream << "[Result \"" << result << "\"]" << endl; // Supplemental tags, ordered alphabetacally. // Currently, only TimeControl is added stream << "[TimeControl \""; if ( timeControlEnabled ( NoColor ) ) { // The PGN specification doesn't include a time control combination with both a number of moves // and an increment per move defined, so we only output one of them // If the spec will ever be expanded, the two lines should be combined: // stream << tc.moves << '/' << QTime().secsTo ( tc.baseTime ) << '+' << tc.increment TimeControl tc = timeControl ( NoColor ); if ( tc.moves ) stream << tc.moves << '/' << QTime().secsTo ( tc.baseTime ); else stream << QTime().secsTo ( tc.baseTime ) << '+' << tc.increment; } else stream << '-'; stream << "\"]"; // A single newline separates the tag pairs from the movetext section stream << endl; qCDebug(LOG_KNIGHTS) << "Starting to write movetext"; int characters = 0; int n = d->moveHistory.size(); for (int i = 0; i < n; ++i) { Move m = d->moveHistory[i]; const QString moveString = m.stringForNotation ( Move::Algebraic ); QString output; if ( i % 2 == 0 ) { // White move output = QString::number(i/2+1) + QLatin1String(". ") + moveString; } else { // Black move output = moveString; } if ( characters + output.size() > LineLimit ) { stream << endl; characters = 0; } if ( characters != 0 ) stream << QLatin1Char(' '); stream << output; characters += output.size(); } qCDebug(LOG_KNIGHTS); stream << ' ' << result; stream << endl; stream.flush(); qCDebug(LOG_KNIGHTS) << "Saved"; } QStack< Move > Manager::moveHistory() const { Q_D(const GameManager); return d->moveHistory; } diff --git a/src/gamemanager.h b/src/gamemanager.h index 5215da5..03cf642 100644 --- a/src/gamemanager.h +++ b/src/gamemanager.h @@ -1,172 +1,172 @@ /*************************************************************************** File : gamemanager.h Project : Knights Description : Game manager -------------------------------------------------------------------- Copyright : (C) 2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2009-2011 by Miha Čančula (miha@noughmad.eu) ***************************************************************************/ /*************************************************************************** * * * This program 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 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef KNIGHTS_GAMEMANAGER_H #define KNIGHTS_GAMEMANAGER_H #include #include #include "offerwidget.h" #include #include class KgDifficultyLevel; namespace Knights { class Protocol; class Rules; class Move; class GameManagerPrivate; struct TimeControl { int moves; QTime baseTime; int increment; QTime currentTime; }; enum GameAction { ActionNone, ActionDraw, ActionUndo, ActionAdjourn, ActionAbort, ActionPause, ActionResume, ActionResign, ActionOther }; class Offer { public: GameAction action; int id; QString text; Color player; int numberOfMoves; // Only used for Takeback offers. void accept() const; void decline() const; }; class Manager : public QObject { Q_OBJECT public: static Manager* self(); explicit Manager(QObject* parent = 0); virtual ~Manager(); void setCurrentTime(Color, const QTime&); void setTimeControl(Color, const TimeControl&); TimeControl timeControl(Color) const; bool timeControlEnabled(Color) const; QTime timeLimit(Color); void changeActivePlayer(); void setActivePlayer(Color); Color activePlayer() const; bool isRunning(); bool canRedo() const; bool isGameActive() const; bool canLocalMove() const; Rules* rules() const; void setRules(Rules*); void reset(); void startGame(); + void resign(); QStack moveHistory() const; private: void addMoveToHistory(const Move&); Move nextUndoMove(); Move nextRedoMove(); void startTime(); void stopTime(); void processMove(const Move&); Protocol* local(); public slots: void levelChanged(const KgDifficultyLevel*); void moveByExternalControl(const Move&); void moveByBoard(const Move&); void sendOffer(GameAction action, Color player = NoColor, int id = 0); void sendOffer(const Offer&); void setOfferResult(int id, OfferAction result); void initialize(); void pause(bool); void undo(); void redo(); void loadGameHistoryFrom(const QString& filename); void saveGameHistoryAs(const QString& filename); private slots: void moveByProtocol(const Move&); void protocolInitSuccesful(); void gameOver(Color); - void resign(); void offerDraw(); void adjourn(); void abort(); void setTimeRunning(bool); void sendPendingMove(); private: GameManagerPrivate* d_ptr; Move pendingMove; Q_DECLARE_PRIVATE(GameManager) protected: virtual void timerEvent(QTimerEvent*); signals: void timeChanged(Color, const QTime&); void undoPossible(bool); void redoPossible(bool); void pieceMoved(const Move&); void activePlayerChanged(Color); void initComplete(); void notification(const Offer& ); void winnerNotify(Color); void historyChanged(); void playerNameChanged(); }; } #endif // KNIGHTS_GAMEMANAGER_H diff --git a/src/knights.cpp b/src/knights.cpp index 1929f86..229750f 100644 --- a/src/knights.cpp +++ b/src/knights.cpp @@ -1,568 +1,574 @@ /*************************************************************************** File : knights.cpp Project : Knights Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2016-1017 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2010-2012 by Miha Čančula (miha@noughmad.eu) ***************************************************************************/ /*************************************************************************** * * * This program 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 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "knights.h" #include "proto/localprotocol.h" #include "gamemanager.h" #include "knightsview.h" #include "knightsdebug.h" #include "settings.h" #include "gamedialog.h" #include "clockwidget.h" #include "historywidget.h" #include "enginesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include const char* DontAskDiscard = "dontAskInternal"; using namespace Knights; /** * This class serves as the main window for Knights. It handles the * menus, toolbars, and status bars. * * @short Main window class * @author %{AUTHOR} <%{EMAIL}> * @version %{VERSION} */ MainWindow::MainWindow() : KXmlGuiWindow(), m_view(new KnightsView(this)), m_themeProvider(new KgThemeProvider("Theme", this)) { // accept dnd setAcceptDrops(true); // tell the KXmlGuiWindow that this is indeed the main widget setCentralWidget(m_view); // initial creation/setup of the docks setDockNestingEnabled(true); setupDocks(); // setup actions and GUI setupActions(); setupGUI(); //protocol features m_protocolFeatures [ KStandardGameAction::name(KStandardGameAction::Pause) ] = Protocol::Pause; m_protocolFeatures [ "propose_draw" ] = Protocol::Draw; m_protocolFeatures [ "adjourn" ] = Protocol::Adjourn; m_protocolFeatures [ "resign" ] = Protocol::Resign; m_protocolFeatures [ "abort" ] = Protocol::Abort; // setup difficulty management connect(Kg::difficulty(), &KgDifficulty::currentLevelChanged, Manager::self(), &Manager::levelChanged); Kg::difficulty()->addLevel(new KgDifficultyLevel(0, "custom", i18n("Custom"), false)); Kg::difficulty()->addStandardLevelRange(KgDifficultyLevel::VeryEasy, KgDifficultyLevel::VeryHard, KgDifficultyLevel::Medium); KgDifficultyGUI::init(this); Kg::difficulty()->setEditable(false); // make all the docks invisible. // Show required docks after the game protocols are selected m_clockDock->hide(); m_bconsoleDock->hide(); m_wconsoleDock->hide(); m_chatDock->hide(); m_historyDock->hide(); connect(m_view, &KnightsView::gameNew, this, &MainWindow::fileNew, Qt::QueuedConnection); connect(Manager::self(), &Manager::initComplete, this, &MainWindow::protocolInitSuccesful); connect(Manager::self(), &Manager::playerNameChanged, this, &MainWindow::updateCaption); connect(Manager::self(), &Manager::pieceMoved, this, &MainWindow::gameChanged); connect(qApp, &QGuiApplication::lastWindowClosed, this, &MainWindow::exitKnights); m_themeProvider->discoverThemes("appdata", QLatin1String("themes")); m_view->drawBoard(m_themeProvider); } void MainWindow::setupDocks() { // clock dock m_clockDock = new QDockWidget(i18n("Clock"), this); m_clockDock->setObjectName(QLatin1String("ClockDockWidget")); // for QMainWindow::saveState() m_playerClock = new ClockWidget(this); m_clockDock->setWidget(m_playerClock); m_clockDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); connect(m_view, &KnightsView::displayedPlayerChanged, m_playerClock, &ClockWidget::setDisplayedPlayer); connect(Manager::self(), &Manager::timeChanged, m_playerClock, &ClockWidget::setCurrentTime); addDockWidget(Qt::RightDockWidgetArea, m_clockDock); // console dock for black m_bconsoleDock = new QDockWidget(); m_bconsoleDock->setObjectName(QLatin1String("BlackConsoleDockWidget")); m_bconsoleDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); addDockWidget(Qt::LeftDockWidgetArea, m_bconsoleDock); // console dock for white m_wconsoleDock = new QDockWidget(); m_wconsoleDock->setObjectName(QLatin1String("WhiteConsoleDockWidget")); m_wconsoleDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); addDockWidget(Qt::LeftDockWidgetArea, m_wconsoleDock); // chat dock m_chatDock = new QDockWidget(); m_chatDock->setObjectName(QLatin1String("ChatDockWidget")); m_chatDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); addDockWidget(Qt::LeftDockWidgetArea, m_chatDock); // history dock m_historyDock = new QDockWidget(); m_historyDock->setObjectName(QLatin1String("HistoryDockWidget")); m_historyDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); m_historyDock->setWindowTitle(i18n("Move History")); m_historyDock->setWidget(new HistoryWidget); addDockWidget(Qt::LeftDockWidgetArea, m_historyDock); } void MainWindow::setupActions() { KStandardGameAction::gameNew(this, SLOT(fileNew()), actionCollection()); KStandardGameAction::quit(qApp, SLOT(closeAllWindows()), actionCollection()); m_pauseAction = KStandardGameAction::pause(Manager::self(), SLOT(pause(bool)), actionCollection()); m_pauseAction->setEnabled(false); KStandardAction::preferences(this, SLOT(optionsPreferences()), actionCollection()); m_saveAction = KStandardGameAction::save(this, SLOT(fileSave()), actionCollection()); m_saveAction->setEnabled(false); m_saveAsAction = KStandardGameAction::saveAs(this, SLOT(fileSaveAs()), actionCollection()); m_saveAsAction->setEnabled(false); KStandardGameAction::load(this, SLOT(fileLoad()), actionCollection()); - QAction* resignAction = actionCollection()->addAction(QLatin1String("resign"), Manager::self(), SLOT(resign())); + QAction* resignAction = actionCollection()->addAction(QLatin1String("resign"), this, SLOT(resign())); resignAction->setText(i18n("Resign")); resignAction->setToolTip(i18n("Admit your inevitable defeat")); resignAction->setIcon(QIcon::fromTheme(QLatin1String("flag-red"))); resignAction->setEnabled(false); QAction* undoAction = actionCollection()->addAction(QLatin1String("move_undo"), this, SLOT(undo())); undoAction->setText(i18n("Undo")); undoAction->setToolTip(i18n("Take back your last move")); undoAction->setIcon(QIcon::fromTheme(QLatin1String("edit-undo"))); connect(Manager::self(), &Manager::undoPossible, undoAction, &QAction::setEnabled); undoAction->setEnabled(false); QAction* redoAction = actionCollection()->addAction(QLatin1String("move_redo"), this, SLOT(redo())); redoAction->setText(i18n("Redo")); redoAction->setToolTip(i18n("Repeat your last move")); redoAction->setIcon(QIcon::fromTheme(QLatin1String("edit-redo"))); connect(Manager::self(), &Manager::redoPossible, redoAction, &QAction::setEnabled); redoAction->setEnabled(false); QAction* drawAction = actionCollection()->addAction(QLatin1String("propose_draw"), Manager::self(), SLOT(offerDraw())); drawAction->setText(i18n("Offer &Draw")); drawAction->setToolTip(i18n("Offer a draw to your opponent")); drawAction->setIcon(QIcon::fromTheme(QLatin1String("flag-blue"))); drawAction->setEnabled(false); QAction* adjournAction = actionCollection()->addAction(QLatin1String("adjourn"), Manager::self(), SLOT(adjourn())); adjournAction->setText(i18n("Adjourn")); adjournAction->setToolTip(i18n("Continue this game at a later time")); adjournAction->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); adjournAction->setEnabled(false); QAction* abortAction = actionCollection()->addAction(QLatin1String("abort"), Manager::self(), SLOT(abort())); abortAction->setText(i18n("Abort")); abortAction->setToolTip(i18n("End the game immediately")); abortAction->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))); abortAction->setEnabled(false); KToggleAction* clockAction = new KToggleAction(QIcon::fromTheme(QLatin1String("clock")), i18n("Show Clock"), actionCollection()); actionCollection()->addAction(QLatin1String("show_clock"), clockAction); connect(clockAction, &KToggleAction::triggered, m_clockDock, &QDockWidget::setVisible); connect(clockAction, &KToggleAction::triggered, this, &MainWindow::setShowClockSetting); clockAction->setVisible(false); KToggleAction* historyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("view-history")), i18n("Show History"), actionCollection()); actionCollection()->addAction(QLatin1String("show_history"), historyAction); connect(historyAction, &KToggleAction::triggered, m_historyDock, &QDockWidget::setVisible); connect(historyAction, &KToggleAction::triggered, this, &MainWindow::setShowHistorySetting); historyAction->setVisible(true); historyAction->setChecked(Settings::showHistory()); KToggleAction* wconsoleAction = new KToggleAction(QIcon::fromTheme(QLatin1String("utilities-terminal")), i18n("Show White Console"), actionCollection()); actionCollection()->addAction(QLatin1String("show_console_white"), wconsoleAction); connect(wconsoleAction, &KToggleAction::triggered, m_wconsoleDock, &QDockWidget::setVisible); connect(wconsoleAction, &KToggleAction::triggered, this, &MainWindow::setShowConsoleSetting); wconsoleAction->setVisible(false); KToggleAction* bconsoleAction = new KToggleAction(QIcon::fromTheme(QLatin1String("utilities-terminal")), i18n("Show Black Console"), actionCollection()); actionCollection()->addAction(QLatin1String("show_console_black"), bconsoleAction); connect(bconsoleAction, &KToggleAction::triggered, m_bconsoleDock, &QDockWidget::setVisible); connect(bconsoleAction, &KToggleAction::triggered, this, &MainWindow::setShowConsoleSetting); bconsoleAction->setVisible(false); KToggleAction* chatAction = new KToggleAction(QIcon::fromTheme(QLatin1String("meeting-attending")), i18n("Show Chat"), actionCollection()); actionCollection()->addAction(QLatin1String("show_chat"), chatAction); connect(chatAction, &KToggleAction::triggered, m_chatDock, &QDockWidget::setVisible); connect(chatAction, &KToggleAction::triggered, this, &MainWindow::setShowChatSetting); chatAction->setVisible(false); } void MainWindow::fileNew() { if(!maybeSave()) return; GameDialog* gameNewDialog = new GameDialog(this); if(gameNewDialog->exec() == QDialog::Accepted) { Manager::self()->reset(); gameNewDialog->setupProtocols(); connect(Protocol::white(), &Protocol::error, this, &MainWindow::protocolError); connect(Protocol::black(), &Protocol::error, this, &MainWindow::protocolError); gameNewDialog->save(); m_pauseAction->setChecked(false); Manager::self()->initialize(); bool difficulty = (Protocol::white()->supportedFeatures()& Protocol::AdjustDifficulty) || (Protocol::black()->supportedFeatures()& Protocol::AdjustDifficulty); Kg::difficulty()->setEditable(difficulty); } delete gameNewDialog; m_saveAction->setEnabled(false); m_saveAsAction->setEnabled(false); } void MainWindow::fileLoad() { if(!maybeSave()) return; KConfigGroup conf(KSharedConfig::openConfig(), "MainWindow"); QString dir = conf.readEntry("LastOpenDir", ""); const QString& fileName = QFileDialog::getOpenFileName(this, i18n("Open File"), dir, i18n("Portable game notation (*.pgn)")); if(fileName.isEmpty()) return; Manager::self()->reset(); Protocol::setWhiteProtocol(new LocalProtocol()); Protocol::setBlackProtocol(new LocalProtocol()); connect(Protocol::white(), &Protocol::error, this, &MainWindow::protocolError); connect(Protocol::black(), &Protocol::error, this, &MainWindow::protocolError); m_loadFileName = fileName; Manager::self()->initialize(); m_saveAction->setEnabled(false); } void MainWindow::protocolInitSuccesful() { qCDebug(LOG_KNIGHTS) << "Show Clock:" << Settings::showClock() << "Show Console:" << Settings::showConsole(); QString whiteName = Protocol::white()->playerName(); QString blackName = Protocol::black()->playerName(); updateCaption(); // show clock action button if timer active // show clock dock widget if timer active and configuration file entry has visible = true if(Manager::self()->timeControlEnabled(White) || Manager::self()->timeControlEnabled(Black)) { actionCollection()->action(QLatin1String("show_clock"))->setVisible(true); m_playerClock->setPlayerName(White, Protocol::white()->playerName()); m_playerClock->setPlayerName(Black, Protocol::black()->playerName()); m_playerClock->setTimeLimit(White, Manager::self()->timeLimit(White)); m_playerClock->setTimeLimit(Black, Manager::self()->timeLimit(Black)); if(Settings::showClock()) { m_clockDock->setVisible(true); actionCollection()->action(QLatin1String("show_clock"))->setChecked(true); } else { m_clockDock->setVisible(false); actionCollection()->action(QLatin1String("show_clock"))->setChecked(false); } } else { m_clockDock->setVisible(false); actionCollection()->action(QLatin1String("show_clock"))->setVisible(false); } Protocol::Features f = Protocol::NoFeatures; if(Protocol::white()->isLocal() && !(Protocol::black()->isLocal())) f = Protocol::black()->supportedFeatures(); else if(Protocol::black()->isLocal() && !(Protocol::white()->isLocal())) f = Protocol::white()->supportedFeatures(); else if(!(Protocol::black()->isLocal()) && !(Protocol::white()->isLocal())) { // These protocol features make sense when neither player is local f = Protocol::Pause | Protocol::Adjourn | Protocol::Abort; f &= Protocol::white()->supportedFeatures(); f &= Protocol::black()->supportedFeatures(); } QMap::ConstIterator it; for(it = m_protocolFeatures.constBegin(); it != m_protocolFeatures.constEnd(); ++it) actionCollection()->action(QLatin1String(it.key()))->setEnabled(f & it.value()); // show console action button if protocol allows a console // show console dock widget if protocol allows and configuration file entry has visible = true // finally, hide any dock widget not needed - in case it is still active from previous game actionCollection()->action(QLatin1String("show_console_white"))->setVisible(false); actionCollection()->action(QLatin1String("show_console_black"))->setVisible(false); actionCollection()->action(QLatin1String("show_chat"))->setVisible(false); QList list; list << Protocol::black()->toolWidgets(); list << Protocol::white()->toolWidgets(); for (const auto& data : list) { switch(data.type) { case Protocol::ConsoleToolWidget: if(data.owner == White) { m_wconsoleDock->setWindowTitle(data.title); m_wconsoleDock->setWidget(data.widget); actionCollection()->action(QLatin1String("show_console_white"))->setVisible(true); if(Settings::showConsole()) { m_wconsoleDock->setVisible(true); actionCollection()->action(QLatin1String("show_console_white"))->setChecked(true); } else { m_wconsoleDock->setVisible(false); actionCollection()->action(QLatin1String("show_console_white"))->setChecked(false); } } else { m_bconsoleDock->setWindowTitle(data.title); m_bconsoleDock->setWidget(data.widget); actionCollection()->action(QLatin1String("show_console_black"))->setVisible(true); if(Settings::showConsole()) { m_bconsoleDock->setVisible(true); actionCollection()->action(QLatin1String("show_console_black"))->setChecked(true); } else { m_bconsoleDock->setVisible(false); actionCollection()->action(QLatin1String("show_console_black"))->setChecked(false); } } break; case Protocol::ChatToolWidget: m_chatDock->setWindowTitle(data.title); m_chatDock->setWidget(data.widget); actionCollection()->action(QLatin1String("show_chat"))->setVisible(true); if(Settings::showChat()) { m_chatDock->setVisible(true); actionCollection()->action(QLatin1String("show_chat"))->setChecked(true); } else { m_chatDock->setVisible(false); actionCollection()->action(QLatin1String("show_chat"))->setChecked(false); } break; default: break; } } if(!actionCollection()->action(QLatin1String("show_console_white"))->isVisible()) m_wconsoleDock->hide(); if(!actionCollection()->action(QLatin1String("show_console_black"))->isVisible()) m_bconsoleDock->hide(); if(!actionCollection()->action(QLatin1String("show_chat"))->isVisible()) m_chatDock->hide(); Manager::self()->startGame(); if(m_loadFileName.isEmpty()) m_view->setupBoard(m_themeProvider); else { int speed = Settings::animationSpeed(); Settings::setAnimationSpeed(Settings::EnumAnimationSpeed::Instant); m_view->setupBoard(m_themeProvider); Manager::self()->loadGameHistoryFrom(m_loadFileName); setCaption(m_loadFileName); m_loadFileName.clear(); Settings::setAnimationSpeed(speed); } } void MainWindow::protocolError(Protocol::ErrorCode errorCode, const QString& errorString) { if(errorCode != Protocol::UserCancelled) KMessageBox::error(this, errorString, Protocol::stringFromErrorCode(errorCode)); Protocol::white()->deleteLater(); Protocol::black()->deleteLater(); //fileNew(); } void MainWindow::optionsPreferences() { if(KConfigDialog::showDialog(QLatin1String("settings"))) return; KConfigDialog *dialog = new KConfigDialog(this, QLatin1String("settings"), Settings::self()); QWidget *generalSettingsDlg = new QWidget; ui_prefs_base.setupUi(generalSettingsDlg); dialog->addPage(generalSettingsDlg, i18n("General"), QLatin1String("games-config-options")); connect(dialog, &KConfigDialog::settingsChanged, m_view, &KnightsView::settingsChanged); EngineSettings* engineSettings = new EngineSettings(this); dialog->addPage(engineSettings, i18n("Computer Engines"), QLatin1String("computer")); connect(dialog, &KConfigDialog::accepted, engineSettings, &EngineSettings::save); QWidget* accessDlg = new QWidget; ui_prefs_access.setupUi(accessDlg); dialog->addPage(accessDlg, i18n("Accessibility"), QLatin1String("preferences-desktop-accessibility")); QWidget* themeDlg = new KgThemeSelector(m_themeProvider, KgThemeSelector::EnableNewStuffDownload, dialog); dialog->addPage(themeDlg, i18n("Theme"), QLatin1String("games-config-theme")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } +void MainWindow::resign() { + int rc = KMessageBox::questionYesNo(this, i18n("Do you really want to resign?"), i18n("Resign")); + if (rc == KMessageBox::Yes) + Manager::self()->resign(); +} + void MainWindow::undo() { if(!Protocol::white()->isLocal() && !Protocol::black()->isLocal()) { // No need to pause the game if both players are local QAction* pa = actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause))); if(pa) pa->setChecked(true); } Manager::self()->undo(); } void MainWindow::redo() { Manager::self()->redo(); if(!Protocol::white()->isLocal() && !Protocol::black()->isLocal()) { // No need to pause the game if both players are local QAction* pa = actionCollection()->action(QLatin1String(KStandardGameAction::name(KStandardGameAction::Pause))); if(pa && !Manager::self()->canRedo()) pa->setChecked(false); } } void MainWindow::gameChanged() { m_saveAction->setEnabled(true); m_saveAsAction->setEnabled(true); } void MainWindow::setShowClockSetting(bool value) { Settings::setShowClock(value); } void MainWindow::setShowHistorySetting(bool value) { Settings::setShowHistory(value); } void MainWindow::setShowConsoleSetting() { if((actionCollection()->action(QLatin1String("show_console_white"))->isChecked()) && (actionCollection()->action(QLatin1String("show_console_white"))->isVisible())) Settings::setShowConsole(true); else if((actionCollection()->action(QLatin1String("show_console_black"))->isChecked()) && (actionCollection()->action(QLatin1String("show_console_black"))->isVisible())) Settings::setShowConsole(true); else Settings::setShowConsole(false); } void MainWindow::setShowChatSetting(bool value) { Settings::setShowChat(value); } void MainWindow::exitKnights() { //This will close the gnuchess/crafty/whatever process if it's running. Manager::self()->reset(); Settings::self()->save(); } void MainWindow::updateCaption() { if(Protocol::white() && Protocol::black()) setCaption(i18n("%1 vs. %2", Protocol::white()->playerName(), Protocol::black()->playerName())); } bool MainWindow::maybeSave() { if(!Manager::self()->isGameActive()) return true; if(!Settings::askDiscard()) return true; Settings::setDontAskInternal(QString()); int result = KMessageBox::warningYesNoCancel(QApplication::activeWindow(), i18n("This will end your game.\nWould you like to save the move history?"), QString(), KStandardGuiItem::save(), KStandardGuiItem::discard(), KStandardGuiItem::cancel(), QLatin1String(DontAskDiscard)); KMessageBox::ButtonCode res; Settings::setAskDiscard(KMessageBox::shouldBeShownYesNo(QLatin1String(DontAskDiscard), res)); if(result == KMessageBox::Yes) fileSave(); return result != KMessageBox::Cancel; } void MainWindow::fileSave() { if(m_fileName.isEmpty()) { KConfigGroup conf(KSharedConfig::openConfig(), "MainWindow"); QString dir = conf.readEntry("LastOpenDir", ""); m_fileName = QFileDialog::getSaveFileName(this, i18n("Save"), dir, i18n("Portable game notation (*.pgn)")); if (m_fileName.isEmpty())// "Cancel" was clicked return; //save new "last open directory" int pos = m_fileName.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = m_fileName.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } } Manager::self()->saveGameHistoryAs(m_fileName); setCaption(m_fileName); m_saveAction->setEnabled(false); } void MainWindow::fileSaveAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWindow"); QString dir = conf.readEntry("LastOpenDir", ""); QString fileName = QFileDialog::getSaveFileName(this, i18n("Save As"), dir, i18n("Portable game notation (*.pgn)")); if (fileName.isEmpty())// "Cancel" was clicked return; if (fileName.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) fileName.append(QLatin1String(".lml")); //save new "last open directory" int pos = fileName.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = fileName.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } m_fileName = fileName; Manager::self()->saveGameHistoryAs(m_fileName); setCaption(m_fileName); } diff --git a/src/knights.h b/src/knights.h index 4f00d45..88efdd1 100644 --- a/src/knights.h +++ b/src/knights.h @@ -1,105 +1,106 @@ /*************************************************************************** File : knights.h Project : Knights Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2016-1017 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2010-2012 by Miha Čančula (miha@noughmad.eu) ***************************************************************************/ /*************************************************************************** * * * This program 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 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef KNIGHTS_H #define KNIGHTS_H #include "ui_prefs_base.h" #include "ui_prefs_access.h" #include "proto/protocol.h" #include #include class KToggleAction; class KgThemeProvider; namespace Knights { class Protocol; class KnightsView; class ClockWidget; class MainWindow : public KXmlGuiWindow { Q_OBJECT public: MainWindow(); public Q_SLOTS: void fileSave(); private Q_SLOTS: void fileNew(); void fileLoad(); void fileSaveAs(); + void resign(); void undo(); void redo(); void gameChanged(); void optionsPreferences(); void protocolInitSuccesful(); void protocolError(Protocol::ErrorCode errorCode, const QString& errorString); void setShowClockSetting(bool); void setShowHistorySetting(bool value); void setShowConsoleSetting(); void setShowChatSetting(bool); void updateCaption(); void exitKnights(); private: void setupDocks(); void setupActions(); bool maybeSave(); Ui::prefs_base ui_prefs_base; Ui::prefs_access ui_prefs_access; KnightsView* m_view; QDockWidget* m_clockDock; ClockWidget* m_playerClock; QDockWidget* m_wconsoleDock; QDockWidget* m_bconsoleDock; QDockWidget* m_chatDock; QDockWidget* m_historyDock; QAction* m_saveAction; QAction* m_saveAsAction; KToggleAction* m_pauseAction; KToggleAction* m_toolbarAction; KToggleAction* m_statusbarAction; QMap m_protocolFeatures; KgThemeProvider* m_themeProvider; QString m_loadFileName; QString m_fileName; }; } #endif // KNIGHTS_H