From: W. Trevor King Date: Tue, 7 Dec 2010 13:26:48 +0000 (-0500) Subject: Add docstrings and doctests to deck.py + Python cleanups. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=e19e03bc7e52fe2b0121944107925e12bce220c5;p=poker.git Add docstrings and doctests to deck.py + Python cleanups. --- diff --git a/DECK.py b/DECK.py deleted file mode 100644 index cff2f71..0000000 --- a/DECK.py +++ /dev/null @@ -1,152 +0,0 @@ -from random import randint, choice, seed -from combinations import * - -''' -Cards in the deck is stored as (n,m), where n is the rank number -n=[0,12], m=[0,3], with n defined to be the card value and m as the suit. - - Examples: - (0,1) is a two of hearts, - (11,0) is a king of spades, - (12,3) is an Ace of clubs -''' - -DECK = dict().fromkeys(range(52)) -for c in DECK: DECK[c] = (c/4,c%4) - -''' Fisher-Yates shuffle from Knuth ''' -def shuffle(DECK): - for n in range(1,52)[::-1]: - k = randint(0,n-1) - DECK[k], DECK[n] = DECK[n], DECK[k] - -''' -To determine which five card hand is stronger according to standard poker -rules, each hand is given a score and a pair rank. The score cooresponds to -the poker hand (Straight Flush=8, Four of a Kind=7, etc...). The pair score -is used to break ties of score. The pair score creates a set of pairs then -rank sorts within each set. Examples: - -Hand A: 7575K -Hand B: 885A5 - -Pair Score A: [ (2, [7,5]), (1, [K]) ] -Pair Score B: [ (2, [8,5]), (1, [A]) ] - -Hand A: AKQJ2 -Hand B: 2345A - -Pair Score A: [ (1, [A,K,Q,J,2]) ] -Pair Score B: [ (1, [A,5,4,3,2]) ] -''' - -def pairScore(cards): - P = dict().fromkeys(cards,0) - for c in cards: P[c] += 1 - V = sorted(set(P.values()))[::-1] - return [[v,sorted([c for c in set(P) if P[c]==v])[::-1]] for v in V] - -def top_pairScore(P1,P2): # Only compare to cards with same score! - for p1, p2 in zip(P1,P2): - if p1[1]>p2[1]: return 1 - elif p1[1]B[0]: return H1,A - elif A[0] 0: return H1,A - elif pairscore < 0: return H2,B - return H1,A - -def top_hand_score(A,B): - A.score,B.score = handScore(A.score), handScore(B.score) - if A.score[0]>B.score[0]: return 1 - elif A.score[0]>> pp_card((0, 1)) + '2h' + >>> pp_card((8, 3)) + 'Tc' + >>> pp_card((11, 0)) + 'Ks' + """ + s = [] + rank_num = card[0] + 2 + if rank_num >= 10: + rank = FACE_CARDS[rank_num-10].capitalize()[0] + else: + rank = str(rank_num) + + suit = SUITS[card[1]] + return '%s%s' % (rank, suit[0]) + +def unpp_card(card): + """Un-pretty-print a card. + + >>> unpp_card('2h') + (0, 1) + >>> unpp_card('Tc') + (8, 3) + >>> unpp_card('Ks') + (11, 0) + """ + rank,suit = card + try: + rank = int(rank) + except ValueError: + rank = rank.lower() + rank = [i for i,r in enumerate(FACE_CARDS) if r.startswith(rank)][0] + rank += 10 + rank -= 2 + suit = [i for i,s in enumerate(SUITS) if s[0] == suit][0] + return (rank, suit) + +def pp_hand(hand): + """Return a hand in a human readable format (pretty-print) + + >>> pp_hand([(0, 1), (11, 0), (12, 3)]) + '2h Ks Ac' + """ + return ' '.join([pp_card(c) for c in hand]) + + +class FiveCardHand (object): + """Poker hand with five cards. + + To determine which five card hand is stronger according to + standard poker rules, each hand is given a score and a pair + rank. The score cooresponds to the poker hand (Straight Flush=8, + Four of a Kind=7, etc...). The pair score is used to break ties. + The pair score creates a set of pairs then rank sorts within each + set. + + >>> h = FiveCardHand([unpp_card(c) for c in ['7h','5h','7s','5s','Ks']]) + >>> h.score() + (2, [(2, [3, 5]), (1, [11])]) + >>> h = FiveCardHand([unpp_card(c) for c in ['8h','8d','5s','As','5c']]) + >>> h.score() + (2, [(2, [3, 6]), (1, [12])]) + >>> h = FiveCardHand([unpp_card(c) for c in ['Ah','Kd','Qs','Ks','2c']]) + >>> h.score() + (1, [(2, [11]), (1, [0, 10, 12])]) + >>> h = FiveCardHand([unpp_card(c) for c in ['Ah','5d','4s','3s','2c']]) + >>> h.score() + (4, [(1, [0, 1, 2, 3, 12])]) + >>> h.pp_score() + 'straight - Ah 5d 4s 3s 2c' + """ + types = ['x high', 'pair 2', 'double pair 2', 'pair 3', 'straight', + 'flush', 'full house', 'pair 4', 'straight flush'] + + def __init__(self, hand=None): + self.hand = hand + + def __str__(self): + return pp_hand(self.hand) + + def __cmp__(self, other): + return cmp(self.score(), other.score()) + + def _pair_score(self, ranks): + """Returns the pair scores from a list of card ranks in a hand. + + For the sake of clarity, name the pairscore tuple elements + `(paircount,ranks)`. + + >>> h = FiveCardHand() + >>> h._pair_score([5, 3, 5, 3, 11]) + [(2, [3, 5]), (1, [11])] + >>> h._pair_score([8, 8, 5, 8, 5]) + [(3, [8]), (2, [5])] + """ + rank_counts = [(ranks.count(r), r) for r in set(ranks)] + counts = sorted(set([rc[0] for rc in rank_counts]), reverse=True) + return [(c, [rc[1] for rc in rank_counts if rc[0] == c]) + for c in counts] + + def _is_full_house(self, pair_score, **kwargs): + """ + >>> FiveCardHand()._is_full_house([(3, [8]), (2, [5])]) + True + """ + return [p[0] for p in pair_score] == [3, 2] + + def _is_pair_4(self, pair_score, **kwargs): + return [p[0] for p in pair_score] == [4, 1] + + def _is_pair_3(self, pair_score, **kwargs): + return [p[0] for p in pair_score] == [3, 1] + + def _is_double_pair_2(self, pair_score, **kwargs): + return ([p[0] for p in pair_score] == [2, 1] + and len(pair_score[0][1]) == 2) + + def _is_pair_2(self, pair_score, **kwargs): + return ([p[0] for p in pair_score] == [2, 1] + and len(pair_score[0][1]) == 1) + + def _is_flush(self, suits, **kwargs): + return len(set(suits)) == 1 + + def _is_straight(self, ranks, **kwargs): # Handles the low Ace as well + ranks = sorted(ranks) + diff = [(x2-x1) for x1,x2 in zip(ranks, ranks[1:])] + return set(diff) == set([1]) or ranks == [0,1,2,3,12] + + def _is_straight_flush(self, **kwargs): + return self._is_straight(**kwargs) and self._is_flush(**kwargs) + + def _type_index(self, ranks, suits, pair_score): + for i,type in enumerate(reversed(self.types)): + if type == 'x high': continue + is_type = getattr(self, '_is_%s' % type.replace(' ', '_')) + if is_type(ranks=ranks, suits=suits, pair_score=pair_score): + return len(self.types) - i - 1 + return 0 + + def score(self): + if hasattr(self, '_score'): return self._score # return cached + ranks,suits = zip(*self.hand) + pair_score = self._pair_score(ranks) + type_index = self._type_index(ranks, suits, pair_score) + self._score = (type_index, pair_score) # cache + return self._score + + def pp_score(self): + score = self.score() + type = self.types[score[0]] + return '%s - %s' % (type, str(self)) + + +class SevenChooseFiveHand (FiveCardHand): + """Poker hand with seven cards. + + To determine who wins a poker hand each player determines the best + hand they can make within their (7 choose 5) = 21 possible five card + combinations. + + >>> h = SevenChooseFiveHand([unpp_card(c) for c in + ... ['7h','5h','7s','5s','Ks', '7d', 'Jh']]) + >>> h.full_hand + [(5, 1), (3, 1), (5, 0), (3, 0), (11, 0), (5, 2), (9, 1)] + >>> h.hand + [(5, 1), (3, 1), (5, 0), (3, 0), (5, 2)] + >>> h.score() + (6, [(3, [5]), (2, [3])]) + >>> h.pp_score() + 'full house - 7h 5h 7s 5s 7d Ks Jh' + """ + # Prevent a bajillion calls to xunqiue_combinations + _hand_indices = list(xunique_combinations(range(7), 5)) + """List of unique indices selecting 5 cards from a hand of 7. + """ + + def __init__(self, hand): + self.full_hand = hand + self.hand,self.residual = self.high_hand() + + def __str__(self): + return pp_hand(self.hand + self.residual) + + def high_hand(self): + score = hand = residual = None + for indices in self._hand_indices: + h = FiveCardHand([self.full_hand[i] for i in indices]) + if h.score() > score: + residual = [self.full_hand[i] + for i in range(len(self.full_hand)) + if i not in indices] + score = h.score() + hand = h.hand + return (hand, residual)