- 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)
This commit is contained in:
Olivier Teulière 2008-11-23 08:18:03 +00:00
parent 4b766d2622
commit bce4ce4604
29 changed files with 1037 additions and 348 deletions

View file

@ -21,30 +21,35 @@ noinst_LIBRARIES = libgame.a
AM_CPPFLAGS = -I$(top_srcdir)/dic -I../intl -I$(top_srcdir)/intl @LIBCONFIG_CFLAGS@
libgame_a_SOURCES= \
libgame_a_SOURCES= \
game_exception.cpp game_exception.h \
rack.cpp rack.h \
pldrack.cpp pldrack.h \
round.cpp round.h \
move.cpp move.h \
results.cpp results.h \
bag.cpp bag.h \
player.cpp player.h \
ai_player.h \
ai_percent.cpp ai_percent.h \
coord.cpp coord.h \
cross.cpp cross.h \
board.cpp board.h \
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 \
duplicate.cpp duplicate.h \
freegame.cpp freegame.h \
training.cpp training.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 \
cross.cpp cross.h \
board.cpp board.h \
board_cross.cpp \
board_search.cpp \
settings.cpp settings.h \
game.cpp game.h \
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

45
game/command.cpp Normal file
View file

@ -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;
}

66
game/command.h Normal file
View file

@ -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

View file

@ -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 @@ int Duplicate::play(const wstring &iCoord, const wstring &iWord)
// 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 @@ void Duplicate::playAI(unsigned int p)
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 @@ void Duplicate::playAI(unsigned int p)
ASSERT(false, "AI tried to cheat!");
}
recordPlayerMove(move, p);
recordPlayerMove(move, *player);
}
@ -102,7 +107,14 @@ int Duplicate::start()
{
const PlayedRack &newRack =
helperSetRackRandom(getCurrentPlayer().getCurrentRack(), true, RACK_NEW);
m_players[m_currPlayer]->setCurrentRack(newRack);
// All the players have the same rack
for (unsigned int i = 0; i < getNPlayers(); i++)
{
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)
{
@ -110,18 +122,6 @@ int Duplicate::start()
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);
}
// Nobody has played yet in this round
m_hasPlayed[i] = false;
}
// Little hack to handle duplicate games with only AI players.
// This will have no effect when there is at least one human player
tryEndTurn();
@ -158,20 +158,12 @@ void Duplicate::tryEndTurn()
}
void Duplicate::recordPlayerMove(const Move &iMove, unsigned int p)
void Duplicate::recordPlayerMove(const Move &iMove, Player &ioPlayer)
{
ASSERT(p < getNPlayers(), "Wrong player number");
Command *pCmd = new PlayerMoveCmd(ioPlayer, iMove);
m_turnCommands[m_currTurn]->addAndExecute(pCmd);
// 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);
// 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 @@ void Duplicate::endTurn()
}
// 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 @@ void Duplicate::endTurn()
{
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();
}

View file

@ -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 @@ private:
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);

View file

@ -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 @@ int FreeGame::play(const wstring &iCoord, const wstring &iWord)
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 @@ void FreeGame::playAI(unsigned int p)
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 @@ int FreeGame::start()
{
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::start()
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 @@ int FreeGame::endTurn()
{
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 @@ int FreeGame::endTurn()
// 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 @@ void FreeGame::endGame()
}
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 @@ int FreeGame::checkPass(const wstring &iToChange, unsigned int p) const
}
// 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::checkPass(const wstring &iToChange, unsigned int p) const
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();

View file

@ -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 @@ private:
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 @@ private:
* 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_ */

View file

@ -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 @@ Game::Game(const Dictionary &iDic):
m_points = 0;
m_currPlayer = 0;
m_finished = false;
m_currTurn = -1;
newTurn();
}
@ -63,6 +66,10 @@ Game::~Game()
{
delete p;
}
BOOST_FOREACH(Command *c, m_turnCommands)
{
delete c;
}
}
@ -73,166 +80,6 @@ const Player& Game::getPlayer(unsigned int iNum) const
}
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 @@ 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;
}

View file

@ -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 PlayedRack;
class Round;
class Rack;
class Turn;
class TurnCmd;
using namespace std;
@ -97,14 +98,28 @@ public:
/// 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 @@ public:
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 @@ private:
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,27 +314,14 @@ protected:
*/
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
* as a parameter.
* Possible return values: same as the play() method
*/
int checkPlayedWord(const wstring &iCoord,
const wstring &iWord, Round &oRound) const;
int checkPlayedWord(const wstring &iCoord,
const wstring &iWord, Round &oRound) const;
/**
* load games from File using the first format.
@ -343,14 +345,6 @@ protected:
*/
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_ */

View file

@ -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 @@ Game* Game::gameLoadFormat_15(FILE *fin, const Dictionary& iDic)
// 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();
}
/**************************************/

182
game/game_move_cmd.cpp Normal file
View file

@ -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
}

66
game/game_move_cmd.h Normal file
View file

@ -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

View file

@ -93,20 +93,20 @@ bool History::beforeFirstRound() const
}
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 @@ void History::removeLastTurn()
// 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

View file

@ -84,8 +84,8 @@ class History
* 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();

View file

@ -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 @@ const wstring & Move::getChangedLetters() const
}
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;

View file

@ -25,6 +25,8 @@
#include "round.h"
class Rack;
class PlayedRack;
using std::wstring;
@ -107,6 +109,14 @@ class Move
*/
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;

View file

@ -63,12 +63,6 @@ const Move & Player::getLastMove() const
}
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 @@ void Player::removeLastTurn()
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;
}

View file

@ -51,7 +51,8 @@ public:
/// 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 @@ public:
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 @@ public:
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:

55
game/player_move_cmd.cpp Normal file
View file

@ -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?
}

58
game/player_move_cmd.h Normal file
View file

@ -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

45
game/player_rack_cmd.cpp Normal file
View file

@ -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);
}

50
game/player_rack_cmd.h Normal file
View file

@ -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

View file

@ -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 @@ void Training::setRackRandom(bool iCheck, set_rack_mode mode)
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 @@ int Training::play(const wstring &iCoord, const wstring &iWord)
}
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 @@ int Training::play(const wstring &iCoord, const wstring &iWord)
}
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 @@ void Training::endTurn()
// 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 @@ int Training::playResult(unsigned int n)
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();

View file

@ -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 @@ private:
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();

View file

@ -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)
{
}

View file

@ -32,7 +32,6 @@ using std::wstring;
/**
* 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 @@ class Turn
{
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 @@ public:
wstring toString(bool iShowExtraSigns = false) const;
private:
unsigned int m_num;
unsigned int m_playerId;
PlayedRack m_pldrack;
Move m_move;

62
game/turn_cmd.cpp Normal file
View file

@ -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();
}
}

55
game/turn_cmd.h Normal file
View file

@ -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

View file

@ -233,6 +233,7 @@ void help_training()
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 @@ void help_freegame()
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 @@ void help_duplicate()
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 @@ void loop_training(Training &iGame)
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 @@ void loop_freegame(FreeGame &iGame)
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 @@ void loop_duplicate(Duplicate &iGame)
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;