eliot-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Eliot-dev] eliot game/Makefile.am game/duplicate.cpp game/...


From: eliot-dev
Subject: [Eliot-dev] eliot game/Makefile.am game/duplicate.cpp game/...
Date: Sun, 23 Nov 2008 08:18:12 +0000

CVSROOT:        /cvsroot/eliot
Module name:    eliot
Changes by:     Olivier Teulière <ipkiss>      08/11/23 08:18:12

Modified files:
        game           : Makefile.am duplicate.cpp duplicate.h 
                         freegame.cpp freegame.h game.cpp game.h 
                         game_io.cpp history.cpp history.h move.cpp 
                         move.h player.cpp player.h training.cpp 
                         training.h turn.cpp turn.h 
        utils          : eliottxt.cpp 
Added files:
        game           : command.cpp command.h game_move_cmd.cpp 
                         game_move_cmd.h player_move_cmd.cpp 
                         player_move_cmd.h player_rack_cmd.cpp 
                         player_rack_cmd.h turn_cmd.cpp turn_cmd.h 

Log message:
         - The main game modifications (playing a move, setting a new rack, 
...) are now done using the Command design pattern, which alows undoing these 
changes easily.
         - Added support for navigation in the game history to the text 
interface (not unit-tested yet)

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/eliot/game/Makefile.am?cvsroot=eliot&r1=1.19&r2=1.20
http://cvs.savannah.gnu.org/viewcvs/eliot/game/duplicate.cpp?cvsroot=eliot&r1=1.21&r2=1.22
http://cvs.savannah.gnu.org/viewcvs/eliot/game/duplicate.h?cvsroot=eliot&r1=1.15&r2=1.16
http://cvs.savannah.gnu.org/viewcvs/eliot/game/freegame.cpp?cvsroot=eliot&r1=1.23&r2=1.24
http://cvs.savannah.gnu.org/viewcvs/eliot/game/freegame.h?cvsroot=eliot&r1=1.13&r2=1.14
http://cvs.savannah.gnu.org/viewcvs/eliot/game/game.cpp?cvsroot=eliot&r1=1.43&r2=1.44
http://cvs.savannah.gnu.org/viewcvs/eliot/game/game.h?cvsroot=eliot&r1=1.38&r2=1.39
http://cvs.savannah.gnu.org/viewcvs/eliot/game/game_io.cpp?cvsroot=eliot&r1=1.11&r2=1.12
http://cvs.savannah.gnu.org/viewcvs/eliot/game/history.cpp?cvsroot=eliot&r1=1.13&r2=1.14
http://cvs.savannah.gnu.org/viewcvs/eliot/game/history.h?cvsroot=eliot&r1=1.13&r2=1.14
http://cvs.savannah.gnu.org/viewcvs/eliot/game/move.cpp?cvsroot=eliot&r1=1.3&r2=1.4
http://cvs.savannah.gnu.org/viewcvs/eliot/game/move.h?cvsroot=eliot&r1=1.2&r2=1.3
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player.cpp?cvsroot=eliot&r1=1.17&r2=1.18
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player.h?cvsroot=eliot&r1=1.23&r2=1.24
http://cvs.savannah.gnu.org/viewcvs/eliot/game/training.cpp?cvsroot=eliot&r1=1.24&r2=1.25
http://cvs.savannah.gnu.org/viewcvs/eliot/game/training.h?cvsroot=eliot&r1=1.18&r2=1.19
http://cvs.savannah.gnu.org/viewcvs/eliot/game/turn.cpp?cvsroot=eliot&r1=1.13&r2=1.14
http://cvs.savannah.gnu.org/viewcvs/eliot/game/turn.h?cvsroot=eliot&r1=1.11&r2=1.12
http://cvs.savannah.gnu.org/viewcvs/eliot/game/command.cpp?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/command.h?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/game_move_cmd.cpp?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/game_move_cmd.h?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player_move_cmd.cpp?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player_move_cmd.h?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player_rack_cmd.cpp?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/player_rack_cmd.h?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/turn_cmd.cpp?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/game/turn_cmd.h?cvsroot=eliot&rev=1.1
http://cvs.savannah.gnu.org/viewcvs/eliot/utils/eliottxt.cpp?cvsroot=eliot&r1=1.25&r2=1.26

Patches:
Index: game/Makefile.am
===================================================================
RCS file: /cvsroot/eliot/eliot/game/Makefile.am,v
retrieving revision 1.19
retrieving revision 1.20
diff -u -b -r1.19 -r1.20
--- game/Makefile.am    22 Nov 2008 13:09:29 -0000      1.19
+++ game/Makefile.am    23 Nov 2008 08:18:03 -0000      1.20
@@ -23,13 +23,18 @@
 
 libgame_a_SOURCES=           \
     game_exception.cpp game_exception.h \
+    command.h command.cpp \
     rack.cpp rack.h                 \
     pldrack.cpp pldrack.h           \
     round.cpp round.h               \
     move.cpp move.h                 \
     results.cpp results.h           \
     bag.cpp bag.h                   \
+    turn.cpp turn.h \
+    history.cpp history.h \
     player.cpp player.h             \
+    player_move_cmd.cpp player_move_cmd.h \
+    player_rack_cmd.cpp player_rack_cmd.h \
     ai_player.h                     \
     ai_percent.cpp ai_percent.h     \
     coord.cpp coord.h               \
@@ -38,13 +43,13 @@
     board_cross.cpp                 \
     board_search.cpp                \
     settings.cpp settings.h         \
-    turn.cpp turn.h                 \
-    history.cpp history.h           \
     game.cpp game.h                 \
-    game_io.cpp                     \
+    game_move_cmd.h game_move_cmd.cpp \
+    turn_cmd.cpp turn_cmd.h \
     duplicate.cpp duplicate.h       \
     freegame.cpp freegame.h         \
     training.cpp training.h         \
     game_factory.cpp game_factory.h \
+    game_io.cpp \
     debug.h
 

Index: game/duplicate.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/duplicate.cpp,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -b -r1.21 -r1.22
--- game/duplicate.cpp  22 Nov 2008 14:40:26 -0000      1.21
+++ game/duplicate.cpp  23 Nov 2008 08:18:05 -0000      1.22
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 2005-2007 Olivier Teulière
+ * Copyright (C) 2005-2008 Olivier Teulière
  * Authors: Olivier Teulière <ipkiss @@ gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -28,8 +28,12 @@
 #include "pldrack.h"
 #include "results.h"
 #include "player.h"
+#include "player_move_cmd.h"
+#include "player_rack_cmd.h"
+#include "game_move_cmd.h"
 #include "ai_player.h"
 #include "settings.h"
+#include "turn_cmd.h"
 #include "debug.h"
 
 
@@ -51,15 +55,16 @@
 
     // If we reach this point, either the move is valid and we can use the
     // "round" variable, or it is invalid but played nevertheless
+    Player &currPlayer = *m_players[m_currPlayer];
     if (res == 0)
     {
         // Everything is OK, we can play the word
-        recordPlayerMove(Move(round), m_currPlayer);
+        recordPlayerMove(Move(round), currPlayer);
     }
     else
     {
         // Record the invalid move of the player
-        recordPlayerMove(Move(iWord, iCoord), m_currPlayer);
+        recordPlayerMove(Move(iWord, iCoord), currPlayer);
     }
 
     // Little hack to handle duplicate games with only AI players.
@@ -78,7 +83,7 @@
     ASSERT(player != NULL, "AI requested for a human player");
 
     player->compute(getDic(), getBoard(), getHistory().beforeFirstRound());
-    const Move move = player->getMove();
+    const Move &move = player->getMove();
     if (move.getType() == Move::CHANGE_LETTERS ||
         move.getType() == Move::PASS)
     {
@@ -86,7 +91,7 @@
         ASSERT(false, "AI tried to cheat!");
     }
 
-    recordPlayerMove(move, p);
+    recordPlayerMove(move, *player);
 }
 
 
@@ -102,25 +107,20 @@
     {
         const PlayedRack &newRack =
             helperSetRackRandom(getCurrentPlayer().getCurrentRack(), true, 
RACK_NEW);
-        m_players[m_currPlayer]->setCurrentRack(newRack);
-    }
-    catch (EndGameException &e)
-    {
-        endGame();
-        return 1;
-    }
-
-    const PlayedRack& pld = m_players[m_currPlayer]->getCurrentRack();
     // All the players have the same rack
     for (unsigned int i = 0; i < getNPlayers(); i++)
     {
-        if (i != m_currPlayer)
-        {
-            m_players[i]->setCurrentRack(pld);
-        }
+            Command *pCmd = new PlayerRackCmd(*m_players[i], newRack);
+            m_turnCommands[m_currTurn]->addAndExecute(pCmd);
         // Nobody has played yet in this round
         m_hasPlayed[i] = false;
     }
+    }
+    catch (EndGameException &e)
+    {
+        endGame();
+        return 1;
+    }
 
     // Little hack to handle duplicate games with only AI players.
     // This will have no effect when there is at least one human player
@@ -158,20 +158,12 @@
 }
 
 
-void Duplicate::recordPlayerMove(const Move &iMove, unsigned int p)
+void Duplicate::recordPlayerMove(const Move &iMove, Player &ioPlayer)
 {
-    ASSERT(p < getNPlayers(), "Wrong player number");
-
-    // Get what was the rack for the current turn
-    Rack oldRack;
-    m_players[p]->getCurrentRack().getRack(oldRack);
-    // Compute the new rack
-    const Rack &newRack = helperComputeRackForMove(oldRack, iMove);
+    Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
 
-    // Update the rack and the score of the playing player
-    m_players[p]->endTurn(iMove, getHistory().getSize(), newRack);
-
-    m_hasPlayed[p] = true;
+    m_hasPlayed[ioPlayer.getId()] = true;
 }
 
 
@@ -222,7 +214,9 @@
     }
 
     // Play the best word on the board
-    helperPlayMove(imax, bestMove);
+    Command *pCmd = new GameMoveCmd(*this, bestMove,
+                                    getPlayer(imax).getLastRack(), imax);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
 
     // Leave the same reliquate to all players
     // This is required by the start() method which will be called to
@@ -232,10 +226,13 @@
     {
         if (i != imax)
         {
-            m_players[i]->setCurrentRack(pld);
+            Command *pCmd = new PlayerRackCmd(*m_players[i], pld);
+            m_turnCommands[m_currTurn]->addAndExecute(pCmd);
         }
     }
 
+    newTurn();
+
     // Start next turn...
     start();
 }

Index: game/duplicate.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/duplicate.h,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -b -r1.15 -r1.16
--- game/duplicate.h    22 Nov 2008 13:09:30 -0000      1.15
+++ game/duplicate.h    23 Nov 2008 08:18:06 -0000      1.16
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 2005-2007 Olivier Teulière
+ * Copyright (C) 2005-2008 Olivier Teulière
  * Authors: Olivier Teulière <ipkiss @@ gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,8 @@
 
 #include "game.h"
 
+class Player;
+
 using std::string;
 using std::wstring;
 
@@ -99,7 +101,7 @@
     Duplicate(const Dictionary &iDic);
 
     /// Record a player move
-    void recordPlayerMove(const Move &iMove, unsigned int p);
+    void recordPlayerMove(const Move &iMove, Player &ioPlayer);
 
     /// Make the AI player whose ID is p play its turn
     void playAI(unsigned int p);

Index: game/freegame.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/freegame.cpp,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -b -r1.23 -r1.24
--- game/freegame.cpp   22 Nov 2008 14:40:26 -0000      1.23
+++ game/freegame.cpp   23 Nov 2008 08:18:06 -0000      1.24
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 2005-2007 Olivier Teulière
+ * Copyright (C) 2005-2008 Olivier Teulière
  * Authors: Olivier Teulière <ipkiss @@ gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -33,9 +33,13 @@
 #include "pldrack.h"
 #include "results.h"
 #include "player.h"
+#include "player_move_cmd.h"
+#include "player_rack_cmd.h"
+#include "game_move_cmd.h"
 #include "ai_player.h"
 #include "settings.h"
 #include "turn.h"
+#include "turn_cmd.h"
 
 #include "debug.h"
 
@@ -63,14 +67,14 @@
         Move move(round);
 
         // Update the rack and the score of the current player
-        recordPlayerMove(move, m_currPlayer);
+        recordPlayerMove(move, *m_players[m_currPlayer]);
     }
     else
     {
         Move move(iWord, iCoord);
 
         // Record the invalid move of the player
-        recordPlayerMove(move, m_currPlayer);
+        recordPlayerMove(move, *m_players[m_currPlayer]);
     }
 
     // Next turn
@@ -88,32 +92,25 @@
     AIPlayer *player = static_cast<AIPlayer*>(m_players[p]);
 
     player->compute(getDic(), getBoard(), getHistory().beforeFirstRound());
-    const Move move = player->getMove();
+    const Move &move = player->getMove();
     if (move.getType() == Move::CHANGE_LETTERS ||
         move.getType() == Move::PASS)
     {
-        ASSERT(checkPass(move.getChangedLetters(), p) == 0, "AI tried to 
cheat!");
+        ASSERT(checkPass(*player, move.getChangedLetters()) == 0,
+               "AI tried to cheat!");
     }
 
     // Update the rack and the score of the current player
-    recordPlayerMove(move, p);
+    recordPlayerMove(move, *player);
 
     endTurn();
 }
 
 
-void FreeGame::recordPlayerMove(const Move &iMove, unsigned int p)
+void FreeGame::recordPlayerMove(const Move &iMove, Player &ioPlayer)
 {
-    ASSERT(p < getNPlayers(), "Wrong player number");
-
-    // Get what was the rack for the current turn
-    Rack oldRack;
-    m_players[p]->getCurrentRack().getRack(oldRack);
-    // Compute the new rack
-    const Rack &newRack = helperComputeRackForMove(oldRack, iMove);
-
-    // Record the invalid move of the player
-    m_players[p]->endTurn(iMove, getHistory().getSize(), newRack);
+    Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
 }
 
 
@@ -126,7 +123,8 @@
     {
         const PlayedRack &newRack =
             helperSetRackRandom(getPlayer(i).getCurrentRack(), false, 
RACK_NEW);
-        m_players[i]->setCurrentRack(newRack);
+        Command *pCmd = new PlayerRackCmd(*m_players[i], newRack);
+        m_turnCommands[m_currTurn]->addAndExecute(pCmd);
     }
 
     m_currPlayer = 0;
@@ -143,9 +141,12 @@
 
 int FreeGame::endTurn()
 {
-    const Move &move = m_players[m_currPlayer]->getLastMove();
+    const Move &move = getCurrentPlayer().getLastMove();
     // Update the game
-    helperPlayMove(m_currPlayer, move);
+    Command *pCmd = new GameMoveCmd(*this, move,
+                                    getCurrentPlayer().getLastRack(),
+                                    m_currPlayer);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
 
     // Complete the rack for the player that just played
     if (move.getType() == Move::VALID_ROUND ||
@@ -155,7 +156,9 @@
         {
             const PlayedRack &newRack =
                 helperSetRackRandom(getCurrentPlayer().getCurrentRack(), 
false, RACK_NEW);
-            m_players[m_currPlayer]->setCurrentRack(newRack);
+            Command *pCmd = new PlayerRackCmd(*m_players[m_currPlayer],
+                                              newRack);
+            m_turnCommands[m_currTurn]->addAndExecute(pCmd);
         }
         catch (EndGameException &e)
         {
@@ -168,8 +171,10 @@
     // Next player
     nextPlayer();
 
+    newTurn();
+
     // If this player is an AI, make it play now
-    if (!m_players[m_currPlayer]->isHuman())
+    if (!getCurrentPlayer().isHuman())
     {
         playAI(m_currPlayer);
     }
@@ -216,10 +221,9 @@
 }
 
 
-int FreeGame::checkPass(const wstring &iToChange, unsigned int p) const
+int FreeGame::checkPass(const Player &iPlayer,
+                        const wstring &iToChange) const
 {
-    ASSERT(p < getNPlayers(), "Wrong player number");
-
     // Check that the game is not finished
     if (m_finished)
         return 3;
@@ -243,8 +247,7 @@
     }
 
     // Check that the letters are all present in the player's rack
-    Player *player = m_players[p];
-    PlayedRack pld = player->getCurrentRack();
+    const PlayedRack &pld = iPlayer.getCurrentRack();
     Rack rack;
     pld.getRack(rack);
     BOOST_FOREACH(wchar_t wch, iToChange)
@@ -270,13 +273,14 @@
 
 int FreeGame::pass(const wstring &iToChange)
 {
-    int res = checkPass(iToChange, m_currPlayer);
+    Player &player = *m_players[m_currPlayer];
+    int res = checkPass(player, iToChange);
     if (res != 0)
         return res;
 
     Move move(iToChange);
     // End the player's turn
-    recordPlayerMove(move, m_currPlayer);
+    recordPlayerMove(move, player);
 
     // Next game turn
     endTurn();

Index: game/freegame.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/freegame.h,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -b -r1.13 -r1.14
--- game/freegame.h     22 Nov 2008 13:09:30 -0000      1.13
+++ game/freegame.h     23 Nov 2008 08:18:06 -0000      1.14
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 2005-2007 Olivier Teulière
+ * Copyright (C) 2005-2008 Olivier Teulière
  * Authors: Olivier Teulière <ipkiss @@ gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,8 @@
 #include "game.h"
 #include "tile.h"
 
+class Player;
+
 using std::string;
 using std::wstring;
 using std::vector;
@@ -87,7 +89,7 @@
     void playAI(unsigned int p);
 
     /// Record a player move
-    void recordPlayerMove(const Move &iMove, unsigned int p);
+    void recordPlayerMove(const Move &iMove, Player &ioPlayer);
 
     /// Finish the current turn
     int endTurn();
@@ -99,7 +101,7 @@
      * Check whether it is legal to change the letters of iToChange.
      * The return codes are the same as the ones on the pass() method
      */
-    int checkPass(const wstring &iToChange, unsigned int p) const;
+    int checkPass(const Player &iPlayer, const wstring &iToChange) const;
 };
 
 #endif /* _FREEGAME_H_ */

Index: game/game.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/game.cpp,v
retrieving revision 1.43
retrieving revision 1.44
diff -u -b -r1.43 -r1.44
--- game/game.cpp       22 Nov 2008 13:09:30 -0000      1.43
+++ game/game.cpp       23 Nov 2008 08:18:06 -0000      1.44
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 1999-2007 Antoine Fraboulet & Olivier Teulière
+ * Copyright (C) 1999-2008 Antoine Fraboulet & Olivier Teulière
  * Authors: Antoine Fraboulet <antoine.fraboulet @@ free.fr>
  *          Olivier Teulière <ipkiss @@ gmail.com>
  *
@@ -41,6 +41,7 @@
 #include "turn.h"
 #include "encoding.h"
 #include "game_exception.h"
+#include "turn_cmd.h"
 
 #include "debug.h"
 
@@ -54,6 +55,8 @@
     m_points = 0;
     m_currPlayer = 0;
     m_finished = false;
+    m_currTurn = -1;
+    newTurn();
 }
 
 
@@ -63,6 +66,10 @@
     {
         delete p;
     }
+    BOOST_FOREACH(Command *c, m_turnCommands)
+    {
+        delete c;
+    }
 }
 
 
@@ -73,166 +80,6 @@
 }
 
 
-Rack Game::helperComputeRackForMove(const Rack &iRack, const Move &iMove)
-{
-    // Start from the given rack
-    Rack newRack = iRack;
-
-    if (iMove.getType() == Move::VALID_ROUND)
-    {
-        // Remove the played tiles from the rack
-        const Round &round = iMove.getRound();
-        for (unsigned int i = 0; i < round.getWordLen(); i++)
-        {
-            if (round.isPlayedFromRack(i))
-            {
-                if (round.isJoker(i))
-                    newRack.remove(Tile::Joker());
-                else
-                    newRack.remove(round.getTile(i));
-            }
-        }
-    }
-    else if (iMove.getType() == Move::CHANGE_LETTERS)
-    {
-        // Remove the changed tiles from the rack
-        const wstring & changed = iMove.getChangedLetters();
-        BOOST_FOREACH(wchar_t ch, changed)
-        {
-            newRack.remove(Tile(ch));
-        }
-    }
-
-    return newRack;
-}
-
-
-void Game::helperPlayMove(unsigned int iPlayerId, const Move &iMove)
-{
-    // Get the original rack from the player history
-    const PlayedRack &oldPldRack = getPlayer(iPlayerId).getLastRack();
-    Rack oldRack;
-    oldPldRack.getRack(oldRack);
-    const Rack &newRack = helperComputeRackForMove(oldRack, iMove);
-
-    // History of the game
-    m_history.setCurrentRack(oldPldRack);
-    m_history.playMove(iPlayerId, m_history.getSize(), iMove, newRack);
-
-    // Points
-    m_points += iMove.getScore();
-
-    // For moves corresponding to a valid round, we have much more
-    // work to do...
-    if (iMove.getType() == Move::VALID_ROUND)
-    {
-        helperPlayRound(iPlayerId, iMove.getRound());
-    }
-#ifdef REAL_BAG_MODE
-    else if (iMove.getType() == Move::CHANGE_LETTERS)
-    {
-        // Put the changed letters back into the bag
-        BOOST_FOREACH(wchar_t ch, iMove.getChangedLetters())
-        {
-            m_bag.replaceTile(Tile(ch));
-        }
-    }
-#endif
-}
-
-
-
-void Game::helperPlayRound(unsigned int iPlayerId, const Round &iRound)
-{
-    // Copy the round, because we may need to modify it (case of
-    // the joker games).
-    Round round = iRound;
-
-    // Before updating the bag and the board, if we are playing a "joker game",
-    // we replace in the round the joker by the letter it represents
-    // This is currently done by a succession of ugly hacks :-/
-    if (m_variant == kJOKER)
-    {
-        for (unsigned int i = 0; i < round.getWordLen(); i++)
-        {
-            if (round.isPlayedFromRack(i) && round.isJoker(i))
-            {
-                // Is the represented letter still available in the bag?
-                // XXX: this way to get the represented letter sucks...
-                Tile t(towupper(round.getTile(i).toChar()));
-#ifdef REAL_BAG_MODE
-                Bag &bag = m_bag;
-#else
-                Bag bag(m_dic);
-                realBag(bag);
-                // FIXME: realBag() does not give us a real bag in this
-                // particular case! This is because Player::endTurn() is called
-                // before Game::helperPlayRound(), which means that the rack
-                // of the player is updated, while the word is not actually
-                // played on the board yet. Since realBag() relies on
-                // Player::getCurrentRack(), it doesn't remove the letters of
-                // the current player, which are in fact available through
-                // Player::getLastRack().
-                // That's why we have to replace the letters of the current
-                // rack and remove the ones from the previous rack...
-                // There is a big design problem here, but i am unsure what is
-                // the best way to fix it.
-                vector<Tile> tiles;
-                getPlayer(iPlayerId).getCurrentRack().getAllTiles(tiles);
-                BOOST_FOREACH(const Tile &tile, tiles)
-                {
-                    bag.replaceTile(tile);
-                }
-                getPlayer(iPlayerId).getLastRack().getAllTiles(tiles);
-                BOOST_FOREACH(const Tile &tile, tiles)
-                {
-                    bag.takeTile(tile);
-                }
-#endif
-
-                if (bag.in(t))
-                {
-                    round.setTile(i, t);
-                    // FIXME: This shouldn't be necessary, this is only
-                    // needed because of the stupid way of handling jokers in
-                    // rounds
-                    round.setJoker(i, false);
-                }
-
-                // In a joker game we should have only 1 joker in the rack
-                break;
-            }
-        }
-    }
-
-#ifdef REAL_BAG_MODE
-#else
-    // Update the bag
-    // We remove tiles from the bag only when they are played
-    // on the board. When going back in the game, we must only
-    // replace played tiles.
-    // We test a rack when it is set but tiles are left in the bag.
-    for (unsigned int i = 0; i < round.getWordLen(); i++)
-    {
-        if (round.isPlayedFromRack(i))
-        {
-            if (round.isJoker(i))
-            {
-                m_bag.takeTile(Tile::Joker());
-            }
-            else
-            {
-                m_bag.takeTile(round.getTile(i));
-            }
-        }
-    }
-#endif
-
-    // Update the board
-    m_board.addRound(m_dic, round);
-}
-
-
 int Game::back(unsigned int n)
 {
     if (m_history.getSize() < n)
@@ -771,3 +618,51 @@
     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;
+}
+

