diff --git a/game/Makefile.am b/game/Makefile.am index cb95c05..af8a41a 100644 --- a/game/Makefile.am +++ b/game/Makefile.am @@ -43,6 +43,7 @@ libgame_a_SOURCES= \ board_cross.cpp \ board_search.cpp \ settings.cpp settings.h \ + navigation.cpp navigation.h \ game.cpp game.h \ game_move_cmd.h game_move_cmd.cpp \ turn_cmd.cpp turn_cmd.h \ diff --git a/game/debug.h b/game/debug.h index 51451e1..fe2b12f 100644 --- a/game/debug.h +++ b/game/debug.h @@ -28,6 +28,11 @@ #ifdef DEBUG # include +# include + +using std::cerr; +using std::endl; + // Assertion macro: if the condition is not verified, print a message on stderr // and stops execution, otherwise do nothing. # define ASSERT(cond, msg) \ diff --git a/game/duplicate.cpp b/game/duplicate.cpp index 4b65332..615a06e 100644 --- a/game/duplicate.cpp +++ b/game/duplicate.cpp @@ -33,7 +33,6 @@ #include "game_move_cmd.h" #include "ai_player.h" #include "settings.h" -#include "turn_cmd.h" #include "debug.h" @@ -111,7 +110,7 @@ int Duplicate::start() for (unsigned int i = 0; i < getNPlayers(); i++) { Command *pCmd = new PlayerRackCmd(*m_players[i], newRack); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); // Nobody has played yet in this round m_hasPlayed[i] = false; } @@ -161,7 +160,7 @@ void Duplicate::tryEndTurn() void Duplicate::recordPlayerMove(const Move &iMove, Player &ioPlayer) { Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); m_hasPlayed[ioPlayer.getId()] = true; } @@ -216,7 +215,7 @@ void Duplicate::endTurn() // Play the best word on the board Command *pCmd = new GameMoveCmd(*this, bestMove, getPlayer(imax).getLastRack(), imax); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); // Leave the same reliquate to all players // This is required by the start() method which will be called to @@ -227,11 +226,11 @@ void Duplicate::endTurn() if (i != imax) { Command *pCmd = new PlayerRackCmd(*m_players[i], pld); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } } - newTurn(); + accessNavigation().newTurn(); // Start next turn... start(); diff --git a/game/freegame.cpp b/game/freegame.cpp index 0be39d0..e4b1596 100644 --- a/game/freegame.cpp +++ b/game/freegame.cpp @@ -39,7 +39,6 @@ #include "ai_player.h" #include "settings.h" #include "turn.h" -#include "turn_cmd.h" #include "debug.h" @@ -110,7 +109,7 @@ void FreeGame::playAI(unsigned int p) void FreeGame::recordPlayerMove(const Move &iMove, Player &ioPlayer) { Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } @@ -124,7 +123,7 @@ int FreeGame::start() const PlayedRack &newRack = helperSetRackRandom(getPlayer(i).getCurrentRack(), false, RACK_NEW); Command *pCmd = new PlayerRackCmd(*m_players[i], newRack); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } m_currPlayer = 0; @@ -146,7 +145,7 @@ int FreeGame::endTurn() Command *pCmd = new GameMoveCmd(*this, move, getCurrentPlayer().getLastRack(), m_currPlayer); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); // Complete the rack for the player that just played if (move.getType() == Move::VALID_ROUND || @@ -158,7 +157,7 @@ int FreeGame::endTurn() helperSetRackRandom(getCurrentPlayer().getCurrentRack(), false, RACK_NEW); Command *pCmd = new PlayerRackCmd(*m_players[m_currPlayer], newRack); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } catch (EndGameException &e) { @@ -171,7 +170,7 @@ int FreeGame::endTurn() // Next player nextPlayer(); - newTurn(); + accessNavigation().newTurn(); // If this player is an AI, make it play now if (!getCurrentPlayer().isHuman()) diff --git a/game/game.cpp b/game/game.cpp index f5c2751..b85604e 100644 --- a/game/game.cpp +++ b/game/game.cpp @@ -55,8 +55,6 @@ Game::Game(const Dictionary &iDic): m_points = 0; m_currPlayer = 0; m_finished = false; - m_currTurn = -1; - newTurn(); } @@ -66,10 +64,6 @@ Game::~Game() { delete p; } - BOOST_FOREACH(Command *c, m_turnCommands) - { - delete c; - } } @@ -85,6 +79,12 @@ int Game::back(unsigned int n) if (m_history.getSize() < n) throw GameException("Cannot go back that far"); + for (unsigned int i = 0; i < n+1; ++i) + { + m_navigation.prevTurn(); + } + m_navigation.clearFuture(); +#if 0 for (unsigned int i = 0; i < n; i++) { prevPlayer(); @@ -113,6 +113,7 @@ int Game::back(unsigned int n) m_players[m_currPlayer]->removeLastTurn(); m_history.removeLastTurn(); } +#endif return 0; } @@ -198,7 +199,6 @@ void Game::realBag(Bag &ioBag) const PlayedRack Game::helperSetRackRandom(const PlayedRack &iPld, bool iCheck, set_rack_mode mode) const { - ASSERT(p < getNPlayers(), "Wrong player number"); // FIXME: RACK_MANUAL shouldn't be in the enum ASSERT(mode != RACK_MANUAL, "Invalid rack mode"); @@ -618,51 +618,3 @@ int Game::checkPlayedWord(const wstring &iCoord, return 0; } -/********************************************************* - *********************************************************/ - -void Game::prevTurn() -{ - if (m_currTurn > 0) - { - --m_currTurn; - m_turnCommands[m_currTurn]->undo(); - } -} - - -void Game::nextTurn() -{ - if (m_currTurn + 1 < m_turnCommands.size()) - { - m_turnCommands[m_currTurn]->execute(); - ++m_currTurn; - } -} - - -void Game::firstTurn() -{ - while (m_currTurn > 0) - { - prevTurn(); - } -} - - -void Game::lastTurn() -{ - while (m_currTurn + 1 < m_turnCommands.size()) - { - nextTurn(); - } -} - - -void Game::newTurn() -{ - lastTurn(); - m_turnCommands.push_back(new TurnCmd); - ++m_currTurn; -} - diff --git a/game/game.h b/game/game.h index 4c5bfd6..a09cdb1 100644 --- a/game/game.h +++ b/game/game.h @@ -28,6 +28,7 @@ #include "bag.h" #include "board.h" #include "history.h" +#include "navigation.h" class Dictionary; class Player; @@ -35,7 +36,6 @@ class PlayedRack; class Round; class Rack; class Turn; -class TurnCmd; using namespace std; @@ -222,10 +222,8 @@ public: void addPoints(int iPoints) { m_points += iPoints; } - void prevTurn(); - void nextTurn(); - void firstTurn(); - void lastTurn(); + const Navigation & getNavigation() const { return m_navigation; } + Navigation & accessNavigation() { return m_navigation; } private: /// Variant @@ -239,8 +237,9 @@ private: */ History m_history; - int m_points; + Navigation m_navigation; + int m_points; // TODO: check what should be private and what should be protected protected: @@ -255,17 +254,12 @@ protected: /// Bag Bag m_bag; - vector m_turnCommands; - unsigned int m_currTurn; - bool m_finished; /********************************************************* * Helper functions *********************************************************/ - void newTurn(); - /** * Complete the given rack randomly. * diff --git a/game/navigation.cpp b/game/navigation.cpp new file mode 100644 index 0000000..82dfcd2 --- /dev/null +++ b/game/navigation.cpp @@ -0,0 +1,118 @@ +/******************************************************************* + * Eliot + * Copyright (C) 2008 Olivier Teulière + * Authors: Olivier Teulière + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#include + +#include "navigation.h" +#include "turn_cmd.h" +#include "debug.h" + + +Navigation::Navigation() + : m_currTurn(0) +{ + newTurn(); +} + + +Navigation::~Navigation() +{ + BOOST_FOREACH(Command *c, m_turnCommands) + { + delete c; + } +} + + +void Navigation::newTurn() +{ + lastTurn(); + m_turnCommands.push_back(new TurnCmd); + ++m_currTurn; +} + + +void Navigation::addAndExecute(Command *iCmd) +{ + ASSERT(isLastTurn(), "Trying to add a command to an old turn!"); + ASSERT(m_currTurn >= 1, "Bug in the turns vector"); + ASSERT(m_currTurn - 1 < m_turnCommands.size(), "Bug in the turns vector"); + m_turnCommands[m_currTurn - 1]->addAndExecute(iCmd); +} + + +void Navigation::prevTurn() +{ + if (m_currTurn > 0) + { + --m_currTurn; + m_turnCommands[m_currTurn]->undo(); + } +} + + +void Navigation::nextTurn() +{ + if (m_currTurn < m_turnCommands.size()) + { + m_turnCommands[m_currTurn]->execute(); + ++m_currTurn; + } +} + + +void Navigation::firstTurn() +{ + while (m_currTurn > 0) + { + prevTurn(); + } +} + + +void Navigation::lastTurn() +{ + while (m_currTurn < m_turnCommands.size()) + { + nextTurn(); + } +} + + +void Navigation::clearFuture() +{ + // When there is no future, don't do anything + if (isLastTurn()) + return; + + for (unsigned int i = m_currTurn; i < m_turnCommands.size(); ++i) + { + delete m_turnCommands[i]; + } + while (m_turnCommands.size() > m_currTurn) + { + m_turnCommands.pop_back(); + } + newTurn(); + // Sanity check + ASSERT(isLastTurn(), + "After removing the next turns, we should be at the last turn"); +} + diff --git a/game/navigation.h b/game/navigation.h new file mode 100644 index 0000000..cd6fb93 --- /dev/null +++ b/game/navigation.h @@ -0,0 +1,62 @@ +/******************************************************************* + * Eliot + * Copyright (C) 2008 Olivier Teulière + * Authors: Olivier Teulière + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#ifndef _NAVIGATION_H +#define _NAVIGATION_H + +#include + +class TurnCmd; +class Command; + +using namespace std; + + +class Navigation +{ + public: + Navigation(); + ~Navigation(); + + void newTurn(); + void addAndExecute(Command *iCmd); + + unsigned int getCurrTurn() const { return m_currTurn; } + bool isFirstTurn() const { return m_currTurn == 0; } + bool isLastTurn() const { return m_currTurn == m_turnCommands.size(); } + + void firstTurn(); + void prevTurn(); + void nextTurn(); + void lastTurn(); + /** + * Get rid of the future turns of the game, the current turn + * becoming the last one. + */ + void clearFuture(); + + private: + vector m_turnCommands; + unsigned int m_currTurn; + +}; + +#endif + diff --git a/game/training.cpp b/game/training.cpp index 20aec59..11f9ef9 100644 --- a/game/training.cpp +++ b/game/training.cpp @@ -40,7 +40,6 @@ #include "game_move_cmd.h" #include "training.h" #include "encoding.h" -#include "turn_cmd.h" #include "debug.h" @@ -60,7 +59,7 @@ void Training::setRackRandom(bool iCheck, set_rack_mode mode) const PlayedRack &newRack = helperSetRackRandom(getCurrentPlayer().getCurrentRack(), iCheck, mode); Command *pCmd = new PlayerRackCmd(*m_players[m_currPlayer], newRack); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } @@ -131,7 +130,7 @@ void Training::recordPlayerMove(const Move &iMove, Player &ioPlayer) // (called in this class in endTurn()). // See the big comment in game.cpp, line 96 Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); + accessNavigation().addAndExecute(pCmd); } @@ -154,8 +153,8 @@ void Training::endTurn() Command *pCmd = new GameMoveCmd(*this, move, getCurrentPlayer().getLastRack(), m_currPlayer); - m_turnCommands[m_currTurn]->addAndExecute(pCmd); - newTurn(); + accessNavigation().addAndExecute(pCmd); + accessNavigation().newTurn(); } diff --git a/game/turn_cmd.cpp b/game/turn_cmd.cpp index d0aa158..2d2f8d4 100644 --- a/game/turn_cmd.cpp +++ b/game/turn_cmd.cpp @@ -24,6 +24,13 @@ #include "player.h" +TurnCmd::TurnCmd() +{ + // Fake execution + execute(); +} + + TurnCmd::~TurnCmd() { BOOST_FOREACH(Command *cmd, m_commands) diff --git a/game/turn_cmd.h b/game/turn_cmd.h index c15a509..faea558 100644 --- a/game/turn_cmd.h +++ b/game/turn_cmd.h @@ -35,6 +35,7 @@ using namespace std; class TurnCmd: public Command { public: + TurnCmd(); virtual ~TurnCmd(); /** @@ -43,6 +44,8 @@ class TurnCmd: public Command */ void addAndExecute(Command *iCmd); + bool isEmpty() const { return m_commands.empty(); } + protected: virtual void doExecute(); virtual void doUndo(); diff --git a/qt/main_window.cpp b/qt/main_window.cpp index e5e7cf9..ede1778 100644 --- a/qt/main_window.cpp +++ b/qt/main_window.cpp @@ -86,6 +86,9 @@ MainWindow::MainWindow(QWidget *iParent) QObject::connect(this, SIGNAL(gameChanged(const Game*)), this, SLOT(updateForGame(const Game*))); + QObject::connect(this, SIGNAL(gameUpdated()), + this, SLOT(refresh())); + refresh(); // Status bar statusBar()->addWidget(new QLabel, 1); @@ -150,6 +153,15 @@ MainWindow::MainWindow(QWidget *iParent) emit gameChangedNonConst(NULL); emit gameChanged(NULL); + QObject::connect(m_ui.buttonFirst, SIGNAL(clicked()), + this, SLOT(onGameFirst())); + QObject::connect(m_ui.buttonPrev, SIGNAL(clicked()), + this, SLOT(onGamePrev())); + QObject::connect(m_ui.buttonNext, SIGNAL(clicked()), + this, SLOT(onGameNext())); + QObject::connect(m_ui.buttonLast, SIGNAL(clicked()), + this, SLOT(onGameLast())); + // Load dictionary QSettings qs(ORGANIZATION, PACKAGE_NAME); QString dicPath = qs.value(PrefsDialog::kINTF_DIC_PATH, "").toString(); @@ -197,6 +209,31 @@ void MainWindow::destroyCurrentGame() } +void MainWindow::refresh() +{ + if (m_game == NULL) + { + m_ui.buttonFirst->setEnabled(false); + m_ui.buttonPrev->setEnabled(false); + m_ui.buttonNext->setEnabled(false); + m_ui.buttonLast->setEnabled(false); + // XXX: tmp + m_ui.labelTurnNb->setText(""); + } + else + { + bool isFirstTurn = m_game->getNavigation().isFirstTurn(); + bool isLastTurn = m_game->getNavigation().isLastTurn(); + m_ui.buttonFirst->setEnabled(!isFirstTurn); + m_ui.buttonPrev->setEnabled(!isFirstTurn); + m_ui.buttonNext->setEnabled(!isLastTurn); + m_ui.buttonLast->setEnabled(!isLastTurn); + // XXX: tmp + m_ui.labelTurnNb->setText(QString("Turn: %1").arg(m_game->getNavigation().getCurrTurn())); + } +} + + void MainWindow::updateForGame(const Game *iGame) { if (iGame == NULL) @@ -715,3 +752,43 @@ void MainWindow::onHelpAbout() aboutBox->exec(); } + +void MainWindow::onGameFirst() +{ + if (m_game == NULL) + return; + + m_game->accessNavigation().firstTurn(); + emit gameUpdated(); +} + + +void MainWindow::onGamePrev() +{ + if (m_game == NULL) + return; + + m_game->accessNavigation().prevTurn(); + emit gameUpdated(); +} + + +void MainWindow::onGameNext() +{ + if (m_game == NULL) + return; + + m_game->accessNavigation().nextTurn(); + emit gameUpdated(); +} + + +void MainWindow::onGameLast() +{ + if (m_game == NULL) + return; + + m_game->accessNavigation().lastTurn(); + emit gameUpdated(); +} + diff --git a/qt/main_window.h b/qt/main_window.h index bc35bbd..f6bd274 100644 --- a/qt/main_window.h +++ b/qt/main_window.h @@ -73,6 +73,14 @@ private slots: void onWindowsDicTools(); void onHelpAbout(); + void onGameFirst(); + void onGamePrev(); + void onGameNext(); + void onGameLast(); + + /** Perform some updates when the game is updated */ + void refresh(); + /** * Perform several updates when the game changes (title bar, status bar, * grey out some menu items, ...) diff --git a/qt/ui/main_window.ui b/qt/ui/main_window.ui index 9d0c0ad..9848ace 100644 --- a/qt/ui/main_window.ui +++ b/qt/ui/main_window.ui @@ -17,15 +17,59 @@ :/images/eliot.xpm:/images/eliot.xpm - - - 0 - 26 - 747 - 568 - - + + + + + + First + + + + + + + Prev + + + + + + + Next + + + + + + + Last + + + + + + + Turn: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -72,20 +116,11 @@ 0 0 747 - 26 - - - - - - - 0 - 594 - 747 - 23 + 25 + true @@ -146,4 +181,5 @@ + diff --git a/utils/eliottxt.cpp b/utils/eliottxt.cpp index 5f3fd0a..f7cf9ee 100644 --- a/utils/eliottxt.cpp +++ b/utils/eliottxt.cpp @@ -576,16 +576,16 @@ void loop_training(Training &iGame) switch (token[0]) { case L'p': - iGame.prevTurn(); + iGame.accessNavigation().prevTurn(); break; case L'n': - iGame.nextTurn(); + iGame.accessNavigation().nextTurn(); break; case L'f': - iGame.firstTurn(); + iGame.accessNavigation().firstTurn(); break; case L'l': - iGame.lastTurn(); + iGame.accessNavigation().lastTurn(); break; } } @@ -705,16 +705,16 @@ void loop_freegame(FreeGame &iGame) switch (token[0]) { case L'p': - iGame.prevTurn(); + iGame.accessNavigation().prevTurn(); break; case L'n': - iGame.nextTurn(); + iGame.accessNavigation().nextTurn(); break; case L'f': - iGame.firstTurn(); + iGame.accessNavigation().firstTurn(); break; case L'l': - iGame.lastTurn(); + iGame.accessNavigation().lastTurn(); break; } } @@ -842,16 +842,16 @@ void loop_duplicate(Duplicate &iGame) switch (token[0]) { case L'p': - iGame.prevTurn(); + iGame.accessNavigation().prevTurn(); break; case L'n': - iGame.nextTurn(); + iGame.accessNavigation().nextTurn(); break; case L'f': - iGame.firstTurn(); + iGame.accessNavigation().firstTurn(); break; case L'l': - iGame.lastTurn(); + iGame.accessNavigation().lastTurn(); break; } } @@ -1120,9 +1120,3 @@ int main(int argc, char *argv[]) return 0; } -/// Local Variables: -/// mode: c++ -/// mode: hs-minor -/// c-basic-offset: 4 -/// indent-tabs-mode: nil -/// End: