2008-11-30 21:53:44 +01:00
|
|
|
/*******************************************************************
|
|
|
|
* Eliot
|
2012-10-07 16:25:41 +02:00
|
|
|
* Copyright (C) 2008-2012 Olivier Teulière
|
2008-11-30 21:53:44 +01:00
|
|
|
* 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 "public_game.h"
|
2011-08-27 19:21:26 +02:00
|
|
|
#include "game_params.h"
|
2008-11-30 21:53:44 +01:00
|
|
|
#include "game.h"
|
|
|
|
#include "training.h"
|
|
|
|
#include "duplicate.h"
|
2012-03-05 01:27:56 +01:00
|
|
|
#include "arbitration.h"
|
2008-11-30 21:53:44 +01:00
|
|
|
#include "freegame.h"
|
2012-12-25 17:30:03 +01:00
|
|
|
#include "topping.h"
|
2009-11-29 17:01:31 +01:00
|
|
|
#include "game_factory.h"
|
2008-11-30 21:53:44 +01:00
|
|
|
#include "game_exception.h"
|
2009-11-29 17:01:31 +01:00
|
|
|
#include "xml_writer.h"
|
2012-03-19 12:39:13 +01:00
|
|
|
#include "player.h"
|
2012-03-26 07:48:33 +02:00
|
|
|
#include "pldrack.h"
|
2008-11-30 21:53:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
PublicGame::PublicGame(Game &iGame)
|
|
|
|
: m_game(iGame)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PublicGame::~PublicGame()
|
|
|
|
{
|
|
|
|
delete &m_game;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PublicGame::GameMode PublicGame::getMode() const
|
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
if (dynamic_cast<Arbitration*>(&m_game))
|
|
|
|
return kARBITRATION;
|
|
|
|
else if (dynamic_cast<Duplicate*>(&m_game))
|
2008-11-30 21:53:44 +01:00
|
|
|
return kDUPLICATE;
|
|
|
|
else if (dynamic_cast<FreeGame*>(&m_game))
|
|
|
|
return kFREEGAME;
|
2012-12-25 17:30:03 +01:00
|
|
|
else if (dynamic_cast<Topping*>(&m_game))
|
|
|
|
return kTOPPING;
|
2008-11-30 21:53:44 +01:00
|
|
|
else
|
|
|
|
return kTRAINING;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-27 19:21:26 +02:00
|
|
|
const GameParams & PublicGame::getParams() const
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2011-08-27 19:21:26 +02:00
|
|
|
return m_game.getParams();
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-30 16:14:13 +01:00
|
|
|
bool PublicGame::hasMasterGame() const
|
|
|
|
{
|
|
|
|
return m_game.hasMasterGame();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
const Dictionary & PublicGame::getDic() const
|
|
|
|
{
|
|
|
|
return m_game.getDic();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Board& PublicGame::getBoard() const
|
|
|
|
{
|
|
|
|
return m_game.getBoard();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Bag& PublicGame::getBag() const
|
|
|
|
{
|
|
|
|
return m_game.getBag();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-26 07:48:33 +02:00
|
|
|
const PlayedRack& PublicGame::getCurrentRack() const
|
|
|
|
{
|
|
|
|
return m_game.getHistory().getCurrentRack();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
const History& PublicGame::getHistory() const
|
|
|
|
{
|
|
|
|
return m_game.getHistory();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::addPlayer(Player *iPlayer)
|
|
|
|
{
|
|
|
|
m_game.addPlayer(iPlayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Player& PublicGame::getPlayer(unsigned int iNum) const
|
|
|
|
{
|
|
|
|
return m_game.getPlayer(iNum);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Player& PublicGame::getCurrentPlayer() const
|
|
|
|
{
|
|
|
|
return m_game.getCurrentPlayer();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int PublicGame::getNbPlayers() const
|
|
|
|
{
|
|
|
|
return m_game.getNPlayers();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int PublicGame::getNbHumanPlayers() const
|
|
|
|
{
|
|
|
|
return m_game.getNHumanPlayers();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-19 12:39:13 +01:00
|
|
|
void PublicGame::setPlayerName(unsigned iPlayerId, const wstring &iName)
|
|
|
|
{
|
|
|
|
m_game.accessPlayer(iPlayerId).setName(iName);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::setPlayerTableNb(unsigned iPlayerId, unsigned iTableNb)
|
|
|
|
{
|
|
|
|
m_game.accessPlayer(iPlayerId).setTableNb(iTableNb);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
bool PublicGame::hasPlayed(unsigned int player) const
|
|
|
|
{
|
|
|
|
return m_game.hasPlayed(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PublicGame::start()
|
|
|
|
{
|
|
|
|
m_game.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-30 01:23:45 +01:00
|
|
|
bool PublicGame::isFinished() const
|
|
|
|
{
|
|
|
|
return m_game.isFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
int PublicGame::play(const wstring &iWord, const wstring &iCoord)
|
|
|
|
{
|
|
|
|
return m_game.play(iCoord, iWord);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-24 16:21:50 +01:00
|
|
|
int PublicGame::checkPlayedWord(const wstring &iWord, const wstring &iCoord, Move &oMove) const
|
|
|
|
{
|
2012-12-24 20:32:47 +01:00
|
|
|
return m_game.checkPlayedWord(iCoord, iWord, oMove, true, false);
|
2012-12-24 16:21:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-07-17 01:31:17 +02:00
|
|
|
int PublicGame::computePoints(const wstring &iWord, const wstring &iCoord) const
|
|
|
|
{
|
2012-12-23 19:31:03 +01:00
|
|
|
Move move;
|
|
|
|
int res = m_game.checkPlayedWord(iCoord, iWord, move);
|
2011-07-17 01:31:17 +02:00
|
|
|
if (res > 0)
|
|
|
|
return -res;
|
2012-12-23 19:31:03 +01:00
|
|
|
return move.getScore();
|
2011-07-17 01:31:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
void PublicGame::shuffleRack()
|
|
|
|
{
|
|
|
|
m_game.shuffleRack();
|
|
|
|
}
|
|
|
|
|
2012-01-12 17:43:57 +01:00
|
|
|
|
2013-01-09 18:42:49 +01:00
|
|
|
void PublicGame::reorderRack(const PlayedRack &iRack)
|
|
|
|
{
|
|
|
|
m_game.reorderRack(iRack);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-01-12 17:43:57 +01:00
|
|
|
void PublicGame::setTestRound(const Round &iRound)
|
|
|
|
{
|
2012-02-18 20:22:10 +01:00
|
|
|
m_game.accessBoard().testRound(iRound);
|
2012-01-12 17:43:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::removeTestRound()
|
|
|
|
{
|
2012-02-18 20:22:10 +01:00
|
|
|
m_game.accessBoard().removeTestRound();
|
2012-01-12 17:43:57 +01:00
|
|
|
}
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
/***************************/
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
template <typename T>
|
|
|
|
static T & getTypedGame(Game &iGame)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
T *typedGame = dynamic_cast<T*>(&iGame);
|
|
|
|
if (typedGame == NULL)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
|
|
|
throw GameException("Invalid game type");
|
|
|
|
}
|
2012-03-05 01:27:56 +01:00
|
|
|
return *typedGame;
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
/***************************/
|
|
|
|
|
2008-11-30 21:53:44 +01:00
|
|
|
void PublicGame::trainingSearch()
|
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Training>(m_game).search();
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Results& PublicGame::trainingGetResults() const
|
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
return getTypedGame<Training>(m_game).getResults();
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PublicGame::trainingPlayResult(unsigned int iResultIndex)
|
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
return getTypedGame<Training>(m_game).playResult(iResultIndex);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::trainingSetRackRandom(bool iCheck, RackMode iRackMode)
|
|
|
|
{
|
|
|
|
if (iRackMode == kRACK_NEW)
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Training>(m_game).setRackRandom(iCheck, Game::RACK_NEW);
|
2008-11-30 21:53:44 +01:00
|
|
|
else
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Training>(m_game).setRackRandom(iCheck, Game::RACK_ALL);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-01-24 11:28:20 +01:00
|
|
|
void PublicGame::trainingSetRackManual(bool iCheck, const wstring &iLetters)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Training>(m_game).setRackManual(iCheck, iLetters);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
|
2013-01-06 19:05:30 +01:00
|
|
|
void PublicGame::toppingPlay(const wstring &iWord, const wstring &iCoord, int iElapsed)
|
|
|
|
{
|
|
|
|
getTypedGame<Topping>(m_game).tryWord(iWord, iCoord, iElapsed);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-16 15:51:09 +01:00
|
|
|
void PublicGame::toppingTimeOut(int iElapsed)
|
2013-01-11 20:24:47 +01:00
|
|
|
{
|
2013-01-16 15:51:09 +01:00
|
|
|
getTypedGame<Topping>(m_game).turnTimeOut(iElapsed);
|
2013-01-11 20:24:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::toppingAddPenalty(int iPenalty)
|
|
|
|
{
|
|
|
|
getTypedGame<Topping>(m_game).addPenalty(iPenalty);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-29 18:49:52 +01:00
|
|
|
vector<Move> PublicGame::toppingGetTriedMoves() const
|
|
|
|
{
|
|
|
|
return getTypedGame<Topping>(m_game).getTriedMoves();
|
|
|
|
}
|
|
|
|
|
2013-01-08 15:54:11 +01:00
|
|
|
|
|
|
|
Move PublicGame::toppingGetTopMove() const
|
|
|
|
{
|
|
|
|
return getTypedGame<Topping>(m_game).getTopMove();
|
|
|
|
}
|
|
|
|
|
2012-12-29 18:49:52 +01:00
|
|
|
/***************************/
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
void PublicGame::duplicateSetPlayer(unsigned int p)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Duplicate>(m_game).setPlayer(p);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
void PublicGame::duplicateSetMasterMove(const Move &iMove)
|
|
|
|
{
|
|
|
|
getTypedGame<Duplicate>(m_game).setMasterMove(iMove);
|
|
|
|
}
|
2008-11-30 21:53:44 +01:00
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
const Move & PublicGame::duplicateGetMasterMove() const
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
return getTypedGame<Duplicate>(m_game).getMasterMove();
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
int PublicGame::freeGamePass(const wstring &iToChange)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
return getTypedGame<FreeGame>(m_game).pass(iToChange);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
|
2012-03-11 00:24:21 +01:00
|
|
|
void PublicGame::arbitrationSetRackRandom()
|
|
|
|
{
|
|
|
|
getTypedGame<Arbitration>(m_game).setRackRandom();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::arbitrationSetRackManual(const wstring &iLetters)
|
|
|
|
{
|
|
|
|
getTypedGame<Arbitration>(m_game).setRackManual(iLetters);
|
|
|
|
}
|
|
|
|
|
2012-03-23 08:12:03 +01:00
|
|
|
|
2012-03-17 23:45:47 +01:00
|
|
|
void PublicGame::arbitrationSearch(LimitResults &oResults)
|
2012-03-05 01:27:56 +01:00
|
|
|
{
|
2012-03-17 23:45:47 +01:00
|
|
|
return getTypedGame<Arbitration>(m_game).search(oResults);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
Move PublicGame::arbitrationCheckWord(const wstring &iWord,
|
|
|
|
const wstring &iCoords) const
|
|
|
|
{
|
|
|
|
return getTypedGame<Arbitration>(m_game).checkWord(iWord, iCoords);
|
|
|
|
}
|
|
|
|
|
2012-03-23 08:12:03 +01:00
|
|
|
|
2012-10-05 12:52:42 +02:00
|
|
|
void PublicGame::arbitrationToggleSolo(unsigned iPlayerId)
|
|
|
|
{
|
|
|
|
Arbitration &game = getTypedGame<Arbitration>(m_game);
|
|
|
|
if (game.getSolo(iPlayerId) != 0)
|
|
|
|
game.removeSolo(iPlayerId);
|
|
|
|
else
|
|
|
|
game.setSolo(iPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PublicGame::arbitrationGetSolo(unsigned iPlayerId) const
|
|
|
|
{
|
|
|
|
return getTypedGame<Arbitration>(m_game).getSolo(iPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-23 07:41:16 +01:00
|
|
|
void PublicGame::arbitrationToggleWarning(unsigned iPlayerId)
|
2012-03-05 01:27:56 +01:00
|
|
|
{
|
2012-03-23 07:41:16 +01:00
|
|
|
Arbitration &game = getTypedGame<Arbitration>(m_game);
|
|
|
|
if (game.hasWarning(iPlayerId))
|
|
|
|
game.removeWarning(iPlayerId);
|
|
|
|
else
|
|
|
|
game.addWarning(iPlayerId);
|
|
|
|
}
|
|
|
|
|
2012-03-23 08:12:03 +01:00
|
|
|
|
|
|
|
bool PublicGame::arbitrationHasWarning(unsigned iPlayerId) const
|
|
|
|
{
|
|
|
|
return getTypedGame<Arbitration>(m_game).hasWarning(iPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-20 22:12:42 +02:00
|
|
|
void PublicGame::arbitrationTogglePenalty(unsigned iPlayerId)
|
2012-03-23 08:12:03 +01:00
|
|
|
{
|
|
|
|
Arbitration &game = getTypedGame<Arbitration>(m_game);
|
2012-05-20 22:12:42 +02:00
|
|
|
if (game.getPenalty(iPlayerId) != 0)
|
|
|
|
game.removePenalty(iPlayerId);
|
|
|
|
else
|
|
|
|
game.addPenalty(iPlayerId);
|
2012-03-23 08:12:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PublicGame::arbitrationGetPenalty(unsigned iPlayerId) const
|
|
|
|
{
|
|
|
|
return getTypedGame<Arbitration>(m_game).getPenalty(iPlayerId);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-23 07:41:16 +01:00
|
|
|
void PublicGame::arbitrationAssign(unsigned iPlayerId, const Move &iMove)
|
|
|
|
{
|
|
|
|
getTypedGame<Arbitration>(m_game).assignMove(iPlayerId, iMove);
|
2012-03-05 01:27:56 +01:00
|
|
|
}
|
|
|
|
|
2012-03-23 08:12:03 +01:00
|
|
|
|
2012-03-05 01:27:56 +01:00
|
|
|
void PublicGame::arbitrationFinalizeTurn()
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2012-03-05 01:27:56 +01:00
|
|
|
getTypedGame<Arbitration>(m_game).finalizeTurn();
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
|
2009-11-29 17:01:31 +01:00
|
|
|
PublicGame *PublicGame::load(const string &iFileName, const Dictionary &iDic)
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2009-11-29 17:01:31 +01:00
|
|
|
Game *game = GameFactory::Instance()->load(iFileName, iDic);
|
2008-11-30 21:53:44 +01:00
|
|
|
return new PublicGame(*game);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-11-29 17:01:31 +01:00
|
|
|
void PublicGame::save(const string &iFileName) const
|
2008-11-30 21:53:44 +01:00
|
|
|
{
|
2009-11-29 17:01:31 +01:00
|
|
|
XmlWriter::write(m_game, iFileName);
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/***************************/
|
|
|
|
|
|
|
|
unsigned int PublicGame::getCurrTurn() const
|
|
|
|
{
|
2012-02-19 18:34:37 +01:00
|
|
|
// +1 to have a 1-based index (more user-friendly)
|
|
|
|
return m_game.getNavigation().getCurrTurn() + 1;
|
2008-11-30 21:53:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int PublicGame::getNbTurns() const
|
|
|
|
{
|
|
|
|
return m_game.getNavigation().getNbTurns();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PublicGame::isFirstTurn() const
|
|
|
|
{
|
|
|
|
return m_game.getNavigation().isFirstTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PublicGame::isLastTurn() const
|
|
|
|
{
|
|
|
|
return m_game.getNavigation().isLastTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::firstTurn()
|
|
|
|
{
|
|
|
|
m_game.accessNavigation().firstTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::prevTurn()
|
|
|
|
{
|
|
|
|
m_game.accessNavigation().prevTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::nextTurn()
|
|
|
|
{
|
|
|
|
m_game.accessNavigation().nextTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::lastTurn()
|
|
|
|
{
|
|
|
|
m_game.accessNavigation().lastTurn();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::clearFuture()
|
|
|
|
{
|
|
|
|
m_game.accessNavigation().clearFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PublicGame::printTurns() const
|
|
|
|
{
|
|
|
|
m_game.getNavigation().print();
|
|
|
|
}
|
|
|
|
|