2005-02-05 12:14:56 +01:00
|
|
|
/*****************************************************************************
|
2008-01-08 14:52:32 +01:00
|
|
|
* Eliot
|
|
|
|
* Copyright (C) 1999-2007 Antoine Fraboulet & Olivier Teulière
|
|
|
|
* 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
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "dic.h"
|
|
|
|
#include "tile.h"
|
|
|
|
#include "rack.h"
|
|
|
|
#include "round.h"
|
|
|
|
#include "pldrack.h"
|
|
|
|
#include "results.h"
|
|
|
|
#include "player.h"
|
2005-02-17 21:01:59 +01:00
|
|
|
#include "ai_percent.h"
|
2005-02-05 12:14:56 +01:00
|
|
|
#include "game.h"
|
2005-02-24 09:06:24 +01:00
|
|
|
#include "game_factory.h"
|
2005-11-06 00:22:41 +01:00
|
|
|
#include "turn.h"
|
2006-01-22 13:23:52 +01:00
|
|
|
#include "encoding.h"
|
2005-02-05 12:14:56 +01:00
|
|
|
|
|
|
|
#include "debug.h"
|
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
const unsigned int Game::RACK_SIZE = 7;
|
2007-08-04 22:01:27 +02:00
|
|
|
const int Game::BONUS_POINTS = 50;
|
2005-02-26 23:57:34 +01:00
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
Game::Game(const Dictionary &iDic):
|
2008-01-08 14:52:32 +01:00
|
|
|
m_dic(iDic), m_bag(iDic)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2005-03-29 00:07:22 +02:00
|
|
|
m_variant = kNONE;
|
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
|
|
|
m_finished = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Game::~Game()
|
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
for (unsigned int i = 0; i < getNPlayers(); i++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
delete m_players[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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-01-08 14:52:32 +01:00
|
|
|
void Game::helperPlayMove(unsigned int iPlayerId, const Move &iMove)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2005-03-29 00:07:22 +02:00
|
|
|
// History of the game
|
2008-01-08 14:52:32 +01:00
|
|
|
m_history.setCurrentRack(getPlayer(iPlayerId).getLastRack());
|
|
|
|
m_history.playMove(iPlayerId, m_history.getSize(), iMove);
|
|
|
|
|
|
|
|
// Points
|
|
|
|
debug(" helper: %d points\n", iMove.getScore());
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
void Game::helperPlayRound(unsigned int iPlayerId, const Round &iRound)
|
|
|
|
{
|
2005-03-29 00:07:22 +02:00
|
|
|
// 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
|
2005-04-02 22:46:42 +02:00
|
|
|
// This is currently done by a succession of ugly hacks :-/
|
2005-03-29 00:07:22 +02:00
|
|
|
if (m_variant == kJOKER)
|
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
for (unsigned int i = 0; i < iRound.getWordLen(); i++)
|
2005-03-29 00:07:22 +02:00
|
|
|
{
|
|
|
|
if (iRound.isPlayedFromRack(i) && iRound.isJoker(i))
|
|
|
|
{
|
|
|
|
// Is the represented letter still available in the bag?
|
2008-01-08 14:52:32 +01:00
|
|
|
// XXX: this way to get the represented letter sucks...
|
2006-12-23 14:50:43 +01:00
|
|
|
Tile t(towupper(iRound.getTile(i).toChar()));
|
2008-01-08 14:52:32 +01:00
|
|
|
Bag bag(m_dic);
|
2005-03-29 00:07:22 +02:00
|
|
|
realBag(bag);
|
2005-04-02 22:46:42 +02:00
|
|
|
// 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;
|
2008-01-08 14:52:32 +01:00
|
|
|
getPlayer(iPlayerId).getCurrentRack().getAllTiles(tiles);
|
2005-04-02 22:46:42 +02:00
|
|
|
for (unsigned int j = 0; j < tiles.size(); j++)
|
|
|
|
{
|
|
|
|
bag.replaceTile(tiles[j]);
|
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
getPlayer(iPlayerId).getLastRack().getAllTiles(tiles);
|
2005-04-02 22:46:42 +02:00
|
|
|
for (unsigned int j = 0; j < tiles.size(); j++)
|
|
|
|
{
|
|
|
|
bag.takeTile(tiles[j]);
|
|
|
|
}
|
|
|
|
|
2005-03-29 00:07:22 +02:00
|
|
|
if (bag.in(t))
|
|
|
|
{
|
|
|
|
// FIXME: A const_cast sucks too...
|
|
|
|
const_cast<Round&>(iRound).setTile(i, t);
|
|
|
|
// FIXME: This shouldn't be necessary either, this is only
|
|
|
|
// needed because of the stupid way of handling jokers in
|
|
|
|
// rounds
|
|
|
|
const_cast<Round&>(iRound).setJoker(i, false);
|
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
// In a joker game we should have only 1 joker in the rack
|
|
|
|
break;
|
2005-03-29 00:07:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// 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 < iRound.getWordLen(); i++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
if (iRound.isPlayedFromRack(i))
|
|
|
|
{
|
|
|
|
if (iRound.isJoker(i))
|
2005-03-29 00:07:22 +02:00
|
|
|
{
|
2005-02-05 12:14:56 +01:00
|
|
|
m_bag.takeTile(Tile::Joker());
|
2005-03-29 00:07:22 +02:00
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
else
|
2006-08-12 00:13:02 +02:00
|
|
|
{
|
2005-02-05 12:14:56 +01:00
|
|
|
m_bag.takeTile(iRound.getTile(i));
|
2006-08-12 00:13:02 +02:00
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Update the board
|
|
|
|
m_board.addRound(m_dic, iRound);
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
int Game::back(unsigned int n)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2006-01-01 20:48:36 +01:00
|
|
|
debug("Game::back %d\n",n);
|
2008-01-08 14:52:32 +01:00
|
|
|
// TODO: throw an exception
|
|
|
|
if (m_history.getSize() < n)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < n; i++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
prevPlayer();
|
|
|
|
const Move &lastMove = m_history.getPreviousTurn().getMove();
|
|
|
|
// Nothing to cancel if the move was not a valid round
|
|
|
|
if (lastMove.getType() != Move::VALID_ROUND)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const Round &lastround = lastMove.getRound();
|
|
|
|
debug("Game::back last round %s\n",
|
|
|
|
convertToMb(lastround.toString()).c_str());
|
|
|
|
/* Remove the word from the board, and put its letters back
|
|
|
|
* into the bag */
|
|
|
|
m_board.removeRound(m_dic, lastround);
|
|
|
|
for (unsigned int j = 0; j < lastround.getWordLen(); j++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
if (lastround.isPlayedFromRack(j))
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
if (lastround.isJoker(j))
|
|
|
|
m_bag.replaceTile(Tile::Joker());
|
|
|
|
else
|
|
|
|
m_bag.replaceTile(lastround.getTile(j));
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
2005-11-05 00:26:03 +01:00
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
/* Remove the points of this round */
|
|
|
|
m_points -= lastround.getPoints();
|
|
|
|
/* Remove the turns */
|
|
|
|
m_players[m_currPlayer]->removeLastTurn();
|
|
|
|
m_history.removeLastTurn();
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
void Game::realBag(Bag &ioBag) const
|
|
|
|
{
|
|
|
|
vector<Tile> tiles;
|
|
|
|
|
|
|
|
/* Copy the bag */
|
|
|
|
ioBag = m_bag;
|
|
|
|
|
2005-03-29 00:07:22 +02:00
|
|
|
/* The real content of the bag depends on the game mode */
|
2005-02-05 12:14:56 +01:00
|
|
|
if (getMode() == kFREEGAME)
|
|
|
|
{
|
2006-01-01 20:48:36 +01:00
|
|
|
/* In freegame mode, take the letters from all the racks */
|
2008-01-08 14:52:32 +01:00
|
|
|
for (unsigned int i = 0; i < getNPlayers(); i++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2005-11-06 02:05:06 +01:00
|
|
|
getPlayer(i).getCurrentRack().getAllTiles(tiles);
|
2005-02-05 12:14:56 +01:00
|
|
|
for (unsigned int j = 0; j < tiles.size(); j++)
|
|
|
|
{
|
|
|
|
ioBag.takeTile(tiles[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-01-01 20:48:36 +01:00
|
|
|
/* In training or duplicate mode, take the rack of the current
|
2005-02-05 12:14:56 +01:00
|
|
|
* player only */
|
2005-11-06 02:05:06 +01:00
|
|
|
getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
|
2005-04-02 22:46:42 +02:00
|
|
|
for (unsigned int j = 0; j < tiles.size(); j++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
ioBag.takeTile(tiles[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
int Game::helperSetRackRandom(unsigned int p, bool iCheck, set_rack_mode mode)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
ASSERT(p < getNPlayers(), "Wrong player number");
|
|
|
|
// FIXME: RACK_MANUAL shouldn't be in the enum
|
|
|
|
ASSERT(mode != RACK_MANUAL, "Invalid rack mode");
|
2005-03-27 23:45:04 +02:00
|
|
|
|
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
|
|
|
|
2006-01-01 20:48:36 +01:00
|
|
|
// Make a copy of the current player's rack
|
2005-11-06 02:05:06 +01:00
|
|
|
PlayedRack pld = getPlayer(p).getCurrentRack();
|
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.
|
2008-01-08 14:52:32 +01:00
|
|
|
Bag bag(m_dic);
|
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);
|
|
|
|
for (unsigned int i = 0; i < tiles.size(); i++)
|
|
|
|
{
|
|
|
|
bag.replaceTile(tiles[i]);
|
|
|
|
}
|
|
|
|
pld.resetNew();
|
|
|
|
}
|
2006-01-01 20:48:36 +01: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);
|
|
|
|
for (unsigned int i = 0; i < tiles.size(); i++)
|
|
|
|
{
|
|
|
|
bag.replaceTile(tiles[i]);
|
|
|
|
}
|
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
|
|
|
|
{
|
|
|
|
debug("Game::helperSetRackRandom not a random mode\n");
|
|
|
|
}
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Get the tiles remaining on the rack
|
|
|
|
vector<Tile> tiles;
|
|
|
|
pld.getOldTiles(tiles);
|
|
|
|
ASSERT(tiles.size() < RACK_SIZE,
|
|
|
|
"Cannot complete the rack, it is already complete");
|
|
|
|
|
|
|
|
bool jokerAdded = false;
|
|
|
|
// Are we dealing with a normal game or a joker game?
|
|
|
|
if (m_variant == kJOKER)
|
|
|
|
{
|
|
|
|
// 1) Is there already a joker in the remaining letters of the rack?
|
|
|
|
bool jokerFound = false;
|
|
|
|
for (unsigned int i = 0; i < tiles.size(); i++)
|
|
|
|
{
|
|
|
|
if (tiles[i].isJoker())
|
|
|
|
{
|
|
|
|
jokerFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2) If there was no joker, we add one if possible
|
|
|
|
if (!jokerFound && bag.in(Tile::Joker()))
|
|
|
|
{
|
|
|
|
jokerAdded = true;
|
|
|
|
pld.addNew(Tile::Joker());
|
|
|
|
tiles.push_back(Tile::Joker());
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3) Remove all the jokers from the bag, to avoid taking another one
|
|
|
|
for (unsigned int i = 0; i < bag.in(Tile::Joker()); ++i)
|
|
|
|
{
|
|
|
|
bag.takeTile(Tile::Joker());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-10 11:23:36 +01:00
|
|
|
// Count the needed consonants and vowels in the rack
|
|
|
|
// (i.e. minimum required, minus what we already have in the rack)
|
|
|
|
unsigned int neededVowels = min;
|
|
|
|
unsigned int neededConsonants = min;
|
|
|
|
for (unsigned int i = 0; i < tiles.size(); ++i)
|
|
|
|
{
|
|
|
|
if (neededVowels > 0 && tiles[i].isVowel())
|
|
|
|
neededVowels--;
|
|
|
|
if (neededConsonants > 0 && tiles[i].isConsonant())
|
|
|
|
neededConsonants--;
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Nothing in the rack, nothing in the bag --> end of the (free)game
|
|
|
|
if (bag.getNbTiles() == 0 && pld.getNbTiles() == 0)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
2008-01-10 11:23:36 +01:00
|
|
|
|
|
|
|
// Check whether it is possible to complete the rack properly
|
|
|
|
if (bag.getNbVowels() < neededVowels ||
|
|
|
|
bag.getNbConsonants() < neededConsonants)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
2008-01-08 14:52:32 +01:00
|
|
|
// End of game condition
|
|
|
|
if (iCheck)
|
|
|
|
{
|
2008-01-10 11:23:36 +01:00
|
|
|
if (bag.getNbVowels() < neededVowels ||
|
|
|
|
bag.getNbConsonants() < neededConsonants ||
|
|
|
|
(bag.getNbTiles() + tiles.size()) == 1)
|
2008-01-08 14:52:32 +01:00
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
Tile l = bag.selectRandom();
|
|
|
|
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);
|
|
|
|
for (unsigned int i = 0; i < rejectedTiles.size(); i++)
|
|
|
|
{
|
|
|
|
bag.replaceTile(rejectedTiles[i]);
|
|
|
|
}
|
|
|
|
pld.reset();
|
|
|
|
// Do not mark the rack as rejected if it was empty
|
|
|
|
if (nold > 0)
|
|
|
|
pld.setReject();
|
|
|
|
|
|
|
|
// Restore the joker if we are in a joker game
|
|
|
|
if (jokerAdded)
|
|
|
|
pld.addNew(Tile::Joker());
|
|
|
|
|
|
|
|
// 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
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the required vowels and consonants first
|
|
|
|
for (unsigned int i = 0; i < neededVowels; ++i)
|
|
|
|
{
|
|
|
|
Tile l = bag.selectRandomVowel();
|
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
// Handle the case where the vowel can also be considered
|
|
|
|
// as a consonant
|
|
|
|
if (l.isConsonant() && neededConsonants > 0)
|
|
|
|
neededConsonants--;
|
|
|
|
}
|
|
|
|
for (unsigned int i = 0; i < neededConsonants; ++i)
|
|
|
|
{
|
|
|
|
Tile l = bag.selectRandomConsonant();
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
Tile l = bag.selectRandom();
|
|
|
|
bag.takeTile(l);
|
|
|
|
pld.addNew(l);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 :)
|
|
|
|
ASSERT(pld.checkRack(min, min), "helperSetRackRandom() is buggy!")
|
|
|
|
|
|
|
|
// Until now we didn't modify anything except local variables.
|
|
|
|
// Let's "commit" the changes
|
|
|
|
m_players[p]->setCurrentRack(pld);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-01 20:48:36 +01:00
|
|
|
bool Game::rackInBag(const Rack &iRack, const Bag &iBag) const
|
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
const vector<Tile>& allTiles = m_dic.getAllTiles();
|
|
|
|
vector<Tile>::const_iterator it;
|
2006-01-01 20:48:36 +01:00
|
|
|
for (it = allTiles.begin(); it != allTiles.end(); it++)
|
|
|
|
{
|
|
|
|
if (iRack.in(*it) > iBag.in(*it))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
|
|
|
|
int Game::helperSetRackManual(unsigned int p, bool iCheck, const wstring &iLetters)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
ASSERT(p < getNPlayers(), "Wrong player number");
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-26 11:10:50 +01:00
|
|
|
if (!m_dic.validateLetters(iLetters, L"+-"))
|
2008-01-08 14:52:32 +01:00
|
|
|
return 3;
|
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
|
|
|
|
|
|
|
Rack rack;
|
|
|
|
pld.getRack(rack);
|
2005-03-27 23:45:04 +02:00
|
|
|
if (!rackInBag(rack, m_bag))
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
pld.reset();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
2005-02-09 23:33:56 +01:00
|
|
|
return 2;
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
2005-02-09 23:33:56 +01:00
|
|
|
m_players[p]->setCurrentRack(pld);
|
|
|
|
|
2005-02-05 12:14:56 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************
|
|
|
|
*********************************************************/
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
for (unsigned int i = 0; i < getNPlayers(); i++)
|
2005-11-06 02:05:06 +01:00
|
|
|
count += (getPlayer(i).isHuman() ? 1 : 0);
|
2005-02-05 12:14:56 +01:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::addHumanPlayer()
|
|
|
|
{
|
2005-11-06 18:13:45 +01:00
|
|
|
// The ID of the player is its position in the m_players vector
|
|
|
|
m_players.push_back(new HumanPlayer(getNPlayers()));
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::addAIPlayer()
|
|
|
|
{
|
2008-01-08 14:52:32 +01:00
|
|
|
// TODO: allow other percentages, and even other types of AI
|
|
|
|
m_players.push_back(new AIPercent(getNPlayers(), 1));
|
2005-02-05 12:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Game::prevPlayer()
|
|
|
|
{
|
2005-03-27 23:45:04 +02:00
|
|
|
ASSERT(getNPlayers() != 0, "Expected at least one player");
|
2005-02-05 12:14:56 +01:00
|
|
|
|
|
|
|
if (m_currPlayer == 0)
|
|
|
|
m_currPlayer = getNPlayers() - 1;
|
|
|
|
else
|
|
|
|
m_currPlayer--;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
if (m_currPlayer == getNPlayers() - 1)
|
|
|
|
m_currPlayer = 0;
|
|
|
|
else
|
|
|
|
m_currPlayer++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-22 13:23:52 +01:00
|
|
|
int Game::checkPlayedWord(const wstring &iCoord,
|
|
|
|
const wstring &iWord, Round &oRound)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
2005-03-27 23:45:04 +02:00
|
|
|
ASSERT(getNPlayers() != 0, "Expected at least one player");
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
if (!m_dic.validateLetters(iWord))
|
|
|
|
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
|
2005-02-05 12:14:56 +01:00
|
|
|
oRound.init();
|
2005-11-05 14:56:59 +01:00
|
|
|
oRound.accessCoord().setFromString(iCoord);
|
|
|
|
if (!oRound.getCoord().isValid())
|
2006-08-12 00:13:02 +02:00
|
|
|
{
|
|
|
|
debug("game: incorrect coordinates\n");
|
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
|
|
|
|
if (!m_dic.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]));
|
|
|
|
}
|
|
|
|
oRound.setWord(tiles);
|
|
|
|
for (unsigned int i = 0; i < iWord.size(); i++)
|
|
|
|
{
|
|
|
|
if (islower(iWord[i]))
|
|
|
|
oRound.setJoker(i);
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
int res = m_board.checkRound(oRound, m_history.getSize() == 0);
|
2005-02-05 12:14:56 +01:00
|
|
|
if (res != 0)
|
|
|
|
return res + 4;
|
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
// Check that the word can be formed with the tiles in the rack:
|
|
|
|
// we first create a copy of the rack, then we remove the tiles
|
|
|
|
// one by one
|
2005-02-05 12:14:56 +01:00
|
|
|
Rack rack;
|
|
|
|
Player *player = m_players[m_currPlayer];
|
2005-02-09 23:33:56 +01:00
|
|
|
player->getCurrentRack().getRack(rack);
|
2005-02-05 12:14:56 +01:00
|
|
|
|
2008-01-08 14:52:32 +01:00
|
|
|
Tile t;
|
|
|
|
for (unsigned int i = 0; i < oRound.getWordLen(); i++)
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
if (oRound.isPlayedFromRack(i))
|
|
|
|
{
|
|
|
|
if (oRound.isJoker(i))
|
|
|
|
t = Tile::Joker();
|
|
|
|
else
|
|
|
|
t = oRound.getTile(i);
|
|
|
|
|
2005-03-27 23:45:04 +02:00
|
|
|
if (!rack.in(t))
|
2005-02-05 12:14:56 +01:00
|
|
|
{
|
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
rack.remove(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-12-27 02:06:54 +01:00
|
|
|
/****************************************************************/
|
|
|
|
/****************************************************************/
|
|
|
|
|
|
|
|
/// Local Variables:
|
|
|
|
/// mode: c++
|
|
|
|
/// mode: hs-minor
|
|
|
|
/// c-basic-offset: 4
|
2006-01-01 20:48:36 +01:00
|
|
|
/// indent-tabs-mode: nil
|
2005-12-27 02:06:54 +01:00
|
|
|
/// End:
|