2005-02-05 12:14:56 +01:00
|
|
|
/*****************************************************************************
|
2008-01-08 14:52:32 +01:00
|
|
|
* Eliot
|
2012-10-07 16:25:41 +02:00
|
|
|
* Copyright (C) 1999-2012 Antoine Fraboulet & Olivier Teulière
|
2008-01-08 14:52:32 +01:00
|
|
|
* Authors: Antoine Fraboulet <antoine.fraboulet @@ free.fr>
|
|
|
|
* Olivier Teulière <ipkiss @@ gmail.com>
|
2005-02-05 12:14:56 +01:00
|
|
|
*
|
|
|
|
* 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
|
2005-10-23 16:53:42 +02:00
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
2005-02-05 12:14:56 +01:00
|
|
|
*****************************************************************************/
|
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
#include <boost/foreach.hpp>
|
2008-11-23 18:07:42 +01:00
|
|
|
#include <sstream>
|
2008-11-22 14:09:28 +01:00
|
|
|
|
2009-01-24 18:11:07 +01:00
|
|
|
#include "config.h"
|
2008-11-22 14:09:28 +01:00
|
|
|
#if ENABLE_NLS
|
|
|
|
# include <libintl.h>
|
|
|
|
# define _(String) gettext(String)
|
|
|
|
#else
|
|
|
|
# define _(String) String
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
#include "dic.h"
|
|
|
|
#include "tile.h"
|
|
|
|
#include "rack.h"
|
|
|
|
#include "round.h"
|
|
|
|
#include "pldrack.h"
|
|
|
|
#include "results.h"
|
|
|
|
#include "player.h"
|
|
|
|
#include "game.h"
|
2012-10-06 01:50:58 +02:00
|
|
|
#include "turn_data.h"
|
2006-01-22 13:23:52 +01:00
|
|
|
#include "encoding.h"
|
2008-09-13 23:32:45 +02:00
|
|
|
#include "game_exception.h"
|
2012-10-06 11:14:01 +02:00
|
|
|
#include "turn.h"
|
2012-12-25 18:25:22 +01:00
|
|
|
#include "cmd/player_rack_cmd.h"
|
|
|
|
#include "cmd/player_move_cmd.h"
|
|
|
|
#include "cmd/game_rack_cmd.h"
|
2005-02-05 12:14:56 +01:00
|
|
|
|
|
|
|
#include "debug.h"
|
|
|
|
|
2011-01-30 00:47:20 +01:00
|
|
|
INIT_LOGGER(game, Game);
|
|
|
|
|
2005-02-26 23:57:34 +01:00
|
|
|
|
2012-12-30 16:14:13 +01:00
|
|
|
Game::Game(const GameParams &iParams, const Game *iMasterGame):
|
|
|
|
m_params(iParams), m_masterGame(iMasterGame),
|
|
|
|
m_board(m_params), m_bag(iParams.getDic())
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
m_points = 0;
|
2008-01-08 14:52:32 +01:00
|
|
|
m_currPlayer = 0;
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Game::~Game()
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(Player *p, m_players)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
delete p;
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
2012-12-30 16:14:13 +01:00
|
|
|
delete m_masterGame;
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-19 12:39:13 +01:00
|
|
|
Player& Game::accessPlayer(unsigned int iNum)
|
|
|
|
{
|
|
|
|
ASSERT(iNum < m_players.size(), "Wrong player number");
|
|
|
|
return *(m_players[iNum]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
const Player& Game::getPlayer(unsigned int iNum) const
|
2005-11-05 16:48:59 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
ASSERT(iNum < m_players.size(), "Wrong player number");
|
2005-11-06 02:05:06 +01:00
|
|
|
return *(m_players[iNum]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-09-14 19:56:18 +02:00
|
|
|
void Game::shuffleRack()
|
|
|
|
{
|
2011-01-30 00:47:20 +01:00
|
|
|
LOG_DEBUG("Shuffling rack for player " << currPlayer());
|
2008-09-14 19:56:18 +02:00
|
|
|
PlayedRack pld = getCurrentPlayer().getCurrentRack();
|
|
|
|
pld.shuffle();
|
|
|
|
m_players[currPlayer()]->setCurrentRack(pld);
|
|
|
|
}
|
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
|
2013-01-09 18:42:49 +01:00
|
|
|
void Game::reorderRack(const PlayedRack &iNewRack)
|
|
|
|
{
|
|
|
|
LOG_DEBUG("Reordering rack for player " << currPlayer() <<
|
|
|
|
" (newRack=" << lfw(iNewRack.toString()) << ")");
|
|
|
|
const PlayedRack &pld = getCurrentPlayer().getCurrentRack();
|
|
|
|
|
|
|
|
// Make sure the new rack uses the same letters
|
|
|
|
ASSERT(pld.getRack() == iNewRack.getRack(),
|
|
|
|
"The old and new racks have different letters" <<
|
|
|
|
"(old=" << lfw(pld.toString()) <<
|
|
|
|
" new=" << lfw(iNewRack.toString()) << ")");
|
|
|
|
|
|
|
|
m_players[currPlayer()]->setCurrentRack(iNewRack);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
void Game::realBag(Bag &ioBag) const
|
|
|
|
{
|
2008-09-13 23:32:45 +02:00
|
|
|
// Copy the bag
|
2005-02-05 12:14:56 +01:00
|
|
|
ioBag = m_bag;
|
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
vector<Tile> tiles;
|
|
|
|
|
2008-09-13 23:32:45 +02:00
|
|
|
// The real content of the bag depends on the game mode
|
2011-08-28 18:20:00 +02:00
|
|
|
if (getMode() == GameParams::kFREEGAME)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-09-13 23:32:45 +02:00
|
|
|
// In freegame mode, take the letters from all the racks
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Player *player, m_players)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
player->getCurrentRack().getAllTiles(tiles);
|
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
ioBag.takeTile(tile);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
// In training or duplicate mode, take the rack of the current
|
|
|
|
// player only
|
2005-11-06 02:05:06 +01:00
|
|
|
getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
ioBag.takeTile(tile);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-05 01:19:32 +02:00
|
|
|
bool Game::canDrawRack(const PlayedRack &iPld, bool iCheck, int *reason) const
|
|
|
|
{
|
|
|
|
// When iCheck is true, we must make sure that there are at least 2 vowels
|
|
|
|
// and 2 consonants in the rack up to the 15th turn, and at least one of
|
|
|
|
// each starting from the 16th turn.
|
|
|
|
// So before trying to fill the rack, we'd better make sure there is a way
|
|
|
|
// to complete the rack with these constraints...
|
|
|
|
unsigned int min = 0;
|
|
|
|
if (iCheck)
|
|
|
|
{
|
|
|
|
// 2 vowels and 2 consonants are needed up to the 15th turn
|
|
|
|
if (m_history.getSize() < 15)
|
|
|
|
min = 2;
|
|
|
|
else
|
|
|
|
min = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a copy of the bag in which we can do everything we want,
|
|
|
|
// and take from it the tiles of the players rack so that "bag"
|
|
|
|
// contains the right number of tiles.
|
|
|
|
Bag bag(getDic());
|
|
|
|
realBag(bag);
|
|
|
|
// Replace all the tiles of the given rack into the bag
|
|
|
|
vector<Tile> tiles;
|
|
|
|
iPld.getAllTiles(tiles);
|
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
|
|
|
{
|
|
|
|
bag.replaceTile(tile);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Nothing in the rack, nothing in the bag --> end of the (free)game
|
|
|
|
if (bag.getNbTiles() == 0)
|
|
|
|
{
|
|
|
|
if (reason)
|
|
|
|
*reason = 1;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether it is possible to complete the rack properly
|
|
|
|
if (bag.getNbVowels() < min ||
|
|
|
|
bag.getNbConsonants() < min)
|
|
|
|
{
|
|
|
|
if (reason)
|
|
|
|
*reason = 2;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In a duplicate game, we need at least 2 letters, even if we have
|
|
|
|
// one letter which can be considered both as a consonant and as a vowel
|
|
|
|
if (iCheck && bag.getNbTiles() < 2)
|
|
|
|
{
|
|
|
|
if (reason)
|
|
|
|
*reason = 2;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-30 16:14:13 +01:00
|
|
|
PlayedRack Game::getRackFromMasterGame() const
|
|
|
|
{
|
|
|
|
ASSERT(hasMasterGame(), "No master game defined");
|
|
|
|
|
|
|
|
// End the game when we reach the end of the master game,
|
|
|
|
// even if it is still possible to draw a rack
|
|
|
|
const unsigned currTurn = getNavigation().getCurrTurn();
|
|
|
|
if (currTurn >= m_masterGame->getHistory().getSize())
|
|
|
|
throw EndGameException(_("No more turn in the master game"));
|
|
|
|
|
|
|
|
const TurnData &turnData = m_masterGame->getHistory().getTurn(currTurn);
|
|
|
|
const PlayedRack &pldRack = turnData.getPlayedRack();
|
|
|
|
LOG_INFO("Using rack from master game: " << lfw(pldRack.toString()));
|
|
|
|
|
|
|
|
// Sanity check
|
|
|
|
ASSERT(rackInBag(pldRack.getRack(), m_bag),
|
|
|
|
"Cannot draw same rack as in the master game");
|
|
|
|
|
|
|
|
return pldRack;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Move Game::getMoveFromMasterGame() const
|
|
|
|
{
|
|
|
|
ASSERT(hasMasterGame(), "No master game defined");
|
|
|
|
|
|
|
|
const unsigned currTurn = getNavigation().getCurrTurn();
|
|
|
|
// Should never happen (already checked in getRackFromMasterGame())
|
|
|
|
ASSERT(currTurn < m_masterGame->getHistory().getSize(),
|
|
|
|
"Not enough turns in the master game");
|
|
|
|
|
|
|
|
const TurnData &turnData = m_masterGame->getHistory().getTurn(currTurn);
|
|
|
|
const Move &move = turnData.getMove();
|
|
|
|
|
|
|
|
// If the move is not valid, it means we reached the end
|
|
|
|
// of the master game. In this case, also end the current game.
|
|
|
|
if (!move.isValid())
|
|
|
|
throw EndGameException(_("No move defined for this turn in the master game"));
|
|
|
|
|
|
|
|
return move;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
PlayedRack Game::helperSetRackRandom(const PlayedRack &iPld,
|
|
|
|
bool iCheck, set_rack_mode mode) const
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2012-12-30 16:14:13 +01:00
|
|
|
// If a master game is defined, use it to retrieve the rack
|
|
|
|
if (hasMasterGame())
|
|
|
|
return getRackFromMasterGame();
|
|
|
|
|
2012-10-05 01:19:32 +02:00
|
|
|
int reason = 0;
|
|
|
|
if (!canDrawRack(iPld, iCheck, &reason))
|
|
|
|
{
|
|
|
|
if (reason == 1)
|
|
|
|
throw EndGameException(_("The bag is empty"));
|
|
|
|
else if (reason == 2)
|
|
|
|
throw EndGameException(_("Not enough vowels or consonants to complete the rack"));
|
|
|
|
ASSERT(false, "Error code not handled")
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// When iCheck is true, we must make sure that there are at least 2 vowels
|
|
|
|
// and 2 consonants in the rack up to the 15th turn, and at least one of
|
|
|
|
// each starting from the 16th turn.
|
|
|
|
// So before trying to fill the rack, we'd better make sure there is a way
|
|
|
|
// to complete the rack with these constraints...
|
|
|
|
unsigned int min = 0;
|
|
|
|
if (iCheck)
|
|
|
|
{
|
|
|
|
// 2 vowels and 2 consonants are needed up to the 15th turn
|
|
|
|
if (m_history.getSize() < 15)
|
|
|
|
min = 2;
|
|
|
|
else
|
|
|
|
min = 1;
|
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
// Make a copy of the given rack
|
|
|
|
PlayedRack pld = iPld;
|
2008-01-08 14:52:32 +01:00
|
|
|
int nold = pld.getNbOld();
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2005-03-29 00:07:22 +02:00
|
|
|
// Create a copy of the bag in which we can do everything we want,
|
2006-01-01 20:48:36 +01:00
|
|
|
// and take from it the tiles of the players rack so that "bag"
|
|
|
|
// contains the right number of tiles.
|
2011-08-28 18:44:34 +02:00
|
|
|
Bag bag(getDic());
|
2005-02-05 12:14:56 +01:00
|
|
|
realBag(bag);
|
|
|
|
if (mode == RACK_NEW && nold != 0)
|
|
|
|
{
|
2006-01-01 20:48:36 +01:00
|
|
|
// We may have removed too many letters from the bag (i.e. the 'new'
|
|
|
|
// letters of the player)
|
2005-03-29 00:07:22 +02:00
|
|
|
vector<Tile> tiles;
|
2005-02-05 12:14:56 +01:00
|
|
|
pld.getNewTiles(tiles);
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
bag.replaceTile(tile);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
pld.resetNew();
|
|
|
|
}
|
2008-07-27 17:26:15 +02:00
|
|
|
else if ((mode == RACK_NEW && nold == 0) || mode == RACK_ALL)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2006-01-01 20:48:36 +01:00
|
|
|
// Replace all the tiles in the bag before choosing random ones
|
|
|
|
vector<Tile> tiles;
|
|
|
|
pld.getAllTiles(tiles);
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
2006-01-01 20:48:36 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
bag.replaceTile(tile);
|
2006-01-01 20:48:36 +01:00
|
|
|
}
|
2005-03-29 00:07:22 +02:00
|
|
|
// RACK_NEW with an empty rack is equivalent to RACK_ALL
|
2005-02-05 12:14:56 +01:00
|
|
|
pld.reset();
|
2005-03-29 00:07:22 +02:00
|
|
|
// Do not forget to update nold, for the RACK_ALL case
|
2005-02-05 12:14:56 +01:00
|
|
|
nold = 0;
|
|
|
|
}
|
2006-01-01 20:48:36 +01:00
|
|
|
else
|
|
|
|
{
|
2009-01-24 11:28:20 +01:00
|
|
|
throw GameException(_("Not a random mode"));
|
2006-01-01 20:48:36 +01:00
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2011-08-27 19:21:26 +02:00
|
|
|
const unsigned int RACK_SIZE = m_params.getRackSize();
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Get the tiles remaining on the rack
|
|
|
|
vector<Tile> tiles;
|
|
|
|
pld.getOldTiles(tiles);
|
2008-11-23 19:18:02 +01:00
|
|
|
// The rack is already complete, there is nothing to do
|
|
|
|
if (tiles.size() >= RACK_SIZE)
|
2013-01-09 17:19:53 +01:00
|
|
|
{
|
|
|
|
LOG_DEBUG("Rack already complete, nothing to do");
|
2008-11-23 19:18:02 +01:00
|
|
|
return iPld;
|
2013-01-09 17:19:53 +01:00
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
bool jokerAdded = false;
|
|
|
|
// Are we dealing with a normal game or a joker game?
|
2011-08-28 19:24:37 +02:00
|
|
|
if (m_params.hasVariant(GameParams::kJOKER) ||
|
|
|
|
m_params.hasVariant(GameParams::kEXPLOSIVE))
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
|
|
|
// 1) Is there already a joker in the remaining letters of the rack?
|
|
|
|
bool jokerFound = false;
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Tile &tile, tiles)
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
if (tile.isJoker())
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
|
|
|
jokerFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2) If there was no joker, we add one if possible
|
2013-01-17 17:33:19 +01:00
|
|
|
if (!jokerFound && bag.contains(Tile::Joker()))
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
|
|
|
jokerAdded = true;
|
|
|
|
pld.addNew(Tile::Joker());
|
|
|
|
tiles.push_back(Tile::Joker());
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3) Remove all the jokers from the bag, to avoid taking another one
|
2013-01-17 17:33:19 +01:00
|
|
|
while (bag.contains(Tile::Joker()))
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
|
|
|
bag.takeTile(Tile::Joker());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle reject:
|
|
|
|
// Now that the joker has been dealt with, we try to complete the rack
|
|
|
|
// with truly random tiles. If it meets the requirements (i.e. if there
|
|
|
|
// are at least "min" vowels and "min" consonants in the rack), fine.
|
|
|
|
// Otherwise, we reject the rack completely, and we try again
|
|
|
|
// to complete it, but this time we ensure by construction that the
|
|
|
|
// requirements will be met.
|
|
|
|
while (bag.getNbTiles() != 0 && pld.getNbTiles() < RACK_SIZE)
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
const Tile &l = bag.selectRandom();
|
2008-01-08 14:52:32 +01:00
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pld.checkRack(min, min))
|
|
|
|
{
|
|
|
|
// Bad luck... we have to reject the rack
|
|
|
|
vector<Tile> rejectedTiles;
|
|
|
|
pld.getAllTiles(rejectedTiles);
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Tile &rejTile, rejectedTiles)
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
bag.replaceTile(rejTile);
|
2008-01-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
pld.reset();
|
|
|
|
// Do not mark the rack as rejected if it was empty
|
|
|
|
if (nold > 0)
|
|
|
|
pld.setReject();
|
2012-10-05 01:19:32 +02:00
|
|
|
|
|
|
|
// Keep track of the needed consonants and vowels in the rack
|
|
|
|
unsigned int neededVowels = min;
|
|
|
|
unsigned int neededConsonants = min;
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
// Restore the joker if we are in a joker game
|
|
|
|
if (jokerAdded)
|
2012-10-05 01:19:32 +02:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
pld.addNew(Tile::Joker());
|
2012-10-05 01:22:42 +02:00
|
|
|
if (neededVowels > 0)
|
|
|
|
--neededVowels;
|
|
|
|
if (neededConsonants > 0)
|
|
|
|
--neededConsonants;
|
2012-10-05 01:19:32 +02:00
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
// RACK_SIZE - tiles.size() is the number of letters to add to the rack
|
|
|
|
if (neededVowels > RACK_SIZE - tiles.size() ||
|
|
|
|
neededConsonants > RACK_SIZE - tiles.size())
|
|
|
|
{
|
|
|
|
// We cannot fill the rack with enough vowels or consonants!
|
|
|
|
// Actually this should never happen, but it doesn't hurt to check...
|
|
|
|
// FIXME: this test is not completely right, because it supposes no
|
|
|
|
// letter can be at the same time a vowel and a consonant
|
2008-11-22 14:09:28 +01:00
|
|
|
throw EndGameException("Not enough vowels or consonants to complete the rack");
|
2008-01-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the required vowels and consonants first
|
|
|
|
for (unsigned int i = 0; i < neededVowels; ++i)
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
const Tile &l = bag.selectRandomVowel();
|
2008-01-08 14:52:32 +01:00
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
// Handle the case where the vowel can also be considered
|
|
|
|
// as a consonant
|
|
|
|
if (l.isConsonant() && neededConsonants > 0)
|
2012-10-05 01:19:32 +02:00
|
|
|
--neededConsonants;
|
2008-01-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
for (unsigned int i = 0; i < neededConsonants; ++i)
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
const Tile &l = bag.selectRandomConsonant();
|
2008-01-08 14:52:32 +01:00
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The difficult part is done:
|
|
|
|
// - we have handled joker games
|
|
|
|
// - we have handled the checks
|
|
|
|
// Now complete the rack with truly random letters
|
|
|
|
while (bag.getNbTiles() != 0 && pld.getNbTiles() < RACK_SIZE)
|
|
|
|
{
|
2008-11-22 14:09:28 +01:00
|
|
|
const Tile &l = bag.selectRandom();
|
2008-01-08 14:52:32 +01:00
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-14 22:53:37 +01:00
|
|
|
// In explosive games, we have to perform a search, then replace the
|
|
|
|
// joker with the letter providing the best score
|
|
|
|
// A joker coming from a previous rack is not replaced
|
2011-08-28 19:24:37 +02:00
|
|
|
if (m_params.hasVariant(GameParams::kEXPLOSIVE) && jokerAdded)
|
2009-01-14 22:53:37 +01:00
|
|
|
{
|
2012-01-26 21:10:41 +01:00
|
|
|
const Rack &rack = pld.getRack();
|
2009-01-14 22:53:37 +01:00
|
|
|
|
2013-01-17 18:39:03 +01:00
|
|
|
MasterResults res(getBag());
|
2009-01-14 22:53:37 +01:00
|
|
|
res.search(getDic(), getBoard(), rack, getHistory().beforeFirstRound());
|
2013-01-16 11:09:28 +01:00
|
|
|
if (!res.isEmpty())
|
2009-01-14 22:53:37 +01:00
|
|
|
{
|
|
|
|
PlayedRack pldCopy = pld;
|
|
|
|
|
|
|
|
// Get the best word
|
2013-01-17 18:39:03 +01:00
|
|
|
const Round & bestRound = res.get(0);
|
2011-01-30 00:47:20 +01:00
|
|
|
LOG_DEBUG("helperSetRackRandom(): initial rack: "
|
2011-07-30 21:48:05 +02:00
|
|
|
<< lfw(pld.toString()) << " (best word: "
|
|
|
|
<< lfw(bestRound.getWord()) << ")");
|
2011-01-30 00:47:20 +01:00
|
|
|
|
2013-01-16 18:04:57 +01:00
|
|
|
// Identify the tile we should use to replace the joker
|
|
|
|
Tile replacingTile;
|
|
|
|
bool jokerUsed = false;
|
2009-01-14 22:53:37 +01:00
|
|
|
for (unsigned int i = 0; i < bestRound.getWordLen(); ++i)
|
|
|
|
{
|
|
|
|
if (bestRound.isJoker(i) && bestRound.isPlayedFromRack(i))
|
|
|
|
{
|
|
|
|
const Tile &jokerTile = bestRound.getTile(i);
|
2013-01-16 18:04:57 +01:00
|
|
|
replacingTile = jokerTile.toUpper();
|
|
|
|
jokerUsed = true;
|
2009-01-14 22:53:37 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-01-16 18:04:57 +01:00
|
|
|
if (!jokerUsed)
|
|
|
|
{
|
|
|
|
// The joker was not needed for the top. Replace it with a
|
|
|
|
// randomly selected tile
|
|
|
|
LOG_DEBUG("helperSetRackRandom(): joker not needed for the top");
|
|
|
|
replacingTile = bag.selectRandom();
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG_DEBUG("helperSetRackRandom(): replacing Joker with "
|
|
|
|
<< lfw(replacingTile.toChar()));
|
|
|
|
|
|
|
|
// If the bag does not contain the letter anymore,
|
|
|
|
// simply keep the joker in the rack.
|
2013-01-17 17:33:19 +01:00
|
|
|
if (bag.contains(replacingTile))
|
2013-01-16 18:04:57 +01:00
|
|
|
{
|
|
|
|
// The bag contains the replacing letter
|
|
|
|
// We need to swap the joker (it is necessarily in the
|
|
|
|
// new tiles, because jokerAdded is true)
|
|
|
|
Rack tmpRack = pld.getNew();
|
2013-01-17 17:58:56 +01:00
|
|
|
ASSERT(tmpRack.contains(Tile::Joker()), "No joker found in the new tiles");
|
2013-01-16 18:04:57 +01:00
|
|
|
tmpRack.remove(Tile::Joker());
|
|
|
|
tmpRack.add(replacingTile);
|
|
|
|
pld.setNew(tmpRack);
|
|
|
|
|
|
|
|
// Make sure the invariant is still correct, otherwise we keep the joker
|
|
|
|
if (!pld.checkRack(min, min))
|
|
|
|
pld = pldCopy;
|
|
|
|
}
|
2009-01-14 22:53:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Shuffle the new tiles, to hide the order we imposed (joker first in a
|
|
|
|
// joker game, then needed vowels, then needed consonants, and rest of the
|
|
|
|
// rack)
|
|
|
|
pld.shuffleNew();
|
|
|
|
|
|
|
|
// Post-condition check. This should never fail, of course :)
|
2008-08-31 13:48:11 +02:00
|
|
|
ASSERT(pld.checkRack(min, min), "helperSetRackRandom() is buggy!");
|
2008-01-08 14:52:32 +01:00
|
|
|
|
2008-11-22 14:09:28 +01:00
|
|
|
return pld;
|
2008-01-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-01 20:48:36 +01:00
|
|
|
bool Game::rackInBag(const Rack &iRack, const Bag &iBag) const
|
|
|
|
{
|
2011-08-28 18:44:34 +02:00
|
|
|
BOOST_FOREACH(const Tile &t, getDic().getAllTiles())
|
2006-01-01 20:48:36 +01:00
|
|
|
{
|
2013-01-17 17:55:00 +01:00
|
|
|
if (iRack.count(t) > iBag.count(t))
|
2006-01-01 20:48:36 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
|
2009-01-24 11:28:20 +01:00
|
|
|
PlayedRack Game::helperSetRackManual(bool iCheck, const wstring &iLetters) const
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2011-08-28 18:44:34 +02:00
|
|
|
if (!getDic().validateLetters(iLetters, L"+-"))
|
2009-01-24 11:28:20 +01:00
|
|
|
throw GameException(_("Some letters are invalid for the current dictionary"));
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
PlayedRack pld;
|
|
|
|
pld.setManual(iLetters);
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2012-01-26 21:10:41 +01:00
|
|
|
const Rack &rack = pld.getRack();
|
2005-03-27 23:45:04 +02:00
|
|
|
if (!rackInBag(rack, m_bag))
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2009-01-24 11:28:20 +01:00
|
|
|
throw GameException(_("The bag does not contain all these letters"));
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (iCheck)
|
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
int min;
|
|
|
|
if (m_bag.getNbVowels() > 1 && m_bag.getNbConsonants() > 1
|
2005-12-26 23:58:58 +01:00
|
|
|
&& m_history.getSize() < 15)
|
2005-02-05 12:14:56 +01:00
|
|
|
min = 2;
|
|
|
|
else
|
|
|
|
min = 1;
|
2008-01-08 14:52:32 +01:00
|
|
|
if (!pld.checkRack(min, min))
|
2008-11-22 14:09:28 +01:00
|
|
|
{
|
2009-01-24 11:28:20 +01:00
|
|
|
throw GameException(_("Not enough vowels or consonants in this rack"));
|
2008-11-22 14:09:28 +01:00
|
|
|
}
|
|
|
|
}
|
2009-01-24 11:28:20 +01:00
|
|
|
return pld;
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
2012-01-12 17:43:57 +01:00
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
/*********************************************************
|
|
|
|
*********************************************************/
|
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
unsigned int Game::getNHumanPlayers() const
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
unsigned int count = 0;
|
2008-11-22 14:09:28 +01:00
|
|
|
BOOST_FOREACH(const Player *player, m_players)
|
|
|
|
{
|
|
|
|
count += (player->isHuman() ? 1 : 0);
|
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-01-28 20:17:33 +01:00
|
|
|
void Game::addPlayer(Player *iPlayer)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-28 20:17:33 +01:00
|
|
|
ASSERT(iPlayer != NULL, "Invalid player pointer in addPlayer()");
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-28 20:17:33 +01:00
|
|
|
// The ID of the player is its position in the m_players vector
|
|
|
|
iPlayer->setId(getNPlayers());
|
|
|
|
m_players.push_back(iPlayer);
|
2011-01-30 00:47:20 +01:00
|
|
|
|
2011-07-30 21:48:05 +02:00
|
|
|
LOG_INFO("Adding player '" << lfw(iPlayer->getName())
|
2011-01-30 00:47:20 +01:00
|
|
|
<< "' (" << (iPlayer->isHuman() ? "human" : "AI") << ")"
|
|
|
|
<< " with ID " << iPlayer->getId());
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::nextPlayer()
|
|
|
|
{
|
2005-03-27 23:45:04 +02:00
|
|
|
ASSERT(getNPlayers() != 0, "Expected at least one player");
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-11-23 18:07:42 +01:00
|
|
|
unsigned int newPlayerId;
|
2005-02-05 12:14:56 +01:00
|
|
|
if (m_currPlayer == getNPlayers() - 1)
|
2008-11-23 18:07:42 +01:00
|
|
|
newPlayerId = 0;
|
2005-02-05 12:14:56 +01:00
|
|
|
else
|
2008-11-23 18:07:42 +01:00
|
|
|
newPlayerId = m_currPlayer + 1;
|
|
|
|
Command *pCmd = new CurrentPlayerCmd(*this, newPlayerId);
|
|
|
|
accessNavigation().addAndExecute(pCmd);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-22 13:23:52 +01:00
|
|
|
int Game::checkPlayedWord(const wstring &iCoord,
|
2009-11-29 17:01:31 +01:00
|
|
|
const wstring &iWord,
|
2012-12-24 20:32:47 +01:00
|
|
|
Move &oMove, bool checkRack,
|
|
|
|
bool checkWordAndJunction) const
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2005-03-27 23:45:04 +02:00
|
|
|
ASSERT(getNPlayers() != 0, "Expected at least one player");
|
|
|
|
|
2012-12-23 19:31:03 +01:00
|
|
|
// Assume that the move is invalid by default
|
|
|
|
const wdstring &dispWord = getDic().convertToDisplay(iWord);
|
|
|
|
oMove = Move(dispWord, iCoord);
|
|
|
|
|
2011-08-28 18:44:34 +02:00
|
|
|
if (!getDic().validateLetters(iWord))
|
2008-01-08 14:52:32 +01:00
|
|
|
return 1;
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Init the round with the given coordinates
|
2012-12-23 19:31:03 +01:00
|
|
|
Round round;
|
|
|
|
round.accessCoord().setFromString(iCoord);
|
|
|
|
if (!round.getCoord().isValid())
|
2006-08-12 00:13:02 +02:00
|
|
|
{
|
2005-02-05 12:14:56 +01:00
|
|
|
return 2;
|
2006-08-12 00:13:02 +02:00
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
// Check the existence of the word
|
2012-12-24 20:32:47 +01:00
|
|
|
if (checkWordAndJunction && !getDic().searchWord(iWord))
|
2006-08-12 00:13:02 +02:00
|
|
|
{
|
2005-02-05 12:14:56 +01:00
|
|
|
return 3;
|
2006-08-12 00:13:02 +02:00
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Set the word
|
2005-02-05 12:14:56 +01:00
|
|
|
// TODO: make this a Round_ function (Round_setwordfromchar for example)
|
2005-03-27 23:45:04 +02:00
|
|
|
// or a Tiles_ function (to transform a char* into a vector<Tile>)
|
2005-02-05 12:14:56 +01:00
|
|
|
// Adding a getter on the word could help too...
|
2008-01-08 14:52:32 +01:00
|
|
|
vector<Tile> tiles;
|
2005-02-05 12:14:56 +01:00
|
|
|
for (unsigned int i = 0; i < iWord.size(); i++)
|
|
|
|
{
|
|
|
|
tiles.push_back(Tile(iWord[i]));
|
|
|
|
}
|
2012-12-23 19:31:03 +01:00
|
|
|
round.setWord(tiles);
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Check the word position, compute its points,
|
|
|
|
// and specify the origin of each letter (board or rack)
|
2012-12-24 20:32:47 +01:00
|
|
|
int res = m_board.checkRound(round, checkWordAndJunction);
|
2005-02-05 12:14:56 +01:00
|
|
|
if (res != 0)
|
|
|
|
return res + 4;
|
2009-03-18 19:54:41 +01:00
|
|
|
// In duplicate mode, the first word must be horizontal
|
2012-12-24 20:32:47 +01:00
|
|
|
if (checkWordAndJunction && m_board.isVacant(8, 8) &&
|
2012-03-05 01:27:56 +01:00
|
|
|
(getMode() == GameParams::kDUPLICATE ||
|
2012-12-25 17:30:03 +01:00
|
|
|
getMode() == GameParams::kARBITRATION ||
|
|
|
|
getMode() == GameParams::kTOPPING))
|
2009-03-18 19:54:41 +01:00
|
|
|
{
|
2012-12-23 19:31:03 +01:00
|
|
|
if (round.getCoord().getDir() == Coord::VERTICAL)
|
2009-03-18 19:54:41 +01:00
|
|
|
return 10;
|
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2012-12-26 18:05:11 +01:00
|
|
|
if (checkWordAndJunction && checkRack)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2009-11-29 17:01:31 +01:00
|
|
|
// Check that the word can be formed with the tiles in the rack:
|
2016-02-07 09:48:56 +01:00
|
|
|
// we first create a copy of the game rack, then we remove the tiles
|
2009-11-29 17:01:31 +01:00
|
|
|
// one by one
|
2016-02-07 09:48:56 +01:00
|
|
|
Rack rack = getHistory().getCurrentRack().getRack();
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2009-11-29 17:01:31 +01:00
|
|
|
Tile t;
|
2012-12-23 19:31:03 +01:00
|
|
|
for (unsigned int i = 0; i < round.getWordLen(); i++)
|
2009-11-29 17:01:31 +01:00
|
|
|
{
|
2012-12-23 19:31:03 +01:00
|
|
|
if (round.isPlayedFromRack(i))
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2012-12-23 19:31:03 +01:00
|
|
|
if (round.isJoker(i))
|
2009-11-29 17:01:31 +01:00
|
|
|
t = Tile::Joker();
|
|
|
|
else
|
2012-12-23 19:31:03 +01:00
|
|
|
t = round.getTile(i);
|
2009-11-29 17:01:31 +01:00
|
|
|
|
2013-01-17 17:58:56 +01:00
|
|
|
if (!rack.contains(t))
|
2009-11-29 17:01:31 +01:00
|
|
|
{
|
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
rack.remove(t);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-23 19:31:03 +01:00
|
|
|
// The move is valid
|
|
|
|
oMove = Move(round);
|
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-23 18:07:42 +01:00
|
|
|
|
2013-01-15 11:38:46 +01:00
|
|
|
void Game::setGameAndPlayersRack(const PlayedRack &iRack, bool iWithNoMove)
|
2012-12-25 18:25:22 +01:00
|
|
|
{
|
|
|
|
// Set the game rack
|
|
|
|
Command *pCmd = new GameRackCmd(*this, iRack);
|
|
|
|
accessNavigation().addAndExecute(pCmd);
|
|
|
|
LOG_INFO("Setting players rack to '" + lfw(iRack.toString()) + "'");
|
|
|
|
// All the players have the same rack
|
|
|
|
BOOST_FOREACH(Player *player, m_players)
|
|
|
|
{
|
|
|
|
Command *pCmd = new PlayerRackCmd(*player, iRack);
|
|
|
|
accessNavigation().addAndExecute(pCmd);
|
|
|
|
}
|
|
|
|
|
2013-01-15 11:38:46 +01:00
|
|
|
if (iWithNoMove)
|
2012-12-25 18:25:22 +01:00
|
|
|
{
|
2013-01-15 11:38:46 +01:00
|
|
|
// Assign a "no move" pseudo-move to all the players.
|
|
|
|
// This avoids the need to distinguish between "has not played yet"
|
|
|
|
// and "has played with no move" in duplicate and arbitration modes.
|
|
|
|
// This is also practical to know at which turn the warnings, penalties
|
|
|
|
// and solos should be assigned.
|
|
|
|
BOOST_FOREACH(Player *player, m_players)
|
|
|
|
{
|
|
|
|
Command *pCmd = new PlayerMoveCmd(*player, Move());
|
|
|
|
accessNavigation().addAndExecute(pCmd);
|
|
|
|
}
|
2012-12-25 18:25:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-23 18:07:42 +01:00
|
|
|
Game::CurrentPlayerCmd::CurrentPlayerCmd(Game &ioGame,
|
|
|
|
unsigned int iPlayerId)
|
|
|
|
: m_game(ioGame), m_newPlayerId(iPlayerId), m_oldPlayerId(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::CurrentPlayerCmd::doExecute()
|
|
|
|
{
|
|
|
|
m_oldPlayerId = m_game.currPlayer();
|
|
|
|
m_game.setCurrentPlayer(m_newPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::CurrentPlayerCmd::doUndo()
|
|
|
|
{
|
|
|
|
m_game.setCurrentPlayer(m_oldPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wstring Game::CurrentPlayerCmd::toString() const
|
|
|
|
{
|
|
|
|
wostringstream oss;
|
|
|
|
oss << L"CurrentPlayerCmd (new player: " << m_newPlayerId;
|
|
|
|
if (isExecuted())
|
|
|
|
{
|
|
|
|
oss << L" old player: " << m_oldPlayerId;
|
|
|
|
}
|
|
|
|
oss << L")";
|
|
|
|
return oss.str();
|
|
|
|
}
|
|
|
|
|