Index: game/game.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/game.h,v
retrieving revision 1.38
retrieving revision 1.39
diff -u -b -r1.38 -r1.39
--- game/game.h 22 Nov 2008 14:40:26 -0000      1.38
+++ game/game.h 23 Nov 2008 08:18:06 -0000      1.39
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 1999-2007 Antoine Fraboulet & Olivier Teulière
+ * Copyright (C) 1999-2008 Antoine Fraboulet & Olivier Teulière
  * Authors: Antoine Fraboulet <antoine.fraboulet @@ free.fr>
  *          Olivier Teulière <ipkiss @@ gmail.com>
  *
@@ -35,6 +35,7 @@
 class Round;
 class Rack;
 class Turn;
+class TurnCmd;
 
 using namespace std;
 
@@ -97,14 +98,28 @@
 
     /// Get the board
     const Board& getBoard() const { return m_board; }
+    Board & accessBoard() { return m_board; }
     /// Get the bag
 #ifdef REAL_BAG_MODE
     Bag getBag() const;
 #else
     const Bag& getBag() const { return m_bag; }
+    Bag & accessBag() { return m_bag; }
+    /**
+     * The realBag is the current bag minus all the racks
+     * present in the game. It represents the actual
+     * letters that are left in the bag.
+     * FIXME: in Duplicate mode, this method uses m_currPlayer to find the
+     * rack of the player. Since not all the players played the same word,
+     * it is important to set m_currPlayer accurately before!
+     */
+    void realBag(Bag &iBag) const;
 #endif
+
+
     /// Get the history of the game */
     const History& getHistory() const { return m_history; }
+    History & accessHistory() { return m_history; }
 
     /***************
      * Methods to access players.
@@ -205,16 +220,14 @@
 
     enum set_rack_mode {RACK_ALL, RACK_NEW, RACK_MANUAL};
 
+    void addPoints(int iPoints) { m_points += iPoints; }
 
-protected:
-    /// All the players, indexed by their ID
-    vector<Player*> m_players;
-    /// ID of the "current" player
-    unsigned int m_currPlayer;
+    void prevTurn();
+    void nextTurn();
+    void firstTurn();
+    void lastTurn();
 
-// TODO: check what should be private and what should be protected
 private:
-
     /// Variant
     GameVariant m_variant;
 
@@ -228,28 +241,30 @@
 
     int m_points;
 
+
+// TODO: check what should be private and what should be protected
 protected:
+    /// All the players, indexed by their ID
+    vector<Player*> m_players;
+    /// ID of the "current" player
+    unsigned int m_currPlayer;
+
     /// Board
     Board m_board;
 
     /// Bag
     Bag m_bag;
 
+    vector<TurnCmd *> m_turnCommands;
+    unsigned int m_currTurn;
+
     bool m_finished;
 
     /*********************************************************
      * Helper functions
      *********************************************************/
 
-    /**
-     * Return the rack obtained from the given one, after playing the
-     * given move.
-     * The move is supposed to be possible for the given rack.
-     */
-    static Rack helperComputeRackForMove(const Rack &iOldRack, const Move 
&iMove);
-
-    /** Play a Move for the given player, updating game history */
-    void helperPlayMove(unsigned int iPlayerId, const Move &iMove);
+    void newTurn();
 
     /**
      * Complete the given rack randomly.
@@ -299,19 +314,6 @@
      */
     bool rackInBag(const Rack &iRack, const Bag &iBag) const;
 
-#ifdef REAL_BAG_MODE
-#else
-    /**
-     * The realBag is the current bag minus all the racks
-     * present in the game. It represents the actual
-     * letters that are left in the bag.
-     * FIXME: in Duplicate mode, this method uses m_currPlayer to find the
-     * rack of the player. Since not all the players played the same word,
-     * it is important to set m_currPlayer accurately before!
-     */
-    void realBag(Bag &iBag) const;
-#endif
-
     /**
      * This function checks whether it is legal to play the given word at the
      * given coordinates. If so, the function fills a Round object, also given
@@ -343,14 +345,6 @@
      */
     void gameSaveFormat_15(ostream &out) const;
 
-private:
-
-    /**
-     * Play a round on the board.
-     * This should only be called by helperPlayMove().
-     */
-    void helperPlayRound(unsigned int iPlayerId, const Round &iRound);
-
 };
 
 #endif /* _GAME_H_ */

Index: game/game_io.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/game_io.cpp,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -b -r1.11 -r1.12
--- game/game_io.cpp    22 Nov 2008 13:09:30 -0000      1.11
+++ game/game_io.cpp    23 Nov 2008 08:18:06 -0000      1.12
@@ -35,6 +35,7 @@
 #include "duplicate.h"
 #include "encoding.h"
 #include "game_exception.h"
+#include "game_move_cmd.h"
 
 using namespace std;
 
@@ -389,7 +390,10 @@
 //                     pGame->m_players[player]->endTurn(round,num - 1);
 
         // Play the round
-        pGame->helperPlayRound(pGame->m_currPlayer, round);
+        GameMoveCmd cmd(*pGame, Move(round),
+                        pGame->getCurrentPlayer().getLastRack(),
+                        pGame->m_currPlayer);
+        cmd.execute();
     }
 
     /**************************************/

Index: game/history.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/history.cpp,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -b -r1.13 -r1.14
--- game/history.cpp    22 Nov 2008 13:09:30 -0000      1.13
+++ game/history.cpp    23 Nov 2008 08:18:08 -0000      1.14
@@ -93,20 +93,20 @@
 }
 
 
-void History::playMove(unsigned int iPlayer, unsigned int iTurn,
-                       const Move &iMove, const Rack &iNewRack)
+void History::playMove(unsigned int iPlayer,
+                       const Move &iMove,
+                       const Rack &iNewRack)
 {
     Turn * current_turn = m_history.back();
 
     // Set the number and the round
-    current_turn->setNum(iTurn);
     current_turn->setPlayer(iPlayer);
     current_turn->setMove(iMove);
 
     // Create a new turn
-    Turn * next_turn = new Turn();
     PlayedRack pldrack;
     pldrack.setOld(iNewRack);
+    Turn * next_turn = new Turn();
     next_turn->setPlayedRack(pldrack);
     m_history.push_back(next_turn);
 }
