/* Euchre - a free as in freedom and as in beer version of the euchre card game Copyright 2002 C Nathan Buckles (nbuckles@bigfoot.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "Debug.hpp" #include "Game.hpp" #include "Options.hpp" #include "ComputerPlayerEasy.hpp" #include "HumanPlayer.hpp" Game::Game() { for (int p = 0; p < Common::PLAYERS_PER_GAME; p++) { itsPlayers[p] = NULL; } itsPaused = 0; itsEventList = NULL; itsRoundCount = 0; } Game::~Game() { g_slist_free(itsEventList); } Common::PlayerPosition Game::getDealer() { return itsDealer; } void Game::addEvent(Event ev) { itsEventList = g_slist_append(itsEventList, (gpointer) ev); } void Game::run() { LOG("enter Game::run()" << endl); /* loop through and process all events in our queue */ while (g_slist_length(itsEventList) != 0) { /* gcc doesn't like conversion from void* to Event so hack around it */ Event ev = (Event) (unsigned int) g_slist_nth_data(itsEventList, 0); itsEventList = g_slist_remove(itsEventList, (gpointer) ev); processEvent(ev); } LOG("exit Game::run()" << endl); } void Game::processEvent(Event ev) { LOG("Enter Game::processEvent with " << ev << endl); switch (ev) { case START: resetForGame(); start(); break; case AUCTION_START: if (itsState != INAUCTION1) { LOG("ERROR: event AUCTION_START called for invalid state " << itsState << endl); } else { auctionStart(); } break; case AUCTION_CONT: if (itsState != INAUCTION1 && itsState != INAUCTION2) { LOG("ERROR: event AUCTION_CONT called for invalid state " << itsState << endl); } else { auctionCont(); } break; case AUCTION_END: if (itsState != AFTERAUCTION1 && itsState != AFTERAUCTION2) { LOG("ERROR: event AUCTION_END called for invalid state " << itsState << endl); } else { auctionEnd(); } break; case DISCARD_START: if (itsState != AFTERAUCTION1) { LOG("ERROR: event DISCARD_STATE called for invalid state " << itsState << endl); } else { discardStart(); } break; case ROUND_START: roundStart(); break; case ROUND_CONT: if (itsState != INROUND) { LOG("ERROR: event ROUND_CONT called for invalid state " << itsState << endl); } else { roundCont(); } break; case ROUND_END: if (itsState != INROUND) { LOG("ERROR: event ROUND_END called for invalid state " << itsState << endl); } else { roundEnd(); } break; case DEAL_END: if (itsState != AFTERROUND) { LOG("ERROR: event DEAL_END called for invalid state " << itsState << endl); } else { dealEnd(); } break; case ALL_PASS: if (itsState != AFTERAUCTION2) { LOG("ERROR: event ALL_PASS called for invalid state " << itsState << endl); } else { allPass(); } break; case GAME_END: if (itsState != AFTERDEAL) { LOG("ERROR: event GAME_END called for invalid state " << itsState << endl); } else { gameEnd(); } break; default: LOG("ERROR: invalid event " << ev << endl); break; } } void Game::resetForGame() { itsDealer = Common::EAST; itsCaller = Common::NOPOS; itsNSPoints = 0; itsEWPoints = 0; getPlayers(); resetForDeal(); } void Game::resetForDeal() { itsState = INIT; itsBid = Common::NOBID; itsNSTricks = 0; itsEWTricks = 0; itsRoundCount = 0; for (int i = 0; i < Common::ROUNDS_PER_GAME; i++) { itsRounds[i].reset(); } itsLeader = (Common::PlayerPosition) ((itsDealer + 1) % Common::PLAYERS_PER_GAME); itsTurnToPlay = itsLeader; itsCaller = Common::NOPOS; itsLonerSkipped = Common::NOPOS; Common::setTrump(Card::NoSuit); itsDeck.init(); for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->resetBid(); } } void Game::start() { itsState = INAUCTION1; deal(); addEvent(AUCTION_START); } void Game::auctionStart() { addEvent(AUCTION_CONT); } void Game::auctionCont() { Event nextEvent = AUCTION_CONT; Common::Bid ret; if (itsState == INAUCTION1) { ret = itsPlayers[itsTurnToPlay]->auction1(itsTopCard, itsDealer); } else { if (Options::get()->getStickTheDealer() && itsTurnToPlay == itsDealer) { ret = itsPlayers[itsTurnToPlay]->auction2(itsTopCard, itsOldTopCard, TRUE); } else { ret = itsPlayers[itsTurnToPlay]->auction2(itsTopCard, itsOldTopCard, FALSE); } } /* if interactive wait for AUCTION_CONT event */ if (ret == Common::NOBID && itsPlayers[itsTurnToPlay]->isInteractive()) { return; } /* set the bid for this player */ bidMade(itsTurnToPlay, ret); /* if they bid something then pop out */ if (ret != Common::PASS) { itsCaller = itsTurnToPlay; itsBid = ret; nextEvent = AUCTION_END; } else if (itsTurnToPlay == itsDealer) { /* if this was the last one to bid then got o auction end */ nextEvent = AUCTION_END; } else { /* otherwise move on to the next player */ itsTurnToPlay = (Common::PlayerPosition) ((itsTurnToPlay + 1) % Common::PLAYERS_PER_GAME); } if (nextEvent == AUCTION_END) { if (itsState == INAUCTION1) { itsState = AFTERAUCTION1; } else { itsState = AFTERAUCTION2; } } else { if (Options::get()->getDelayBetweenPlays()) { delayBetweenPlays(); return; } } addEvent(nextEvent); } void Game::auctionEnd() { LOG("enter Game::auctionEnd()" << endl); if (itsBid == Common::NOBID) { if (itsState == AFTERAUCTION1) { itsOldTopCard = itsTopCard; itsState = INAUCTION2; for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->auction1End(itsOldTopCard); itsPlayers[i]->resetBid(); } itsTurnToPlay = itsLeader; addEvent(AUCTION_CONT); } else { addEvent(ALL_PASS); } } else { setBid(); if (itsBid == Common::LONER) { itsLonerSkipped = Common::getPartner(itsCaller); } if (itsState == AFTERAUCTION1) { addEvent(DISCARD_START); } else { addEvent(ROUND_START); } } } void Game::roundStart() { /* handle case where the leader is the partner of the person who called a loner. in that case they will be skipped but we will remember that they led and things will get screwy. */ if (itsLeader == itsLonerSkipped) { itsLeader = (Common::PlayerPosition) ((itsLeader + 1) % Common::PLAYERS_PER_GAME); } itsTurnToPlay = itsLeader; itsState = INROUND; itsRoundLeader[itsRoundCount] = itsLeader; addEvent(ROUND_CONT); } void Game::roundCont() { if (itsTurnToPlay == itsLonerSkipped) { itsTurnToPlay = (Common::PlayerPosition) ((itsTurnToPlay + 1) % Common::PLAYERS_PER_GAME); if (itsTurnToPlay == itsLeader) { addEvent(ROUND_END); } else { addEvent(ROUND_CONT); } return; } Card t = itsPlayers[itsTurnToPlay]->getCard(itsRounds[itsRoundCount], itsLeader); if (t.getSuit() == Card::NoSuit && itsPlayers[itsTurnToPlay]->isInteractive()) { return; } cardPlayed(itsTurnToPlay, t); itsTurnToPlay = (Common::PlayerPosition) ((itsTurnToPlay + 1) % Common::PLAYERS_PER_GAME); if (itsTurnToPlay == itsLeader) { addEvent(ROUND_END); return; } if (Options::get()->getDelayBetweenPlays()) { /* for interactive players only delay if we would be skipping this player */ if (itsPlayers[itsTurnToPlay]->isInteractive()) { if (itsTurnToPlay == itsLonerSkipped) { delayBetweenPlays(); return; } } else { /* always delay */ delayBetweenPlays(); return; } } addEvent(ROUND_CONT); } void Game::roundEnd() { itsState = AFTERROUND; for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->finishRound(itsRounds[itsRoundCount], itsLeader); } itsLeader = itsRounds[itsRoundCount].getWinner(itsLeader, Common::getTrump()); updateTrickCounts(itsLeader); itsRoundWinner[itsRoundCount] = itsLeader; if (isDealOver()) { addEvent(DEAL_END); } else { addEvent(ROUND_START); } } int Game::isDealOver() { itsRoundCount++; /* if this was the last round */ if (itsRoundCount == Common::ROUNDS_PER_GAME) { return 1; } /* if configured for auto deal end then end the deal early if somebody has already won */ if (Options::get()->getAutoDealEnd()) { /* there is no way to win before the third trick */ if (itsRoundCount < 3) { return 0; } /* get the number of tricks the caller and the opponent have */ int callerTricks; int otherTricks; if (itsCaller == Common::NORTH || itsCaller == Common::SOUTH) { callerTricks = itsNSTricks; otherTricks = itsEWTricks; } else { callerTricks = itsEWTricks; otherTricks = itsNSTricks; } LOG("callerTricks is " << callerTricks << " otherTricks is " << otherTricks << endl); /* if the opponents have more than this then it's a set already */ int other_max_tricks = (Common::ROUNDS_PER_GAME - Options::get()->getTricksForOnePoint()); if (otherTricks > other_max_tricks) { LOG("end game early because of a set" << endl); return 1; } /* if the caller's have enough for one point and the opponents have at least one then it's a one point game already */ if (callerTricks >= Options::get()->getTricksForOnePoint() && otherTricks > 0) { LOG("end game early because of a one point win" << endl); return 1; } } return 0; } void Game::updateTrickCounts(Common::PlayerPosition theWinner) { if (theWinner == Common::NORTH || theWinner == Common::SOUTH) { itsNSTricks++; } else { itsEWTricks++; } } void Game::dealEnd() { int callerTricks, otherTricks; if (itsCaller == Common::NORTH || itsCaller == Common::SOUTH) { callerTricks = itsNSTricks; otherTricks = itsEWTricks; } else { callerTricks = itsEWTricks; otherTricks = itsNSTricks; } if (callerTricks == Common::CARDS_PER_HAND) { if (itsBid == Common::LONER) { updateLonerPoints(); } else { updateTakeAllPoints(); } } else if (callerTricks >= Options::get()->getTricksForOnePoint()) { updateTakeEnufPoints(); } else { updateSetPoints(); } for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->finishDeal(itsNSPoints, itsEWPoints); } itsDealer = (Common::PlayerPosition) ((itsDealer + 1) % Common::PLAYERS_PER_GAME); itsState = AFTERDEAL; addEvent(GAME_END); } void Game::gameEnd() { if (itsNSPoints >= Options::get()->getPointsForGame() || itsEWPoints >= Options::get()->getPointsForGame()) { gameEndForReal(); } else { resetForDeal(); start(); } } void Game::gameEndForReal() { LOG("game over" << endl); } Player* Game::getPlayer(Common::PlayerPosition pos) { if (pos < Common::NORTH || pos >= Common::WEST) { return NULL; } return itsPlayers[pos]; } void Game::getPlayers() { LOG("in Game::getPlayers" << endl); for (int p = 0; p < Common::PLAYERS_PER_GAME; p++) { if (itsPlayers[p] != NULL) { delete itsPlayers[p]; } } itsPlayers[Common::NORTH] = new ComputerPlayerEasy(Common::NORTH); itsPlayers[Common::EAST] = new ComputerPlayerEasy(Common::EAST); itsPlayers[Common::SOUTH] = new HumanPlayer(Common::SOUTH); itsPlayers[Common::WEST] = new ComputerPlayerEasy(Common::WEST); } void Game::deal() { Hand theHands[Common::PLAYERS_PER_GAME]; for (int c = 0; c < Common::CARDS_PER_HAND; c++) { for (int p = 0; p < Common::PLAYERS_PER_GAME; p++) { theHands[p].setCard(c, itsDeck.removeCard()); } } for (int p = 0; p < Common::PLAYERS_PER_GAME; p++) { LOG("1 " << theHands[p] << endl); itsPlayers[p]->setHand(theHands[p]); LOG("2 " << itsPlayers[p]->getHand() << endl); } /* set potential trump */ setTopCard(); } void Game::setTopCard() { itsTopCard = itsDeck.removeCard(); } int Game::isPaused() { return itsPaused; } void Game::delayBetweenPlays() { sleep(1); addEvent(ROUND_CONT); } void Game::pause() { itsPaused = 1; switch (Options::get()->getDelayMode()) { case Options::NODELAY: break; case Options::SDELAY: sleep(2); break; case Options::MDELAY: sleep(5); break; case Options::LDELAY: sleep(10); break; case Options::PAUSE: return; } /* if we make it to here then we have finished the pause so move on with life */ addEvent(PAUSE_END); } void Game::unpause() { itsPaused = 0; } void Game::updateLonerPoints() { updatePoints(itsCaller, 4); } void Game::updateTakeAllPoints() { updatePoints(itsCaller, 2); } void Game::updateTakeEnufPoints() { updatePoints(itsCaller, 1); } void Game::updateSetPoints() { Common::PlayerPosition notCaller = (Common::PlayerPosition) ((itsCaller + 1) % Common::PLAYERS_PER_GAME); updatePoints(notCaller, 2); } void Game::updatePoints(Common::PlayerPosition whosePoints, int howMuch) { int* points; if (whosePoints == Common::NORTH || whosePoints == Common::SOUTH) { points = &itsNSPoints; } else { points = &itsEWPoints; } *points += howMuch; } void Game::discardStart() { itsState = INDISCARD; Card t = itsPlayers[itsDealer]->discard(itsTopCard); if (t.getSuit() == Card::NoSuit && itsPlayers[itsDealer]->isInteractive()) { return; } else { itsDeck.addCard(t); addEvent(ROUND_START); } } void Game::allPass() { for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->allPass(); } itsDealer = (Common::PlayerPosition) ((itsDealer + 1) % Common::PLAYERS_PER_GAME); itsState = AFTERDEAL; addEvent(GAME_END); } void Game::setBid() { Common::setTrump(itsTopCard.getSuit()); for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { itsPlayers[i]->setBid(itsCaller, itsBid); itsPlayers[i]->setTrump(itsTopCard.getSuit()); } } void Game::bidMade(Common::PlayerPosition who, Common::Bid theBid) {} void Game::cardPlayed(Common::PlayerPosition who, const Card& theCard) { itsRounds[itsRoundCount].setCard(who, theCard); } void Game::setOptions() { } ostream& operator<<(ostream& o, Game::Event e) { switch (e) { case Game::START: o << "START"; break; case Game::AUCTION_START: o << "AUCTION_START"; break; case Game::AUCTION_CONT: o << "AUCTION_CONT"; break; case Game::AUCTION_END: o << "AUCTION_END"; break; case Game::DISCARD_START: o << "DISCARD_START"; break; case Game::ROUND_START: o << "ROUND_START"; break; case Game::ROUND_CONT: o << "ROUND_CONT"; break; case Game::ROUND_END: o << "ROUND_END"; break; case Game::DEAL_END: o << "DEAL_END"; break; case Game::ALL_PASS: o << "ALL_PASS"; break; case Game::GAME_END: o << "GAME_END"; break; case Game::PAUSE_END: o << "PAUSE_END"; break; default: o << "UNKNOWN"; break; } return o; }