+++ /dev/null
-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]<p2[1]: return -1
- return 0
-
-def isFullHouse(P0): return P0==(3,2)
-def isPair4(P0): return P0==(4,1)
-def isPair3(P0): return P0==(3,1)
-def isdoublePair2(P0,P1): return P0==(2,1) and len(P1[0])==2
-def isPair2(P0,P1): return P0==(2,1) and len(P1[0])==1
-def isFlush(suit): return len(set(suit)) == 1
-def isSt(cards) : # Handles the low Ace as well
- cards = sorted(cards)
- diff = [(x2-x1) for x1,x2 in zip(cards,cards[1:])]
- return len([x for x in diff if x==1])==4 or cards==[0,1,2,3,12]
-
-def handScore(H):
- cards,suit = zip(*H)
- P = pairScore(cards)
- P0,P1 = zip(*P)
-
- score = 0
- S,F = isSt(cards), isFlush(suit)
-
- if isPair2(P0,P1) : score = 1
- elif isdoublePair2(P0,P1): score = 2
- elif isPair3(P0) : score = 3
- elif S and not F : score = 4
- elif F and not S : score = 5
- elif isFullHouse(P0) : score = 6
- elif isPair4(P0) : score = 7
- elif S and F : score = 8
- return [score, P]
-
-
-'''
- To determine who wins a poker hand, given two players hole cards, each player
- determines the best hand they can make within their 7 choose 5 = 21 possible five
- card combinations. Then each players best hands are put against each other. 1, -1
- or 0 is returned if player A wins, B wins, or ties respectively.
-'''
-
-# This function prevents call xunqiueCombinations a bajillon times
-HAND_COMBOS = list(xuniqueCombinations(range(7),5))
-def pull_hand(H):
- for L in HAND_COMBOS: yield [H[n] for n in L]
-
-def top_hand_reduce(P1,P2):
- H1, A = P1
- H2, B = P2
- if A[0]>B[0]: return H1,A
- elif A[0]<B[0]: return H2,B
- pairscore = top_pairScore(A[1],B[1])
- if pairscore > 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]<B.score[0]: return -1
- return top_pairScore(A.score[1],B.score[1])
-
-def HOLDEM_score(A, B, board):
- A.score = reduce(top_hand_reduce,[(H,handScore(H)) for H in pull_hand(A.hole+board)])[0]
- B.score = reduce(top_hand_reduce,[(H,handScore(H)) for H in pull_hand(B.hole+board)])[0]
- return top_hand_score(A,B)
-
-# ***************************************
-# Returns the hand in a human readable format (pretty-print)
-def pp_hand(cards):
- S = ''
- for c in cards:
- if c[0]==8: S+='T'
- elif c[0]==9: S+='J'
- elif c[0]==10: S+='Q'
- elif c[0]==11: S+='K'
- elif c[0]==12: S+='A'
- else : S+=str(c[0]+2)
-
- if c[1]==0 : S+='s'
- elif c[1]==1 : S+='d'
- elif c[1]==2 : S+='c'
- else : S+='h'
- S += ' '
- return S
-
-def pp_score(A):
- try:
- S = ''
- if A.score[0] == 0: S = 'HighCard '
- elif A.score[0] == 1: S = 'Pair '
- elif A.score[0] == 2: S = 'TwoKind '
- elif A.score[0] == 3: S = 'ThreeKind '
- elif A.score[0] == 4: S = 'Straight '
- elif A.score[0] == 5: S = 'Flush '
- elif A.score[0] == 6: S = 'FullHouse '
- elif A.score[0] == 7: S = 'FourKind '
- elif A.score[0] == 8: S = 'StFlush '
- S += str(A.score[1])
- return S
- except:
- return "INCOMPLETE"
--- /dev/null
+"""Define a deck of cards, single-player scoring rules, and pretty-printing.
+"""
+
+from combinations import xunique_combinations
+
+
+SUITS = ['spades', 'hearts', 'diamonds', 'clubs']
+"""Ordered list of suits.
+"""
+
+FACE_CARDS = ['ten', 'jack', 'queen', 'king', 'ace']
+"""Ordered list of face cards.
+"""
+
+DECK = dict([(c/4, c%4) for c in range(52)])
+"""Cards in the deck are stored as (n,m), where `n` is the rank number
+`n=[0,12]`, and `m` is the suit `m=[0,3]`.
+
+Examples:
+ (0,1) is a two of hearts,
+ (11,0) is a king of spades,
+ (12,3) is an ace of clubs
+"""
+
+
+def pp_card(card):
+ """Pretty-print a card.
+
+ >>> 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)