@@ -126,7 +126,6 @@
 
     // Now we have the previous played round in back()
     Turn *t = m_history.back();
-    t->setNum(0);
     t->setPlayer(0);
     //t->setRound(Round());
 #ifdef BACK_REMOVE_RACK_NEW_PART

Index: game/history.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/history.h,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -b -r1.13 -r1.14
--- game/history.h      22 Nov 2008 13:09:31 -0000      1.13
+++ game/history.h      23 Nov 2008 08:18:08 -0000      1.14
@@ -84,8 +84,8 @@
      * A new turn is created with the unplayed letters in the rack
      * 03 sept 2000: We have to sort the tiles according to the new rules
      */
-    void playMove(unsigned int player, unsigned int turn,
-                  const Move &iMove, const Rack &iNewRack);
+    void playMove(unsigned int player, const Move &iMove,
+                  const Rack &iNewRack);
 
     /// Remove last turn
     void removeLastTurn();

Index: game/move.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/move.cpp,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -b -r1.3 -r1.4
--- game/move.cpp       13 Sep 2008 21:32:46 -0000      1.3
+++ game/move.cpp       23 Nov 2008 08:18:08 -0000      1.4
@@ -18,11 +18,15 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *****************************************************************************/
 
+#include <boost/foreach.hpp>
+
 #include <algorithm>
 #include <wctype.h>
 #include <sstream>
 
 #include "move.h"
+#include "rack.h"
+#include "pldrack.h"
 
 
 Move::Move(const Round &iRound)
@@ -100,6 +104,41 @@
 }
 
 
+Rack Move::ComputeRackForMove(const PlayedRack &iOldRack, const Move &iMove)
+{
+    // Start from the given rack
+    Rack newRack;
+    iOldRack.getRack(newRack);
+
+    if (iMove.getType() == Move::VALID_ROUND)
+    {
+        // Remove the played tiles from the rack
+        const Round &round = iMove.getRound();
+        for (unsigned int i = 0; i < round.getWordLen(); i++)
+        {
+            if (round.isPlayedFromRack(i))
+            {
+                if (round.isJoker(i))
+                    newRack.remove(Tile::Joker());
+                else
+                    newRack.remove(round.getTile(i));
+            }
+        }
+    }
+    else if (iMove.getType() == Move::CHANGE_LETTERS)
+    {
+        // Remove the changed tiles from the rack
+        const wstring & changed = iMove.getChangedLetters();
+        BOOST_FOREACH(wchar_t ch, changed)
+        {
+            newRack.remove(Tile(ch));
+        }
+    }
+
+    return newRack;
+}
+
+
 wstring Move::toString() const
 {
     wstringstream wss;

Index: game/move.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/move.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -b -r1.2 -r1.3
--- game/move.h 8 Jan 2008 13:52:38 -0000       1.2
+++ game/move.h 23 Nov 2008 08:18:08 -0000      1.3
@@ -25,6 +25,8 @@
 
 #include "round.h"
 
+class Rack;
+class PlayedRack;
 using std::wstring;
 
 
@@ -107,6 +109,14 @@
          */
         const wstring & getChangedLetters() const;
 
+        /**
+         * Return the rack obtained from the given one, after playing the
+         * given move.
+         * The move is supposed to be possible for the given rack.
+         */
+        static Rack ComputeRackForMove(const PlayedRack &iOldRack,
+                                       const Move &iMove);
+
         /// To help debugging
         wstring toString() const;
 

Index: game/player.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/player.cpp,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -b -r1.17 -r1.18
--- game/player.cpp     22 Nov 2008 13:09:31 -0000      1.17
+++ game/player.cpp     23 Nov 2008 08:18:09 -0000      1.18
@@ -63,12 +63,6 @@
 }
 
 
-void Player::endTurn(const Move &iMove, unsigned int iTurn, const Rack 
&iNewRack)
-{
-    addPoints(iMove.getScore());
-    m_history.playMove(m_id, iTurn, iMove, iNewRack);
-}
-
 void Player::removeLastTurn()
 {
     // Remove points of the last turn
@@ -78,13 +72,14 @@
 
 wstring Player::toString() const
 {
-    wstring res;
+    wstring res = L"Player ";
 
     wchar_t buff[6];
-    _swprintf(buff, 5, L"Player %d\n", m_id);
-    res = wstring(buff);
+    _swprintf(buff, 5, L"%d\n", m_id);
+    res += wstring(buff);
     res += m_history.toString() + L"\n";
-    _swprintf(buff, 5, L"score %d\n", m_score);
+    res += L"score ";
+    _swprintf(buff, 5, L"%d\n", m_score);
     res += wstring(buff);
     return res;
 }

Index: game/player.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/player.h,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -b -r1.23 -r1.24
--- game/player.h       22 Nov 2008 13:09:31 -0000      1.23
+++ game/player.h       23 Nov 2008 08:18:09 -0000      1.24
@@ -51,7 +51,8 @@
     /// Set the name of the player
     void setName(const wstring &iName) { m_name = iName; }
 
-    /// Set the ID
+    /// ID handling
+    unsigned int getId() const { return m_id; }
     void setId(unsigned int iId) { m_id = iId; }
 
     /**************************
@@ -67,6 +68,7 @@
     void setCurrentRack(const PlayedRack &iPld);
 
     const History& getHistory() const { return m_history; }
+    History & accessHistory() { return m_history; }
 
     /// Remove last turn
     void removeLastTurn();
@@ -79,14 +81,6 @@
     void addPoints(int iPoints) { m_score += iPoints; }
     int  getPoints() const      { return m_score; }
 
-    /**
-     * Update the player "history", with the given move.
-     * A new rack is created with the remaining letters.
-     * The score of the player is updated with the one of the move, if it is
-     * meaningful.
-     */
-    void endTurn(const Move &iMove, unsigned int iTurn, const Rack &iNewRack);
-
     wstring toString() const;
 
 private:

Index: game/training.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/training.cpp,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -b -r1.24 -r1.25
--- game/training.cpp   22 Nov 2008 14:40:26 -0000      1.24
+++ game/training.cpp   23 Nov 2008 08:18:10 -0000      1.25
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 1999-2007 Antoine Fraboulet & Olivier Teulière
+ * Copyright (C) 1999-2008 Antoine Fraboulet & Olivier Teulière
  * Authors: Antoine Fraboulet <antoine.fraboulet @@ free.fr>
  *          Olivier Teulière <ipkiss @@ gmail.com>
  *
@@ -35,8 +35,12 @@
 #include "move.h"
 #include "pldrack.h"
 #include "player.h"
+#include "player_move_cmd.h"
+#include "player_rack_cmd.h"
+#include "game_move_cmd.h"
 #include "training.h"
 #include "encoding.h"
+#include "turn_cmd.h"
 
 #include "debug.h"
 
@@ -55,7 +59,8 @@
     m_results.clear();
     const PlayedRack &newRack =
         helperSetRackRandom(getCurrentPlayer().getCurrentRack(), iCheck, mode);
-    m_players[m_currPlayer]->setCurrentRack(newRack);
+    Command *pCmd = new PlayerRackCmd(*m_players[m_currPlayer], newRack);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
 }
 
 
@@ -110,11 +115,7 @@
     }
 
     Move move(round);
-    // Update the rack and the score of the current player
-    // Player::endTurn() must be called before Game::helperPlayMove()
-    // (called here in endTurn()).
-    // See the big comment in game.cpp, line 96
-    recordPlayerMove(move, m_currPlayer);
+    recordPlayerMove(move, *m_players[m_currPlayer]);
 
     // Next turn
     endTurn();
@@ -123,18 +124,14 @@
 }
 
 
