game.cpp

Go to the documentation of this file.
00001 /*****************************************************************************
00002  * Copyright (C) 1999-2005 Eliot
00003  * Authors: Antoine Fraboulet <antoine.fraboulet@free.fr>
00004  *          Olivier Teuliere  <ipkiss@via.ecp.fr>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00019  *****************************************************************************/
00020 
00021 #include "dic.h"
00022 #include "dic_search.h"
00023 #include "tile.h"
00024 #include "rack.h"
00025 #include "round.h"
00026 #include "pldrack.h"
00027 #include "results.h"
00028 #include "player.h"
00029 #include "ai_percent.h"
00030 #include "game.h"
00031 #include "game_factory.h"
00032 #include "turn.h"
00033 
00034 #include "debug.h"
00035 
00036 
00037 const int Game::RACK_SIZE = 7;
00038 
00039 
00040 Game::Game(const Dictionary &iDic):
00041     m_dic(&iDic)
00042 {
00043     m_variant = kNONE;
00044     m_points = 0;
00045     m_currPlayer = -1;
00046     m_finished = false;
00047 }
00048 
00049 
00050 Game::~Game()
00051 {
00052     for (int i = 0; i < getNPlayers(); i++)
00053     {
00054         delete m_players[i];
00055     }
00056 }
00057 
00058 
00059 const Player& Game::getPlayer(int iNum) const
00060 {
00061     ASSERT(0 <= iNum && iNum < (int)m_players.size(), "Wrong player number");
00062     return *(m_players[iNum]);
00063 }
00064 
00065 
00066 /* This function plays a round on the board */
00067 int Game::helperPlayRound(const Round &iRound)
00068 {
00069     /*
00070      * We remove tiles from the bag only when they are played
00071      * on the board. When going back in the game, we must only
00072      * replace played tiles.
00073      * We test a rack when it is set but tiles are left in the bag.
00074      */
00075 
00076     // History of the game
00077   m_history.setCurrentRack(getCurrentPlayer().getLastRack());
00078   m_history.playRound(m_currPlayer, m_history.getSize(),  iRound);
00079 
00080     m_points += iRound.getPoints();
00081 
00082     // Before updating the bag and the board, if we are playing a "joker game",
00083     // we replace in the round the joker by the letter it represents
00084     // This is currently done by a succession of ugly hacks :-/
00085     if (m_variant == kJOKER)
00086     {
00087         for (int i = 0; i < iRound.getWordLen(); i++)
00088         {
00089             if (iRound.isPlayedFromRack(i) && iRound.isJoker(i))
00090             {
00091                 // Is the represented letter still available in the bag?
00092                 // FIXME: this way to get the represented letter sucks...
00093                 Tile t(toupper(iRound.getTile(i).toChar()));
00094                 Bag bag;
00095                 realBag(bag);
00096                 // FIXME: realBag() does not give us a real bag in this
00097                 // particular case! This is because Player::endTurn() is called
00098                 // before Game::helperPlayRound(), which means that the rack
00099                 // of the player is updated, while the word is not actually
00100                 // played on the board yet. Since realBag() relies on
00101                 // Player::getCurrentRack(), it doesn't remove the letters of
00102                 // the current player, which are in fact available through
00103                 // Player::getLastRack().
00104                 // That's why we have to replace the letters of the current
00105                 // rack and remove the ones from the previous rack...
00106                 // There is a big design problem here, but i am unsure what is
00107                 // the best way to fix it.
00108                 vector<Tile> tiles;
00109                 getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
00110                 for (unsigned int j = 0; j < tiles.size(); j++)
00111                 {
00112                     bag.replaceTile(tiles[j]);
00113                 }
00114                 getPlayer(m_currPlayer).getLastRack().getAllTiles(tiles);
00115                 for (unsigned int j = 0; j < tiles.size(); j++)
00116                 {
00117                     bag.takeTile(tiles[j]);
00118                 }
00119 
00120                 if (bag.in(t))
00121                 {
00122                     // FIXME: A const_cast sucks too...
00123                     const_cast<Round&>(iRound).setTile(i, t);
00124                     // FIXME: This shouldn't be necessary either, this is only
00125                     // needed because of the stupid way of handling jokers in
00126                     // rounds
00127                     const_cast<Round&>(iRound).setJoker(i, false);
00128                 }
00129             }
00130         }
00131     }
00132 
00133     // Update the bag and the board
00134     for (int i = 0; i < iRound.getWordLen(); i++)
00135     {
00136         if (iRound.isPlayedFromRack(i))
00137         {
00138             if (iRound.isJoker(i))
00139             {
00140                 m_bag.takeTile(Tile::Joker());
00141             }
00142             else
00143                 m_bag.takeTile(iRound.getTile(i));
00144         }
00145     }
00146     m_board.addRound(*m_dic, iRound);
00147 
00148     return 0;
00149 }
00150 
00151 
00152 int Game::back(int n)
00153 {
00154     int i, j;
00155     Player *player;
00156 
00157     if (n < 0)
00158     {
00159         debug("Game::back negative argument\n");
00160         n = -n;
00161     }
00162     debug("Game::back %d\n",n);
00163     for (i = 0; i < n; i++)
00164     {
00165         if (m_history.getSize() > 0)
00166         {
00167             prevPlayer();
00168             player = m_players[m_currPlayer];
00169             const Round &lastround = m_history.getPreviousTurn().getRound();
00170             debug("Game::back last round %s\n",lastround.toString().c_str());
00171             /* Remove the word from the board, and put its letters back
00172              * into the bag */
00173             m_board.removeRound(*m_dic, lastround);
00174             for (j = 0; j < lastround.getWordLen(); j++)
00175             {
00176                 if (lastround.isPlayedFromRack(j))
00177                 {
00178                     if (lastround.isJoker(j))
00179                         m_bag.replaceTile(Tile::Joker());
00180                     else
00181                         m_bag.replaceTile(lastround.getTile(j));
00182                 }
00183             }
00184             /* Remove the points of this round */
00185             player->addPoints(- lastround.getPoints());
00186             m_points -= lastround.getPoints();
00187             /* Remove the turns */
00188             player->removeLastTurn();
00189             m_history.removeLastTurn();
00190         }
00191         else
00192         {
00193             return 1;
00194         }
00195     }
00196     return 0;
00197 }
00198 
00199 
00200 /*********************************************************
00201  *********************************************************/
00202 
00203 void Game::realBag(Bag &ioBag) const
00204 {
00205     vector<Tile> tiles;
00206 
00207     /* Copy the bag */
00208     ioBag = m_bag;
00209 
00210     /* The real content of the bag depends on the game mode */
00211     if (getMode() == kFREEGAME)
00212     {
00213         /* In freegame mode, replace the letters from all the racks */
00214         for (int i = 0; i < getNPlayers(); i++)
00215         {
00216             getPlayer(i).getCurrentRack().getAllTiles(tiles);
00217             for (unsigned int j = 0; j < tiles.size(); j++)
00218             {
00219                 ioBag.takeTile(tiles[j]);
00220             }
00221         }
00222     }
00223     else
00224     {
00225         /* In training or duplicate mode, replace the rack of the current
00226          * player only */
00227         getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
00228         for (unsigned int j = 0; j < tiles.size(); j++)
00229         {
00230             ioBag.takeTile(tiles[j]);
00231         }
00232     }
00233 }
00234 
00235 
00236 bool Game::rackInBag(const Rack &iRack, const Bag &iBag) const
00237 {
00238     const list<Tile>& allTiles = Tile::getAllTiles();
00239     list<Tile>::const_iterator it;
00240     for (it = allTiles.begin(); it != allTiles.end(); it++)
00241     {
00242         if (iRack.in(*it) > iBag.in(*it))
00243             return false;
00244     }
00245     return true;
00246 }
00247 
00248 
00249 int Game::helperSetRackRandom(int p, bool iCheck, set_rack_mode mode)
00250 {
00251     ASSERT(0 <= p && p < getNPlayers(), "Wrong player number");
00252 
00253     int nold, min;
00254 
00255     // Make a copy of the player's rack
00256     PlayedRack pld = getPlayer(p).getCurrentRack();
00257     nold = pld.nOld();
00258 
00259     // Create a copy of the bag in which we can do everything we want,
00260     // and remove from it the tiles of the racks
00261     Bag bag;
00262     realBag(bag);
00263 
00264     // We may have removed too many letters from the bag (i.e. the 'new'
00265     // letters of the player)
00266     if (mode == RACK_NEW && nold != 0)
00267     {
00268         vector<Tile> tiles;
00269         pld.getNewTiles(tiles);
00270         for (unsigned int i = 0; i < tiles.size(); i++)
00271         {
00272             bag.replaceTile(tiles[i]);
00273         }
00274         pld.resetNew();
00275     }
00276     else
00277     {
00278         // RACK_NEW with an empty rack is equivalent to RACK_ALL
00279         pld.reset();
00280         // Do not forget to update nold, for the RACK_ALL case
00281         nold = 0;
00282     }
00283 
00284     // Nothing in the rack, nothing in the bag --> end of the game
00285     if (bag.nTiles() == 0 && pld.nTiles() == 0)
00286     {
00287         return 1;
00288     }
00289 
00290     // When iCheck is true, we must make sure that there are at least 2 vowels
00291     // and 2 consonants in the rack up to the 15th turn, and at least one of
00292     // them from the 16th turn.
00293     // So before trying to fill the rack, we'd better make sure there is a way
00294     // to complete the rack with these constraints...
00295     min = 0;
00296     if (iCheck)
00297     {
00298         int oldc, oldv;
00299 
00300         if (bag.nVowels() == 0 || bag.nConsonants() == 0)
00301         {
00302             return 1;
00303         }
00304         // 2 vowels and 2 consonants are needed up to the 15th turn
00305         if (bag.nVowels() > 1 && bag.nConsonants() > 1
00306             && m_history.getSize() < 15)
00307             min = 2;
00308         else
00309             min = 1;
00310 
00311         // Count the remaining consonants and vowels in the rack
00312         vector<Tile> tiles;
00313         pld.getOldTiles(tiles);
00314         oldc = 0;
00315         oldv = 0;
00316         for (unsigned int i = 0; i < tiles.size(); i++)
00317         {
00318             if (tiles[i].isConsonant())
00319                 oldc++;
00320             if (tiles[i].isVowel())
00321                 oldv++;
00322         }
00323 
00324         // RACK_SIZE - nold is the number of letters to add
00325         if (min > oldc + RACK_SIZE - nold ||
00326             min > oldv + RACK_SIZE - nold)
00327         {
00328             // We cannot fill the rack with enough vowels or consonants!
00329             return 3;
00330         }
00331     }
00332 
00333     // Are we dealing with a normal game or a joker game?
00334     if (m_variant == kJOKER)
00335     {
00336         // 1) Is there already a joker in the remaining letters of the rack?
00337         bool jokerFound = false;
00338         vector<Tile> tiles;
00339         pld.getOldTiles(tiles);
00340         for (unsigned int i = 0; i < tiles.size(); i++)
00341         {
00342             if (tiles[i].isJoker())
00343             {
00344                 jokerFound = true;
00345                 break;
00346             }
00347         }
00348 
00349         // 2) If there was no joker, we add one if possible
00350         if (!jokerFound && bag.in(Tile::Joker()))
00351         {
00352             bag.takeTile(Tile::Joker());
00353             pld.addNew(Tile::Joker());
00354         }
00355 
00356         // 3) Complete the rack normally... but without any joker!
00357         Tile l;
00358         while (bag.nTiles() != 0 && pld.nTiles() != RACK_SIZE)
00359         {
00360             l = bag.selectRandom();
00361             if (!l.isJoker())
00362             {
00363                 bag.takeTile(l);
00364                 pld.addNew(l);
00365             }
00366         }
00367     }
00368     else // Normal game
00369     {
00370         // Get new tiles from the bag
00371         Tile l;
00372         while (bag.nTiles() != 0 && pld.nTiles() != RACK_SIZE)
00373         {
00374             l = bag.selectRandom();
00375             bag.takeTile(l);
00376             pld.addNew(l);
00377         }
00378     }
00379 
00380     if (iCheck && !pld.checkRack(min))
00381         return 2;
00382 
00383     m_players[p]->setCurrentRack(pld);
00384 
00385     return 0;
00386 }
00387 
00388 
00389 int Game::helperSetRackManual(int p, bool iCheck, const string &iLetters)
00390 {
00391     unsigned int i;
00392     int min;
00393 
00394     PlayedRack pld = getPlayer(p).getCurrentRack();
00395     pld.reset();
00396 
00397     if (iLetters.size() == 0)
00398     {
00399         return 0;
00400     }
00401 
00402     for (i = 0; i < iLetters.size() && iLetters[i] != '+'; i++)
00403     {
00404         Tile tile(iLetters[i]);
00405         if (tile.isEmpty())
00406         {
00407             return 1;
00408         }
00409         pld.addOld(tile);
00410     }
00411 
00412     if (i < iLetters.size() && iLetters[i] == '+')
00413     {
00414         for (i++; i < iLetters.size(); i++)
00415         {
00416             Tile tile(iLetters[i]);
00417             if (tile.isEmpty())
00418             {
00419                 return 1;
00420             }
00421             pld.addNew(tile);
00422         }
00423     }
00424 
00425     Rack rack;
00426     pld.getRack(rack);
00427     if (!rackInBag(rack, m_bag))
00428     {
00429         pld.reset();
00430         return 1;
00431     }
00432 
00433     if (iCheck)
00434     {
00435         if (m_bag.nVowels() > 1 && m_bag.nConsonants() > 1
00436             && m_history.getSize() < 15)
00437             min = 2;
00438         else
00439             min = 1;
00440         if (!pld.checkRack(min))
00441             return 2;
00442     }
00443 
00444     m_players[p]->setCurrentRack(pld);
00445 
00446     return 0;
00447 }
00448 
00449 /*********************************************************
00450  *********************************************************/
00451 
00452 string Game::getPlayerRack(int num, PlayedRack::display_mode mode) const
00453 {
00454     return getPlayer(num).getCurrentRack().toString(mode);
00455 }
00456 
00457 
00458 int Game::getNHumanPlayers() const
00459 {
00460     int count = 0;
00461     for (int i = 0; i < getNPlayers(); i++)
00462         count += (getPlayer(i).isHuman() ? 1 : 0);
00463     return count;
00464 }
00465 
00466 
00467 void Game::addHumanPlayer()
00468 {
00469     // The ID of the player is its position in the m_players vector
00470     m_players.push_back(new HumanPlayer(getNPlayers()));
00471 }
00472 
00473 
00474 void Game::addAIPlayer()
00475 {
00476     m_players.push_back(new AIPercent(getNPlayers(), 0));
00477 }
00478 
00479 
00480 void Game::prevPlayer()
00481 {
00482     ASSERT(getNPlayers() != 0, "Expected at least one player");
00483 
00484     if (m_currPlayer == 0)
00485         m_currPlayer = getNPlayers() - 1;
00486     else
00487         m_currPlayer--;
00488 }
00489 
00490 
00491 void Game::nextPlayer()
00492 {
00493     ASSERT(getNPlayers() != 0, "Expected at least one player");
00494 
00495     if (m_currPlayer == getNPlayers() - 1)
00496         m_currPlayer = 0;
00497     else
00498         m_currPlayer++;
00499 }
00500 
00501 
00502 /*
00503  * This function checks whether it is legal to play the given word at the
00504  * given coordinates. If so, the function fills a Round object, also given as
00505  * a parameter.
00506  * Possible return values:
00507  *  0: correct word, the Round can be used by the caller
00508  *  1: no dictionary set
00509  *  2: invalid coordinates (unreadable or out of the board)
00510  *  3: word not present in the dictionary
00511  *  4: not enough letters in the rack to play the word
00512  *  5: word is part of a longer one
00513  *  6: word overwriting an existing letter
00514  *  7: invalid crosscheck, or word going out of the board
00515  *  8: word already present on the board (no new letter from the rack)
00516  *  9: isolated word (not connected to the rest)
00517  * 10: first word not horizontal
00518  * 11: first word not covering the H8 square
00519  */
00520 int Game::checkPlayedWord(const string &iCoord,
00521                           const string &iWord, Round &oRound)
00522 {
00523     ASSERT(getNPlayers() != 0, "Expected at least one player");
00524 
00525     int res;
00526     vector<Tile> tiles;
00527     Tile t;
00528 
00529     /* Init the round with the given coordinates */
00530     oRound.init();
00531     oRound.accessCoord().setFromString(iCoord);
00532     if (!oRound.getCoord().isValid())
00533         return 2;
00534 
00535     /* Check the existence of the word */
00536     if (Dic_search_word(*m_dic, iWord.c_str()) == 0)
00537         return 3;
00538 
00539     /* Set the word */
00540     // TODO: make this a Round_ function (Round_setwordfromchar for example)
00541     // or a Tiles_ function (to transform a char* into a vector<Tile>)
00542     // Adding a getter on the word could help too...
00543     for (unsigned int i = 0; i < iWord.size(); i++)
00544     {
00545         tiles.push_back(Tile(iWord[i]));
00546     }
00547     oRound.setWord(tiles);
00548     for (unsigned int i = 0; i < iWord.size(); i++)
00549     {
00550         if (islower(iWord[i]))
00551             oRound.setJoker(i);
00552     }
00553 
00554     /* Check the word position, compute its points,
00555      * and specify the origin of each letter (board or rack) */
00556     res = m_board.checkRound(oRound, m_history.getSize() == 0);
00557     if (res != 0)
00558         return res + 4;
00559 
00560     /* Check that the word can be formed with the tiles in the rack:
00561      * we first create a copy of the rack, then we remove the tiles
00562      * one by one */
00563     Rack rack;
00564     Player *player = m_players[m_currPlayer];
00565     player->getCurrentRack().getRack(rack);
00566 
00567     for (int i = 0; i < oRound.getWordLen(); i++)
00568     {
00569         if (oRound.isPlayedFromRack(i))
00570         {
00571             if (oRound.isJoker(i))
00572                 t = Tile::Joker();
00573             else
00574                 t = oRound.getTile(i);
00575 
00576             if (!rack.in(t))
00577             {
00578                 return 4;
00579             }
00580             rack.remove(t);
00581         }
00582     }
00583 
00584     return 0;
00585 }
00586 
00587 /****************************************************************/
00588 /****************************************************************/
00589 
00590 /// Local Variables:
00591 /// mode: c++
00592 /// mode: hs-minor
00593 /// c-basic-offset: 4
00594 /// indent-tabs-mode: nil
00595 /// End:

Generated on Thu Dec 29 02:01:14 2005 for Eliot by  doxygen 1.4.5