import operator import random class InvalidCard(Exception): """Invalid Card Exception Thrown if an invalid face value or suit is supplied for a card """ pass class InvalidHand(Exception): """Invalid Hand Exception Thrown if the hand being created includes zero or more than 5 cards """ pass def unique_combinations(items, n): if n==0: yield [] else: for i in xrange(len(items)): for cc in unique_combinations(items[i+1:],n-1): yield [items[i]]+cc class Card: """Represents a single poker playing card""" VALUES = {'T': 10, 'J': 11, 'Q': 12, 'K': 13, 'A': 14} SUITS = {'Hearts': 'H', 'Diamonds': 'D', 'Clubs': 'C', 'Spades': 'S'} def __init__(self, string): self.value = string[0] if self.value in '23456789': self.value = int(self.value) else: try: self.value = Card.VALUES[self.value] except KeyError as e: raise InvalidCard('Invalid value: {0}'.format(string)) self.suit = string[1] if not self.suit in Card.SUITS.values(): raise InvalidCard('Invalid suit: {0}'.format(string)) def __cmp__(self, other): """Compare hand values Compares this card's value with another. Used for sorting. """ return cmp(self.value, other.value) def __repr__(self): """Builds a string representation of the hand""" val = self.value for k, v in Card.VALUES.iteritems(): if self.value == v: val = k return str.format('{0}{1}', val, self.suit) class Deck(): def __init__(self): self.__cards = [] for suit in Card.SUITS.values(): for i in range(2,15): if i >= 10: for k, v in Card.VALUES.iteritems(): if i == v: value = k else: value = i self.__cards.append(Card('{0}{1}'.format(value, suit))) def shuffle(self): random.shuffle(self.__cards) def cards(self): return self.__cards def deal(self, n=1, players=[]): if players: for i in range(n): for player in players: player.add_card(self.__cards.pop()) else: cards = [] for i in range(n): cards.append(self.__cards.pop()) return cards class Hand: HIGH_CARD = 0 ONE_PAIR = 1 TWO_PAIRS = 2 THREE_OF_A_KIND = 3 STRAIGHT = 4 FLUSH = 5 FULL_HOUSE = 6 FOUR_OF_A_KIND = 7 STRAIGHT_FLUSH = 8 ROYAL_FLUSH = 9 RANKS = [ 'High Card', 'One Pair', 'Two Pairs', 'Three of a Kind', 'Straight', 'Flush', 'Full House', 'Four of a Kind', 'Straight Flush', 'Royal Flush' ] def __init__(self, cards): if len(cards) < 1 or len(cards) > 5: raise InvalidHand(cards) self.__rank = None self.__cards = sorted([Card(c) for c in cards], reverse=True) self.__values = [] self.rank() def __repr__(self): """Builds a string representation of the hand""" return str.format("Cards: {0} Rank: '{1}' Values: {2}", self.__cards, Hand.RANKS[self.rank()], self.values()) def rank(self): """Get the hand rank Determines the rank of the poker hand if it has not already been computed, and returns it. """ if self.__rank: return self.__rank flush = True straight = False last = None merged = {} for c in self.__cards: if last: if flush and c.suit != last.suit: flush = False last = c if c.value in merged: merged[c.value] = merged[c.value] + 1 else: merged[c.value] = 1 if (len(merged)) == 5: # All unique cards, check for a straight if self.__cards[0].value - self.__cards[4].value == 4: straight = True if self.__cards[4].value == 2 and self.__cards[1].value == 5 and self.__cards[0].value == 14: straight = True # Set the value of the ace to 1 and resort so hand comparisons work correctly self.__cards[0].value = 1 self.__cards = sorted(self.__cards, reverse=True) if straight and flush: if self.__cards[0].value == 14: self.__rank = Hand.ROYAL_FLUSH else: self.__rank = Hand.STRAIGHT_FLUSH elif flush: self.__rank = Hand.FLUSH elif straight: self.__rank = Hand.STRAIGHT else: self.__rank = Hand.HIGH_CARD self.__values = [c.value for c in self.__cards] else: multiples = [m for m in sorted(merged.items(), key = operator.itemgetter(1), reverse = True) if m[1] > 1] if len(multiples) > 1: if multiples[0][1] == multiples[1][1]: self.__rank = Hand.TWO_PAIRS else: self.__rank = Hand.FULL_HOUSE elif multiples: if multiples[0][1] > 3: self.__rank = Hand.FOUR_OF_A_KIND elif multiples[0][1] == 3: self.__rank = Hand.THREE_OF_A_KIND else: self.__rank = Hand.ONE_PAIR mvalues = [m[0] for m in multiples] self.__values = mvalues + [c.value for c in self.__cards if c.value not in mvalues] if not self.__rank: self.__rank = Hand.HIGH_CARD return self.__rank def values(self): """Returns a list of card values for the hand Values for sets are provided first in order of importance and descending strength, followed by all additional card values in order of descending strength. Used for comparison and sorting. Examples: Full House ['5S', '5D', '5H', '8S', '8C'] Values = [5, 8] Two Pair ['9D', '9S', '5H', '5C', 'KH'] Values = [9, 5, 13] Straight ['AS', '2H', '3C', '4C', '5H'] Values = [5, 4, 3, 2, 1] """ if not self.__values: self.rank() return self.__values @staticmethod def create_best_hand(cards): """Create the strongest possible poker hand Using the supplied cards, this will build the strongest possible five- card poker hand. Uses smart hand creation if more than five cards are specified. """ if len(cards) <= 5: return Hand(cards) else: return Hand.create_best_hand_smart(cards) return false @staticmethod def create_best_hand_bruteforce(cards): """Create the strongest possible poker hand Builds every possible poker hand from the supplied set of cards, and returns the strongest result. """ combos = unique_combinations(cards, 5) hands = [Hand(combo) for combo in combos] hands = sorted(hands, reverse=True) return hands[0] @staticmethod def create_best_hand_smart(cards): """Create the strongest possible poker hand Intelligently seeks out the strongest possible poker hand from the supplied set of cards using the following steps: * Find all flushes * Find all straights ** Return best hand present in both flushes and straights, if applicable * Find all sets ** Return best quads with top remaining card ** Find and return best full house ** Find and return best three of a kind with top remaining cards ** Find and return best two pair with top remaining cards ** Find and return best single pair with top remaining cards ** Return top 5 cards """ cards = sorted([Card(c) for c in cards], reverse=True) # Get all flushes flushes = [] for suit in Card.SUITS.values(): suited = [str(c) for c in cards if c.suit == suit] if len(suited) >= 5: combos = unique_combinations(suited, 5) for combo in combos: flushes.append(Hand(combo)) flushes = sorted(flushes, reverse=True) if (flushes and flushes[0].rank() >= Hand.STRAIGHT_FLUSH): # Straight flush! No need to check anything else return flushes[0] #Get all sets merged = {} for c in cards: if c.value in merged: merged[c.value] = merged[c.value] + 1 else: merged[c.value] = 1 multiples = [m for m in sorted(merged.items(), key = operator.itemgetter(1), reverse = True) if m[1] > 1] quads = [c[0] for c in multiples if c[1] == 4] quads = [c for c in cards if c.value in quads] trips = [c[0] for c in multiples if c[1] == 3] trips = [c for c in cards if c.value in trips] pairs = [c[0] for c in multiples if c[1] == 2] pairs = [c for c in cards if c.value in pairs] remaining = [c for c in cards if c.value not in [m[0] for m in multiples]] if quads: h = quads[:4] remaining = [c for c in cards if c.value not in [cc.value for cc in h]][:1] for r in remaining: h.append(r) return Hand([str(c) for c in h]) if trips and pairs: # Get a full house together h = trips[:3] remaining = pairs[:2] for r in remaining: h.append(r) return Hand([str(c) for c in h]) if flushes: # We've already got a flush, return it! return flushes[0] # Look for a straight! mvals = sorted(merged.keys(), reverse=True) for i in range(0, len(mvals) -4, 1): if (mvals[i] - mvals[i + 4]) == 4: # Regular straight h = [[c for c in cards if c.value == v][0] for v in mvals[i:i + 5]] return Hand([str(c) for c in h]) elif 14 in [c.value for c in cards] and mvals[i + 1] == 5 and mvals[i + 4] == 2: # Ace low straight h = [[c for c in cards if c.value == v][0] for v in mvals[i + 1:i + 5]] h.append([c for c in cards if c.value == 14][0]) return Hand([str(c) for c in h]) if trips: h = trips[:3] remaining = [c for c in cards if c.value not in [cc.value for cc in h]][:2] for r in remaining: h.append(r) return Hand([str(c) for c in h]) if pairs: if len(pairs) > 2: h = pairs[:4] remaining = [c for c in cards if c.value not in [cc.value for cc in h]][:1] for r in remaining: h.append(r) return Hand([str(c) for c in h]) else: h = pairs remaining = [c for c in cards if c.value not in [cc.value for cc in h]][:3] for r in remaining: h.append(r) return Hand([str(c) for c in h]) # High card, send the top 5 reverse-sorted cards return Hand([str(c) for c in cards[:5]]) def __cmp__(self, other): """Compare hand rankings Compares this hand with another by first checking rank, and if they are equal in that regard, by their card values. Used for sorting. """ result = cmp(self.rank(), other.rank()) if (result == 0): # Compare hand values for i in range(len(self.values())): result = cmp(self.values()[i], other.values()[i]) if (result != 0): return result return result class Player: def __init__(self, name): self.__name = name self.__hand = None self.__cards = [] self.__community_cards = [] def add_card(self, card, community=False): if community: self.__community_cards.append(card) else: self.__cards.append(card) # Rebuild and re-evaluate hand self.__hand = Hand.create_best_hand([str(c) for c in self.__community_cards + self.__cards]) def discard(self, index): self.__cards.pop(index) self.__hand = Hand.create_best_hand([str(c) for c in self.__community_cards + self.__cards]) def hand(self): return self.__hand def __repr__(self): return '"{0}"\n\tCards: {1}\n\tHand: {2}'.format(self.__name, self.__cards, self.__hand) def __cmp__(self, other): return cmp(self.hand(), other.hand()) if __name__ == '__main__': deck = Deck() deck.shuffle() players = [] for i in range(1,10): players.append(Player('Player {0}'.format(i))) community_cards = [] deck.deal(2, players) deck.deal() community_cards.extend(deck.deal(3)) deck.deal() community_cards.extend(deck.deal()) deck.deal() community_cards.extend(deck.deal()) for player in players: for card in community_cards: player.add_card(card, True) players = sorted(players, reverse=True) print 'Community', community_cards for player in players: print player