-void Training::recordPlayerMove(const Move &iMove, unsigned int p)
+void Training::recordPlayerMove(const Move &iMove, Player &ioPlayer)
 {
-    ASSERT(p < getNPlayers(), "Wrong player number");
-
-    // Get what was the rack for the current turn
-    Rack oldRack;
-    m_players[p]->getCurrentRack().getRack(oldRack);
-    // Compute the new rack
-    const Rack &newRack = helperComputeRackForMove(oldRack, iMove);
-
-    // Record the invalid move of the player
-    m_players[p]->endTurn(iMove, getHistory().getSize(), newRack);
+    // Update the rack and the score of the current player
+    // PlayerMoveCmd::execute() must be called before Game::helperPlayMove()
+    // (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);
 }
 
 
@@ -154,7 +151,11 @@
 
     // Play the word on the board
     const Move &move = m_players[m_currPlayer]->getLastMove();
-    helperPlayMove(m_currPlayer, move);
+    Command *pCmd = new GameMoveCmd(*this, move,
+                                    getCurrentPlayer().getLastRack(),
+                                    m_currPlayer);
+    m_turnCommands[m_currTurn]->addAndExecute(pCmd);
+    newTurn();
 }
 
 
@@ -174,7 +175,7 @@
 
     Move move(m_results.get(n));
     // Update the rack and the score of the current player
-    recordPlayerMove(move, m_currPlayer);
+    recordPlayerMove(move, *m_players[m_currPlayer]);
 
     // Next turn
     endTurn();

Index: game/training.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/training.h,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -b -r1.18 -r1.19
--- game/training.h     22 Nov 2008 13:09:31 -0000      1.18
+++ game/training.h     23 Nov 2008 08:18:10 -0000      1.19
@@ -1,6 +1,6 @@
 /*****************************************************************************
  * Eliot
- * Copyright (C) 1999-2007 Antoine Fraboulet & Olivier Teulière
+ * Copyright (C) 1999-2008 Antoine Fraboulet & Olivier Teulière
  * Authors: Antoine Fraboulet <antoine.fraboulet @@ free.fr>
  *          Olivier Teulière <ipkiss @@ gmail.com>
  *
@@ -28,6 +28,8 @@
 #include "round.h"
 #include "results.h"
 
+class Player;
+
 using std::string;
 using std::wstring;
 
@@ -96,7 +98,7 @@
     Training(const Dictionary &iDic);
 
     /// Record a player move
-    void recordPlayerMove(const Move &iMove, unsigned int p);
+    void recordPlayerMove(const Move &iMove, Player &ioPlayer);
 
     void endTurn();
 

Index: game/turn.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/game/turn.cpp,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -b -r1.13 -r1.14
--- game/turn.cpp       22 Nov 2008 13:09:31 -0000      1.13
+++ game/turn.cpp       23 Nov 2008 08:18:10 -0000      1.14
@@ -25,14 +25,14 @@
 // FIXME: move set to an invalid value. It would be better to get rid of this
 // constructor completely
 Turn::Turn()
-    : m_num(0), m_playerId(0), m_move(L"", L"")
+    : m_playerId(0), m_move(L"", L"")
 {
 }
 
 
-Turn::Turn(unsigned int iNum, unsigned int iPlayerId,
-           const PlayedRack& iPldRack, const Move& iMove)
-    : m_num(iNum), m_playerId(iPlayerId), m_pldrack(iPldRack), m_move(iMove)
+Turn::Turn(unsigned int iPlayerId, const PlayedRack& iPldRack,
+           const Move& iMove)
+    : m_playerId(iPlayerId), m_pldrack(iPldRack), m_move(iMove)
 {
 }
 

Index: game/turn.h
===================================================================
RCS file: /cvsroot/eliot/eliot/game/turn.h,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -b -r1.11 -r1.12
--- game/turn.h 22 Nov 2008 13:09:31 -0000      1.11
+++ game/turn.h 23 Nov 2008 08:18:10 -0000      1.12
@@ -32,7 +32,6 @@
 /**
  * A Turn is the information about one 'move' done by a player.
  * It consists of the player who played, the rack, and the actual move.
- * A turn also has an id (XXX: currently never read)
  *
  * This class has no logic, it is merely there to aggregate corresponding
  * data.
@@ -41,15 +40,13 @@
 {
 public:
     Turn();
-    Turn(unsigned int iNum, unsigned int iPlayerId,
+    Turn(unsigned int iPlayerId,
          const PlayedRack& iPldRack, const Move& iMove);
 
-    void setNum(unsigned int iNum)                 { m_num = iNum; }
     void setPlayer(unsigned int iPlayerId)         { m_playerId = iPlayerId; }
     void setPlayedRack(const PlayedRack& iPldRack) { m_pldrack = iPldRack; }
     void setMove(const Move& iMove)             { m_move = iMove; }
 
-    unsigned int      getNum()        const { return m_num; }
     unsigned int      getPlayer()     const { return m_playerId; }
     const PlayedRack& getPlayedRack() const { return m_pldrack; }
     const Move&       getMove()       const { return m_move; }
@@ -57,7 +54,6 @@
     wstring toString(bool iShowExtraSigns = false) const;
 
 private:
-    unsigned int m_num;
     unsigned int m_playerId;
     PlayedRack   m_pldrack;
     Move         m_move;

Index: utils/eliottxt.cpp
===================================================================
RCS file: /cvsroot/eliot/eliot/utils/eliottxt.cpp,v
retrieving revision 1.25
retrieving revision 1.26
diff -u -b -r1.25 -r1.26
--- utils/eliottxt.cpp  13 Sep 2008 21:32:47 -0000      1.25
+++ utils/eliottxt.cpp  23 Nov 2008 08:18:11 -0000      1.26
@@ -233,6 +233,7 @@
     printf("  n [] : jouer le résultat numéro []\n");
     printf("  r    : rechercher les meilleurs résultats\n");
     printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  h [p|n|f|l] : naviguer dans l'historique (prev, next, first, 
last\n");
     printf("  q    : quitter le mode entraînement\n");
 }
 
@@ -257,6 +258,7 @@
     printf("  j [] {} : jouer le mot [] aux coordonnées {}\n");
     printf("  p [] : passer son tour en changeant les lettres []\n");
     printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  h [p|n|f|l] : naviguer dans l'historique (prev, next, first, 
last\n");
     printf("  q    : quitter le mode partie libre\n");
 }
 
@@ -280,6 +282,7 @@
     printf("  j [] {} : jouer le mot [] aux coordonnées {}\n");
     printf("  n [] : passer au joueur n°[]\n");
     printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  h [p|n|f|l] : naviguer dans l'historique (prev, next, first, 
last\n");
     printf("  q    : quitter le mode duplicate\n");
 }
 
@@ -566,6 +569,27 @@
                             fout.close();
                         }
                         break;
+                    case L'h':
+                        token = next_token_alpha(NULL, delim, &state);
+                        if (token != NULL)
+                        {
+                            switch (token[0])
+                            {
+                                case L'p':
+                                    iGame.prevTurn();
+                                    break;
+                                case L'n':
+                                    iGame.nextTurn();
+                                    break;
+                                case L'f':
+                                    iGame.firstTurn();
+                                    break;
+                                case L'l':
+                                    iGame.lastTurn();
+                                    break;
+                            }
+                        }
+                        break;
                     case L'q':
                         quit = 1;
                         break;
@@ -674,6 +698,27 @@
                             fout.close();
                         }
                         break;
+                    case L'h':
+                        token = next_token_alpha(NULL, delim, &state);
+                        if (token != NULL)
+                        {
+                            switch (token[0])
+                            {
+                                case L'p':
+                                    iGame.prevTurn();
+                                    break;
+                                case L'n':
+                                    iGame.nextTurn();
+                                    break;
+                                case L'f':
+                                    iGame.firstTurn();
+                                    break;
+                                case L'l':
+                                    iGame.lastTurn();
+                                    break;
+                            }
+                        }
+                        break;
                     case L'q':
                         quit = 1;
                         break;
@@ -790,6 +835,27 @@
                             fout.close();
                         }
                         break;
+                    case L'h':
+                        token = next_token_alpha(NULL, delim, &state);
+                        if (token != NULL)
+                        {
+                            switch (token[0])
+                            {
+                                case L'p':
+                                    iGame.prevTurn();
+                                    break;
+                                case L'n':
+                                    iGame.nextTurn();
+                                    break;
+                                case L'f':
+                                    iGame.firstTurn();
+                                    break;
+                                case L'l':
+                                    iGame.lastTurn();
+                                    break;
+                            }
+                        }
+                        break;
                     case L'q':
                         quit = 1;
                         break;

Index: game/command.cpp
===================================================================
RCS file: game/command.cpp
diff -N game/command.cpp
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/command.cpp    23 Nov 2008 08:18:04 -0000      1.1
@@ -0,0 +1,45 @@
+/*****************************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 "command.h"
+#include "debug.h"
+
+
+Command::Command()
+    : m_executed(false)
+{
+}
+
+
+void Command::execute()
+{
+    ASSERT(!m_executed, "Command already executed!");
+    doExecute();
+    m_executed = true;
+}
+
+
+void Command::undo()
+{
+    ASSERT(m_executed, "Command already undone!");
+    doUndo();
+    m_executed = false;
+}
+

Index: game/command.h
===================================================================
RCS file: game/command.h
diff -N game/command.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/command.h      23 Nov 2008 08:18:05 -0000      1.1
@@ -0,0 +1,66 @@
+/*****************************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 _COMMAND_H
+#define _COMMAND_H
+
+
+/**
+ * This abstract class is the parent of all classes implementing the Command
+ * design pattern.
+ */
+class Command
+{
+    public:
+        Command();
+        virtual ~Command() {}
+
+        /**
+         * Execute the command. This can later be undone using the undo()
+         * method.
+         *
+         * You are not allowed to call this method twice before calling
+         * undo() first.
+         */
+        void execute();
+
+        /**
+         * Undo the previous call to execute().
+         *
+         * You are not allowed to undo a command which is not executed yet.
+         */
+        void undo();
+
+        /**
+         * Return true if the command has been executed (i.e. if it is
+         * allowed to call undo()), false otherwise.
+         */
+        bool isExecuted() const { return m_executed; }
+
+    protected:
+        virtual void doExecute() = 0;
+        virtual void doUndo() = 0;
+
+    private:
+        bool m_executed;
+};
+
+#endif
+

