/* 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 "Debug.hpp" #include "Options.hpp" #include "ComputerPlayerHard.hpp" ComputerPlayerHard::ComputerPlayerHard(Common::PlayerPosition myPos) : ComputerPlayer(myPos) { /* clean up variables */ resetDealVariables(); itsOpponentPos[0] = (Common::PlayerPosition) ((myPos + 1) % Common::PLAYERS_PER_GAME); itsOpponentPos[1] = (Common::PlayerPosition) ((myPos + 3) % Common::PLAYERS_PER_GAME); } ComputerPlayerHard::~ComputerPlayerHard() {} Common::Bid ComputerPlayerHard::auction1(const Card& upCard, Common::PlayerPosition dealer) { Common::Bid ret; /* take into account if we are the dealer that we will have upCard in our hand. This modifies itsHand, so make sure to set it back to copyHand before we return */ Hand copyHand = itsHand; Card copyCard = upCard; if (dealer == itsPos) { discard(copyCard); } /* qualify our hand */ rateHand(upCard.getSuit()); if ((itsHandRating & UTTER_CRAP)) { /* if it's really bad then don't look any further */ ret = Common::PASS; } else { /* if it's at least okay then look for other strengths */ unsigned int power_count = 0; /* adjust minimum power based on aggression */ unsigned int min_power = 2; if (Options::get()->getAIAgg(itsPos) == Options::MAXAGG) { min_power = 1; } /* adjust number of losers based on aggession */ unsigned int max_normal_losers = 3; unsigned int max_loner_losers = 1; if (Options::get()->getAIAgg(itsPos) == Options::MAXAGG) { max_normal_losers = 4; max_loner_losers = 2; } if ((itsHandRating & OFFSUIT_POWER)) { power_count++; } if ((itsHandRating & TRUMP_POWER)) { power_count++; } if ((itsHandRating & VOID_POWER)) { power_count++; } LOG(itsPos << "itsHandRating=0x" << hex << itsHandRating << " power_count=" << dec << power_count << " itsNumLosers=" << itsNumLosers << endl); if ((itsHandRating & DECENT_POINTS)) { /* if we've got some intangibles and we don't have more than three losers then go for it. Basically we assume either our partner is good for one trick or our power will play into a trick somehow. */ if (power_count >= min_power && itsNumLosers <= max_normal_losers) { ret = Common::PICKITUP; } else { ret = Common::PASS; } } else { /* if we've got some intagibles and our loser count is low then go loner, otherwise just go normal */ if (power_count >= min_power && itsNumLosers <= max_loner_losers) { ret = Common::LONER; } else { ret = Common::PICKITUP; } } } /* reset loner to pickitup if we are the human's partner and we are not allowed to go loner */ if (itsBid == Common::LONER && itsPos == Common::NORTH) { if (Options::get()->getPartnerLoner() == 0) { LOG("AI going pickitup instead of loner!" << endl); itsBid = Common::PICKITUP; } } itsHand = copyHand; return ret; } void ComputerPlayerHard::auction1End(const Card& upCard) { itsOutOfPlay = upCard; } Common::Bid ComputerPlayerHard::auction2(Card& yourTrump, const Card& upCard, bool stuck) { Card t; Common::Bid bid; for (int i = Card::Diamonds; i <= Card::Spades; i++) { /* can't bid the suit of the formerly "up" card */ if (upCard.getSuit() == i) { continue; } t.setSuit((Card::Suit)i); /* same logic as auction1 but make sure we aren't the dealer */ bid = ComputerPlayerHard::auction1(t, (Common::PlayerPosition) (itsPos + 1)); if (bid != Common::PASS) { yourTrump = t; break; } } /* if we are stuck with doing it and we didn't find a good suit, then just go with the highest scoring suit. */ if (stuck && bid == Common::PASS) { bid = Common::PICKITUP; yourTrump.setSuit(getStuckTrumpSuit(upCard.getSuit())); } itsBid = bid; return bid; } Card ComputerPlayerHard::discard(Card& newCard) { /* first option is to go void in a non-trump suit */ int lowest_void_index = -1; int lowest_void_value = (Card::JackTrumpVal+1); Card temp; for (int i = 0; i < Common::CARDS_PER_HAND; i++) { temp = itsHand.getCard(i); /* if not trump */ if (! temp.isSuit(Common::getTrump())) { /* and not an ACE */ if (temp.getNumber() != Card::Ace) { /* and there is only one of this suit */ if (itsHand.count(temp.getSuit()) == 1) { /* and this is the lowest so far */ if (temp.getValue() < lowest_void_value) { lowest_void_value = temp.getValue(); lowest_void_index = i; } } } } } /* if we found a low void then get rid of that */ if (lowest_void_index != -1) { itsOutOfPlay = itsHand.getCard(lowest_void_index); itsHand.setCard(lowest_void_index, newCard); return itsOutOfPlay; } /* if we can't go void then discard the lowest card we have unless that card is better than the new card (could happen right...) */ temp = itsHand.getWorstCard(); if (temp.getValue() < newCard.getValue()) { itsOutOfPlay = temp; itsHand.replaceCard(itsOutOfPlay, newCard); } else { itsOutOfPlay = newCard; } return itsOutOfPlay; } void ComputerPlayerHard::setBid(Common::PlayerPosition who, Common::Bid bid) { itsWhoBid = who; itsWhatBid = bid; if (itsWhoBid == itsPos) { if (bid == Common::LONER) { itsPlayStyle = LONEROFF; } else { itsPlayStyle = OFFENSIVE; } } else if (itsWhoBid == itsPartnerPos) { itsPlayStyle = CATALYST; } else { itsPlayStyle = DEFENSIVE; } LOG(itsPos << " setBid called with " << who << ' ' << bid << " and style is now " << itsPlayStyle << endl); } Card ComputerPlayerHard::getCard(const Round& theRound, Common::PlayerPosition whoStarted) { /* number of cards played so far */ int cardsPlayed = (itsPos - whoStarted); if (cardsPlayed < 0) { cardsPlayed += Common::PLAYERS_PER_GAME; } LOG(" " << itsPos << " cardPlayed=" << cardsPlayed << endl); Card ret; if (cardsPlayed == 0) { /* if it's my lead */ ret = getCard1(theRound, whoStarted); } else if (cardsPlayed == 1) { /* if i'm second */ ret = getCard2(theRound, whoStarted); } else if (cardsPlayed == 2) { /* if i'm third */ ret = getCard3(theRound, whoStarted); } else { /* if i'm fourth */ ret = getCard4(theRound, whoStarted); } itsHand.removeCard(ret); return ret; } void ComputerPlayerHard::finishRound(const Round& theRound, Common::PlayerPosition whoStarted) { /* we use this a few times later on ... */ Card::Suit ledSuit = theRound.getSuit(whoStarted); /* save off the round for future use */ itsRounds[itsRoundNum++] = theRound; /* now parse the round looking for partner hints */ Card::Suit pSuit = theRound.getSuit(itsPartnerPos); /* if they didn't follow suit and they didn't play trump then that's a hint we might use later (we only save one of these...) */ if (pSuit != ledSuit && pSuit != Common::getTrump()) { itsPartnerHintSuit = pSuit; } /* remember who (if anyone) is void in the suit led, also count the cards */ for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { if (i == itsPos) { continue; } Card::Suit suit = theRound.getSuit((Common::PlayerPosition) i); if (suit != ledSuit) { itsOthersVoids[i][ledSuit] = 1; } itsCardsPlayed[suit]++; } } void ComputerPlayerHard::finishDeal(int NSPoints, int EWPoints) { resetDealVariables(); } void ComputerPlayerHard::resetDealVariables() { /* reset deal specific stuff */ itsOutOfPlay.setSuit(Card::NoSuit); memset(itsOthersVoids, 0, sizeof(itsOthersVoids)); memset(itsCardsPlayed, 0, sizeof(itsCardsPlayed)); itsRoundNum = 0; itsPartnerHintSuit = Card::NoSuit; } Card ComputerPlayerHard::getCard1(const Round& theRound, Common::PlayerPosition whoStarted) { Card ret; /* if we are going loner then always lead trump */ if (itsPlayStyle == LONEROFF) { ret = itsHand.getBestCard(Common::getTrump()); if (ret.getSuit() != Card::NoSuit) { return ret; } } /* if we have the high trump then lead it unless we know both opponents are void */ ret = itsHand.getBestCard(Common::getTrump()); if (ret.getSuit() != Card::NoSuit && isBestInSuit(ret)) { if (itsOthersVoids[itsOpponentPos[0]][Common::getTrump()] == 0 || itsOthersVoids[itsOpponentPos[1]][Common::getTrump()] == 0) { return ret; } } /* next priority is anything our partner might have hinted at */ Card::Suit pHintSuit = getPartnerHintSuit(); if (pHintSuit != Card::NoSuit) { /* do we have that suit. we are anticipating our partner will win this either with a trump or a high card so play low if we can */ ret = itsHand.getWorstCard(pHintSuit); if (ret.getSuit() == pHintSuit) { LOG(" " << itsPos << " returning " << ret << " because of partner hint" << endl); return ret; } } /* if we know our partner is void in something and we don't know they are void in trump then try to lead that suit */ if (itsOthersVoids[itsPartnerPos][Common::getTrump()] == 0) { for (int i = Card::Diamonds; i <= Card::Spades; i++) { if (itsOthersVoids[itsPartnerPos][i] == 1) { ret = itsHand.getWorstCard((Card::Suit) i); if (ret.getSuit() != Card::NoSuit) { return ret; } } } } /* for all play styles if we are still here then try to play a probable winner */ ret = getProbableNonTrumpWinner(); if (ret.getSuit() != Card::NoSuit) { return ret; } /* doesn't seem to matter what we do as our hand stinks and/or we don't know anything so just duck */ ret = getDuckCard(); return ret; } Card ComputerPlayerHard::getCard2(const Round& theRound, Common::PlayerPosition whoStarted) { Card ret; Card winningCard; Card::Suit suitLed; Common::PlayerPosition winningPos; suitLed = theRound.getSuit(whoStarted); winningPos = theRound.getWinner(whoStarted, Common::getTrump()); winningCard = theRound.getCard(winningPos); if (itsHand.contains(suitLed)) { /* if we need to follow suit then play a winner if we can */ ret = itsHand.getBestCard(suitLed); /* if we can't win then play low */ if (ret.getValue() < winningCard.getValue()) { ret = itsHand.getWorstCard(suitLed); } } else { /* play according to how aggressive we are. if we are not aggressive then try to let our partner win if they have a shot at it */ if (Options::get()->getAIAgg(itsPos) == Options::MINAGG) { if (! isProbableWinner(winningCard, whoStarted, itsPos)) { ret = getDuckCard(); return ret; } } /* so either we are aggressive or the card played is most likely a winner so try to win the trick with the lowest possible trump */ ret = itsHand.getJustBetterCard(winningCard); if (ret.getSuit() == Card::NoSuit) { ret = getDuckCard(); } } return ret; } Card ComputerPlayerHard::getCard3(const Round& theRound, Common::PlayerPosition whoStarted) { Card ret; Card winningCard; Card::Suit suitLed; Common::PlayerPosition winningPos; suitLed = theRound.getSuit(whoStarted); winningPos = theRound.getWinner(whoStarted, Common::getTrump()); winningCard = theRound.getCard(winningPos); if (winningPos == itsPartnerPos) { /* if our partner is winning so far */ if (itsHand.contains(suitLed)) { /* if we have the suit led */ ret = itsHand.getBestCard(suitLed); /* if our card is not at least two better then our partner's then try to duck (ie don't play an Ace on partner's King if we don't have too). */ if ((ret.getValue() - winningCard.getValue()) < 2) { ret = itsHand.getWorstCard(suitLed); } } else { /* if our partner is likely to win then duck */ if (isProbableWinner(winningCard, itsPartnerPos, itsPos)) { ret = getDuckCard(); } else { /* otherwise try to win the trick */ ret = itsHand.getJustBetterCard(winningCard); /* if we can't then duck */ if (ret.getSuit() == Card::NoSuit) { ret = getDuckCard(); } } } } else { /* our partner is not winning but has already played so win if we can */ if (itsHand.contains(suitLed)) { ret = itsHand.getBestCard(suitLed); if (ret.getValue() < winningCard.getValue()) { ret = itsHand.getWorstCard(suitLed); } } else { ret = itsHand.getJustBetterCard(winningCard); if (ret.getSuit() == Card::NoSuit) { ret = getDuckCard(); } } } return ret; } Card ComputerPlayerHard::getCard4(const Round& theRound, Common::PlayerPosition whoStarted) { Card ret; Card winningCard; Card::Suit suitLed; Common::PlayerPosition winningPos; suitLed = theRound.getSuit(whoStarted); winningPos = theRound.getWinner(whoStarted, Common::getTrump()); winningCard = theRound.getCard(winningPos); if (winningPos == itsPartnerPos) { /* if our partner is winning then duck */ if (itsHand.contains(suitLed)) { ret = itsHand.getWorstCard(suitLed); } else { ret = getDuckCard(); } } else { /* win if we can */ if (itsHand.contains(suitLed)) { ret = itsHand.getBestCard(suitLed); if (ret.getValue() < winningCard.getValue()) { ret = itsHand.getWorstCard(suitLed); } } else { ret = itsHand.getJustBetterCard(winningCard); if (ret.getSuit() == Card::NoSuit) { ret = getDuckCard(); } } } return ret; } void ComputerPlayerHard::rateHand(Card::Suit trump) { /* get a base point count */ itsBaseHandValue = itsHand.getValue(trump); /* get a count of trump */ itsTrumpCount = itsHand.count(trump, trump); if (itsBaseHandValue < 18) { /* the 18 comes from a K-K-K-Q-Q hand with no trump, anything better than that is not necessarily crap... */ itsHandRating = UTTER_CRAP; } else if (itsBaseHandValue < 36) { /* the 36 comes from a hand like T-T-T-A-A where the three trumps (T) are low, or from a hand like T-T-T-X-X where two of the three trump are high but the X are low */ itsHandRating = DECENT_POINTS; } else { itsHandRating = GOOD_POINTS; } /* loop through all the cards and for each trump get the value, for each non-trump if it's not an ace then it's a loser */ itsTrumpValue = 0; itsNumLosers = 0; for (int i = 0; i < Common::CARDS_PER_HAND; i++) { Card t = itsHand.getCard(i); if (t.isSuit(trump, trump)) { itsTrumpValue += t.getValue(trump); } else { if (t.getNumber() != Card::Ace) { itsNumLosers++; } } } /* number of trump is important because there are only 7 trumps so if we have 3 or more that's more than our far share. if we have three then make sure we have pretty good ones (ie 3 of Q-K-A-J-J) */ if (itsTrumpCount == 3) { if (itsTrumpValue > 30) { itsHandRating |= TRUMP_POWER; } } else if (itsTrumpCount > 3) { itsHandRating |= TRUMP_POWER; } /* if we have more than one offset ace that's pretty good to so note that, also if we have voids then note that ... */ unsigned int ace_count = 0; unsigned int void_count = 0; for (int i = Card::Diamonds; i <= Card::Spades; i++) { if (i != trump) { if (itsHand.contains((Card::Suit) i, trump)) { if (itsHand.getBestCard((Card::Suit) i).getNumber() == Card::Ace) { ace_count++; } } else { void_count++; } } } if (ace_count >= 2) { itsHandRating |= OFFSUIT_POWER; } if (void_count >= 2) { itsHandRating |= VOID_POWER; } LOG(itsPos << " adj hand is " << itsHand << endl); LOG(itsPos << " hand rating is " << hex << itsHandRating << dec << endl); } /* this method determines if the card passed in is the highest in it's suit (based on the discard card and the cards already played. This should work for an Ace or a Jack of trump as well. It's fairly inneficient but the data set is pretty small so it's still fairly fast */ int ComputerPlayerHard::isBestInSuit(const Card& card) { int ret = 1; int maxval = Card::NonTrumpHigh; Card::Suit suit = card.getSuit(); if (card.isSuit(Common::getTrump())) { maxval = Card::JackTrumpVal; suit = Common::getTrump(); } /* look for each higher card in the previously played rounds, if we find it then move on to the next. if we don't then pop out as we do not know if it's the best or not */ for (int i = (card.getValue()+1); i <= maxval; i++) { int foundit = 0; for (int j = 0; j < itsRoundNum; j++) { for (int k = 0; k < Common::CARDS_PER_ROUND; k++) { Card t = itsRounds[j].getCard((Common::PlayerPosition) k); /* if this is the card we are looking for then set the flag and pop out of the innermost loop */ if (t.isSuit(suit)) { if (t.getValue() == i) { foundit = 1; break; } } } /* end of for each card in round */ /* if we already found the card then pop out and move to the next value */ if (foundit) { break; } } /* end of for each round played */ /* if we did not find the card then return that we didn't find it */ if (foundit == 0) { ret = 0; break; } } /* end of for each higher value */ LOG(" " << itsPos << " return isBestInSuit " << card << " with " << ret << endl); return ret; } Card::Suit ComputerPlayerHard::getPartnerHintSuit() { return itsPartnerHintSuit; } /* this will return 1 if the card passed in is a winner as far as we know. All players except the one passed in are considered */ int ComputerPlayerHard::isProbableWinner(Card& card, Common::PlayerPosition excludePos1, Common::PlayerPosition excludePos2) { Card temp; Card::Suit cardSuit = card.getAdjSuit(); /* if we aren't excluded from winning */ if (excludePos1 != itsPos && excludePos2 != itsPos) { /* then see if we can beat it by following suit or with trump */ if (itsHand.contains(cardSuit)) { temp = itsHand.getBestCard(cardSuit); if (temp.getValue() > card.getValue()) { return 0; } } else { if (itsHand.contains(Common::getTrump())) { return 0; } } } /* now loop through everybody else seeing if we think they can win the trick */ for (int i = 0; i < Common::PLAYERS_PER_GAME; i++) { if (i != excludePos1 && i != excludePos2) { /* if somebody is void and has trump then they will probably use it */ if (itsOthersVoids[i][cardSuit] == 1) { if (itsOthersVoids[i][Common::getTrump()] == 0) { return 0; } } } } /* if nobody is known to be void yet, but if somebody must be ... */ if ((Common::CARDS_PER_SUIT - itsCardsPlayed[cardSuit]) < Common::PLAYERS_PER_GAME) { return 0; } /* finally if this is not the best remaining card in the suit, then guess that it will not win */ if (! isBestInSuit(card)) { return 0; } /* we haven't found any reason to believe it won't win so assume that it will */ LOG(" " << itsPos << " isProbableWinner 1 " << card << endl); return 1; } /* this is a probable winner because it's not trump, so who knows what will happen */ Card ComputerPlayerHard::getProbableNonTrumpWinner() { Card ret; Card temp; int best_score = 0; int best_index = -1; LOG(" " << itsPos << " enter getProbableNonTrumpWinner" << endl); /* loop through each card giving it a rating, if the rating is the highest so far then remember this card */ for (int i = 0; i < Common::CARDS_PER_HAND; i++) { temp = itsHand.getCard(i); /* skip already played cards */ if (temp.getSuit() == Card::NoSuit) { continue; } /* skip trump */ if (temp.isSuit(Common::getTrump())) { continue; } /* if this card is best in it's suit then it's a candidate */ if (isBestInSuit(temp)) { int temp_score = 0; if (itsOthersVoids[itsOpponentPos[0]][temp.getAdjSuit()] == 1 && itsOthersVoids[itsOpponentPos[0]][Common::getTrump()] == 0) { /* if either opponent is void in this suit and is not void in trump then this is not as good of a candidate */ temp_score = 1; } else if (itsOthersVoids[itsOpponentPos[1]][temp.getAdjSuit()] == 1 && itsOthersVoids[itsOpponentPos[1]][Common::getTrump()] == 0) { /* if either opponent is void in this suit and is not void in trump then this is not as good of a candidate */ temp_score = 1; } else if ((Common::CARDS_PER_SUIT - itsCardsPlayed[temp.getAdjSuit()]) < Common::PLAYERS_PER_GAME) { /* if the number of cards left in this suit is less than the number of players than this is not as good of a candidate as somebody must be void, but we don't know who */ temp_score = 1; } else if (itsOthersVoids[itsPartnerPos][temp.getAdjSuit()] == 1 && itsOthersVoids[itsPartnerPos][Common::getTrump()] == 0) { /* if our partner is void in this suit and is not void in trump then this is very good candidate */ temp_score = 3; } else { /* this is a pretty good candidate */ temp_score = 2; } LOG(" " << itsPos << ' ' << temp_score << ' ' << best_score << endl); if (temp_score > best_score) { best_score = temp_score; best_index = i; } } } if (best_index != -1) { ret = itsHand.getCard(best_index); } LOG(" " << itsPos << " exit getProbableNonTrumpWinner with " << ret << endl); return ret; } Card ComputerPlayerHard::getDuckCard() { /* if we have at least one trump then go void in a non-trump suit if we can */ if (itsHand.count(Common::getTrump()) > 0) { int min_index = -1; int min_score = (Card::JackTrumpVal + 1); for (int i = Card::Diamonds; i <= Card::Spades; i++) { if (i != Common::getTrump()) { /* if there is only one of this suit */ if (itsHand.count((Card::Suit) i) == 1) { /* if it is the lowest so far */ int t_score = itsHand.getWorstCard((Card::Suit) i).getValue(); if (t_score < min_score) { min_index = i; min_score = t_score; } } } } if (min_index != -1) { return itsHand.getWorstCard((Card::Suit) min_index); } } /* can't go void so try to give our partner a hint. */ Card good_card = itsHand.getBestNonTrump(); Card bad_card = itsHand.getWorstCard(good_card.getAdjSuit()); /* if we have a good card in a suit and a bad card in a suit, lead the bad card as a hint to our partner that we have a good card waiting */ if (good_card.getSuit() != Card::NoSuit && good_card != bad_card) { return bad_card; } /* nothing special to duck so just return our worst card */ return itsHand.getWorstCard(); }