Index: game/game_move_cmd.cpp
===================================================================
RCS file: game/game_move_cmd.cpp
diff -N game/game_move_cmd.cpp
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/game_move_cmd.cpp      23 Nov 2008 08:18:07 -0000      1.1
@@ -0,0 +1,182 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 "game_move_cmd.h"
+#include "player.h"
+#include "game.h"
+#include "rack.h"
+
+
+GameMoveCmd::GameMoveCmd(Game &ioGame, const Move &iMove,
+                         const PlayedRack &iMoveRack, unsigned int iPlayerId)
+    : m_game(ioGame), m_move(iMove), m_moveRack(iMoveRack),
+    m_playerId(iPlayerId)
+{
+}
+
+
+void GameMoveCmd::doExecute()
+{
+    // Get the original rack from the player history
+    const Rack &newRack = Move::ComputeRackForMove(m_moveRack, m_move);
+
+    // History of the game
+    History &history = m_game.accessHistory();
+    history.setCurrentRack(m_moveRack);
+    history.playMove(m_playerId, m_move, newRack);
+
+    // Points
+    m_game.addPoints(m_move.getScore());
+
+    // For moves corresponding to a valid round, we have much more
+    // work to do...
+    if (m_move.getType() == Move::VALID_ROUND)
+    {
+        playRound();
+    }
+#ifdef REAL_BAG_MODE
+    else if (m_move.getType() == Move::CHANGE_LETTERS)
+    {
+        // Put the changed letters back into the bag
+        BOOST_FOREACH(wchar_t ch, m_move.getChangedLetters())
+        {
+            m_bag.replaceTile(Tile(ch));
+        }
+    }
+#endif
+}
+
+
+void GameMoveCmd::doUndo()
+{
+    // Undo playing the round on the board
+    if (m_move.getType() == Move::VALID_ROUND)
+    {
+        unplayRound();
+    }
+
+    // Points
+    m_game.addPoints(- m_move.getScore());
+
+    // History
+    m_game.accessHistory().removeLastTurn();
+}
+
+
+void GameMoveCmd::playRound()
+{
+    // Copy the round, because we may need to modify it (case of
+    // the joker games). It will also be convenient for the unplayRound()
+    // method.
+    m_round = m_move.getRound();
+
+#ifdef REAL_BAG_MODE
+#else
+    // Update the bag
+    // We remove tiles from the bag only when they are played
+    // on the board. When going back in the game, we must only
+    // replace played tiles.
+    // We test a rack when it is set but tiles are left in the bag.
+    Bag & bag = m_game.accessBag();
+    for (unsigned int i = 0; i < m_round.getWordLen(); i++)
+    {
+        if (m_round.isPlayedFromRack(i))
+        {
+            if (m_round.isJoker(i))
+            {
+                bag.takeTile(Tile::Joker());
+            }
+            else
+            {
+                bag.takeTile(m_round.getTile(i));
+            }
+        }
+    }
+#endif
+
+    if (m_game.getVariant() == Game::kJOKER)
+    {
+        for (unsigned int i = 0; i < m_round.getWordLen(); i++)
+        {
+            if (m_round.isPlayedFromRack(i) && m_round.isJoker(i))
+            {
+                // Get the real bag
+#ifdef REAL_BAG_MODE
+                Bag &bag = m_game.accessBag();
+#else
+                Bag bag(m_game.getDic());
+                m_game.realBag(bag);
+#endif
+
+                // Is the represented letter still available in the bag?
+                // XXX: this way to get the represented letter sucks...
+                Tile t(towupper(m_round.getTile(i).toChar()));
+                if (bag.in(t))
+                {
+                    bag.replaceTile(Tile::Joker());
+                    bag.takeTile(t);
+                    m_round.setTile(i, t);
+                    // FIXME: This shouldn't be necessary, this is only
+                    // needed because of the stupid way of handling jokers in
+                    // rounds
+                    m_round.setJoker(i, false);
+                }
+
+                // In a joker game we should have only 1 joker in the rack
+                break;
+            }
+        }
+    }
+
+    // Update the board
+    m_game.accessBoard().addRound(m_game.getDic(), m_round);
+}
+
+
+void GameMoveCmd::unplayRound()
+{
+    // Update the board
+    m_game.accessBoard().removeRound(m_game.getDic(), m_round);
+
+#ifdef REAL_BAG_MODE
+#else
+    // Update the bag
+    // We remove tiles from the bag only when they are played
+    // on the board. When going back in the game, we must only
+    // replace played tiles.
+    // We test a rack when it is set but tiles are left in the bag.
+    Bag & bag = m_game.accessBag();
+    for (unsigned int i = 0; i < m_round.getWordLen(); i++)
+    {
+        if (m_round.isPlayedFromRack(i))
+        {
+            if (m_round.isJoker(i))
+            {
+                bag.replaceTile(Tile::Joker());
+            }
+            else
+            {
+                bag.replaceTile(m_round.getTile(i));
+            }
+        }
+    }
+#endif
+}
+

Index: game/game_move_cmd.h
===================================================================
RCS file: game/game_move_cmd.h
diff -N game/game_move_cmd.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/game_move_cmd.h        23 Nov 2008 08:18:07 -0000      1.1
@@ -0,0 +1,66 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 _GAME_MOVE_CMD_H
+#define _GAME_MOVE_CMD_H
+
+#include "command.h"
+#include "move.h"
+#include "pldrack.h"
+#include "round.h"
+
+class Game;
+
+
+/**
+ * This class implements the Command design pattern.
+ * It encapsulates the logic to update the game state when a move is
+ * played:
+ *  - game score update
+ *  - game history update
+ *  - bag update
+ *  - board update
+ *
+ * The Move validation must have been done before calling execute().
+ */
+class GameMoveCmd: public Command
+{
+    public:
+        GameMoveCmd(Game &ioGame, const Move &iMove,
+                    const PlayedRack &iMoveRack,
+                    unsigned int iPlayerId);
+
+    protected:
+        virtual void doExecute();
+        virtual void doUndo();
+
+    private:
+        Game &m_game;
+        Move m_move;
+        Round m_round;
+        PlayedRack m_moveRack;
+        unsigned int m_playerId;
+
+        void playRound();
+        void unplayRound();
+};
+
+#endif
+

Index: game/player_move_cmd.cpp
===================================================================
RCS file: game/player_move_cmd.cpp
diff -N game/player_move_cmd.cpp
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/player_move_cmd.cpp    23 Nov 2008 08:18:09 -0000      1.1
@@ -0,0 +1,55 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 "player_move_cmd.h"
+#include "player.h"
+#include "rack.h"
+
+
+PlayerMoveCmd::PlayerMoveCmd(Player &ioPlayer, const Move &iMove)
+    : m_player(ioPlayer), m_move(iMove)
+{
+}
+
+
+void PlayerMoveCmd::doExecute()
+{
+    // Get what was the rack for the current turn
+    m_originalRack = m_player.getCurrentRack();
+
+    // Compute the new rack
+    const Rack &newRack = Move::ComputeRackForMove(m_originalRack, m_move);
+
+    // Update the score of the player
+    m_player.addPoints(m_move.getScore());
+    // Update the history and rack of the player
+    m_player.accessHistory().playMove(m_player.getId(), m_move, newRack);
+}
+
+
+void PlayerMoveCmd::doUndo()
+{
+    // Remove the last history item
+    m_player.accessHistory().removeLastTurn();
+    // Restore the score
+    m_player.addPoints(- m_move.getScore());
+    // TODO: restore rack?
+}
+

Index: game/player_move_cmd.h
===================================================================
RCS file: game/player_move_cmd.h
diff -N game/player_move_cmd.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/player_move_cmd.h      23 Nov 2008 08:18:09 -0000      1.1
@@ -0,0 +1,58 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 _PLAYER_MOVE_CMD_H
+#define _PLAYER_MOVE_CMD_H
+
+#include "command.h"
+#include "move.h"
+#include "pldrack.h"
+
+class Player;
+class Rack;
+
+
+/**
+ * This class implements the Command design pattern.
+ * It encapsulates the logic to update the player state when a player
+ * plays a move:
+ *  - score update
+ *  - rack update
+ *  - player history update
+ *
+ * The Move validation must have been done before calling execute().
+ */
+class PlayerMoveCmd: public Command
+{
+    public:
+        PlayerMoveCmd(Player &ioPlayer, const Move &iMove);
+
+    protected:
+        virtual void doExecute();
+        virtual void doUndo();
+
+    private:
+        Player &m_player;
+        Move m_move;
+        PlayedRack m_originalRack;
+};
+
+#endif
+

Index: game/player_rack_cmd.cpp
===================================================================
RCS file: game/player_rack_cmd.cpp
diff -N game/player_rack_cmd.cpp
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/player_rack_cmd.cpp    23 Nov 2008 08:18:09 -0000      1.1
@@ -0,0 +1,45 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 "player_rack_cmd.h"
+#include "player.h"
+
+
+PlayerRackCmd::PlayerRackCmd(Player &ioPlayer, const PlayedRack &iNewRack)
+    : m_player(ioPlayer), m_newRack(iNewRack)
+{
+}
+
+
+void PlayerRackCmd::doExecute()
+{
+    // Get what was the rack for the current turn
+    m_oldRack = m_player.getCurrentRack();
+    // Update the rack of the player
+    m_player.setCurrentRack(m_newRack);
+}
+
+
+void PlayerRackCmd::doUndo()
+{
+    // Restore the rack of the player
+    m_player.setCurrentRack(m_oldRack);
+}
+

Index: game/player_rack_cmd.h
===================================================================
RCS file: game/player_rack_cmd.h
diff -N game/player_rack_cmd.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/player_rack_cmd.h      23 Nov 2008 08:18:09 -0000      1.1
@@ -0,0 +1,50 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 _PLAYER_RACK_CMD_H
+#define _PLAYER_RACK_CMD_H
+
+#include "command.h"
+#include "pldrack.h"
+
+class Player;
+
+
+/**
+ * This class implements the Command design pattern.
+ * It encapsulates the logic to update the player rack, usually to complete it.
+ */
+class PlayerRackCmd: public Command
+{
+    public:
+        PlayerRackCmd(Player &ioPlayer, const PlayedRack &iNewRack);
+
+    protected:
+        virtual void doExecute();
+        virtual void doUndo();
+
+    private:
+        Player &m_player;
+        PlayedRack m_oldRack;
+        PlayedRack m_newRack;
+};
+
+#endif
+

Index: game/turn_cmd.cpp
===================================================================
RCS file: game/turn_cmd.cpp
diff -N game/turn_cmd.cpp
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/turn_cmd.cpp   23 Nov 2008 08:18:10 -0000      1.1
@@ -0,0 +1,62 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 <boost/foreach.hpp>
+
+#include "turn_cmd.h"
+#include "player.h"
+
+
+TurnCmd::~TurnCmd()
+{
+    BOOST_FOREACH(Command *cmd, m_commands)
+    {
+        delete cmd;
+    }
+}
+
+
+void TurnCmd::addAndExecute(Command *iCmd)
+{
+    m_commands.push_back(iCmd);
+    iCmd->execute();
+}
+
+
+void TurnCmd::doExecute()
+{
+    BOOST_FOREACH(Command *cmd, m_commands)
+    {
+        if (!cmd->isExecuted())
+            cmd->execute();
+    }
+}
+
+
+void TurnCmd::doUndo()
+{
+    // Undo commands in the reverse order of execution
+    vector<Command*>::reverse_iterator it;
+    for (it = m_commands.rbegin(); it != m_commands.rend(); ++it)
+    {
+        (*it)->undo();
+    }
+}
+

Index: game/turn_cmd.h
===================================================================
RCS file: game/turn_cmd.h
diff -N game/turn_cmd.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ game/turn_cmd.h     23 Nov 2008 08:18:11 -0000      1.1
@@ -0,0 +1,55 @@
+/*******************************************************************
+ * Eliot
+ * Copyright (C) 2008 Olivier Teulière
+ * Authors: Olivier Teulière <ipkiss @@ gmail.com>
+ *
+ * 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 _TURN_CMD_H
+#define _TURN_CMD_H
+
+#include <vector>
+
+#include "command.h"
+
+using namespace std;
+
+
+/**
+ * This class implements both the Command and Composite design patterns.
+ * It encapsulates commands, while still behaving like one.
+ */
+class TurnCmd: public Command
+{
+    public:
+        virtual ~TurnCmd();
+
+        /**
+         * Add the given command and execute it.
+         * The TurnCmd object takes ownership of the given Command.
+         */
+        void addAndExecute(Command *iCmd);
+
+    protected:
+        virtual void doExecute();
+        virtual void doUndo();
+
+    private:
+        vector<Command *> m_commands;
+};
+
+#endif
+




reply via email to

[Prev in Thread] Current Thread [Next in Thread]