#!/usr/bin/env python """ Poker hand implementation, an extended version of exercise 14, page 380 of Zelle's "Intro Python" Prints lots of tests if run from the command line, including median 'winning' hand of four hands dealt from one deck (which is about a pair of queens); see the end of this file for the output. Here's what you can do with the classes defined in this file. # -- PokerCard -- card = PokerCard(rank, suit) # rank=1..13, suit='c', 'd', 's', or 'h' = PokerCard() # or this way for a random card print card.getRank() # returns 2 (duece) ... 14 (ace); or card.rank print card.getSuit() # returns 'c', 'd', 'h', or 's'; or card.suit print card # e.g. 'Three of Clubs' card1 < card2 # ordering by (i) rank, (ii) suit sort(cards) # sort a list of cards, low to high, in place # -- PokerDeck -- deck = PokerDeck() # a 52-card deck print len(deck) # number of cards left in the deck card = deck.deal_a_card() # return and remove a random card from deck list = deck.get_n_cards(n) # ditto for a list of cards # -- PokerHand -- hand = PokerHand(string) # make hand from e.g. '1 s, 2 h, 3 d, 4 c, 5 s' = PokerHand(cards=list) # ... from a list of PokerCard's = PokerHand(deck=d) # ... from given deck (and remove 'em from deck) hand = PokerHand() # ... from a new deck hand1 < hand2 # using (losing hand < winning hand) sort(hands) # sort a list of hands, low to high, in place print hand # e.g. 'Straight Flush : Queen of Hearts, Jack of Hearts, ...' print hand.category # e.g. 'Two Pair' print hand.description # e.g. 'Sixes over Fours' Jim Mahoney, Nov 11 2010, GPL """ from random import randint # These definitions are used by several # of the classes so I've left 'em here outside of any one class. suits = ('d', 'c', 'h', 's') # i.e. Diamonds, Clubs, Hearts, Spades ranks = range(2, 15) # 2 ... 10, Jack=11, Queen=12, King=13, Ace=14 (high) suit_names = { 'd' : 'Diamonds', 'c' : 'Clubs', 'h' : 'Hearts', 's' : 'Spades', } rank_names = { 2 : 'Two', 3 : 'Three', 4 : 'Four', 5 : 'Five', 6 : 'Six', 7 : 'Seven', 8 : 'Eight', 9 : 'Nine', 10: 'Ten', 11: 'Jack', 12: 'Queen', 13: 'King', 14: 'Ace', } class PokerCard: """ A playing card. Ace is high. """ def __init__(self, rank=False, suit=False): """ PokerCard(rank, suit) rank is 1..10 for Ace...10, and (11,12,13) for jack, queen, king. suits are single lower case letters, ('c', 'd', 'h', 's'). Aces may also be input as rank=14 (one higher than king). PokerCard() returns a random card.""" if rank == 1: # If an ace is input as 1, then rank = 14 # turn it into 14 so it's bigger than king=13 if not rank: rank = randint(2,14) if not suit: suit = suits[randint(0,3)] if rank not in ranks: raise Exception("illegal card rank '"+str(rank)+"'") if suit not in suits: raise Exception("illegal suit '"+str(suit)+"'") self.rank = rank self.suit = suit def getRank(self): """ return rank of card as integer n, 2<=n<=14 """ return self.rank def getSuit(self): """ return suit of card as a letter in ('d', 'c', 'h', 's')""" return self.suit def __str__(self): """ Display card as a string, e.g. 'Ace of Spades'. """ return rank_names[int(self.rank)] + ' of ' + suit_names[self.suit] def __cmp__(card1, card2): """ Compare cards by rank, and within rank by suit. Note that this function automatically enables cards.sort() and all the comparison operators like <, >, ==, etc. """ (rank1, rank2) = (card1.getRank(), card2.getRank()) if rank1 != rank2: return cmp(rank1, rank2) else: return cmp(card1.getSuit(), card2.getSuit()) class PokerDeck: """ 52 playing cards """ def __init__(self): self.cards = [] for rank in ranks: for suit in suits: self.cards.append(PokerCard(rank, suit)) # terse version: # self.cards = [PokerCard(rank,suit) for rank in ranks for suit in suits] def deal_a_card(self): how_many_in_deck = len(self.cards) random_index = randint(0, how_many_in_deck - 1) card = self.cards.pop(random_index) # get it and remove it from list return card # terse version: # return self.cards.pop(randint(0,len(self.cards)-1)) def deal_n_cards(self, n_cards): cards = [] for n in range(n_cards): cards.append( self.deal_a_card() ) return cards # terse version: # return [self.deal_a_card() for i in range(n_cards)] def __len__(self): return len(self.cards) class PokerHand: types = { 'Royal Flush' : 10, # name : value ; higher is better 'Straight Flush' : 9, 'Four of a Kind' : 8, 'Full House' : 7, 'Flush' : 6, 'Straight' : 5, 'Three of a Kind' : 4, 'Two Pair' : 3, 'Pair' : 2, 'High' : 1 # e.g. 'King high' } """ Five playing cards with sorting by which hand wins at poker. """ def __init__(self, string='', cards=[], deck=False): """ Create a hand from either 1) a string like '1 s, 1 d, 1 c', or 2) a list of PokerCard's, or 3) 5 cards taken from the given deck 3) create a random hand if none of those specified. """ self.cards = [] if string: for rank_suit in string.split(','): # e.g. rank_suit = '1 s' rank_suit_tuple = rank_suit.split() # e.g. ('1', 's') if len(rank_suit_tuple)==2: (rank, suit) = rank_suit_tuple # e.g. rank='1', suit='s' self.cards.append( PokerCard(int(rank), suit) ) elif cards: for card in cards: self.cards.append(card) elif deck: for i in range(5): self.cards.append( deck.deal_a_card() ) else: deck = PokerDeck() for i in range(5): self.cards.append( deck.deal_a_card() ) self.cards.sort() # low to high self.cards.reverse() # high to low self.calculate_frequencies() self.calculate_type() def __str__(self): result = '' for card in self.cards: result += str(card) + ", " return self.description() + ' : ' + result[:-2] def is_flush(self): suit = self.cards[0].getSuit() for card in self.cards: if card.getSuit() != suit: return False return True def is_straight(self): high = self.cards[0].getRank() if (high == 14 and self.cards[1].getRank() != 13): high = 6 for i in range(1,5): if self.cards[i].getRank() != high - i: return False return True def pluralFreq(self, which): """ Return plural form of a card rank, e.g. 'Sixes' or 'Aces'. which is 0, 1, ... for rank with most duplicates, 2nd most, etc """ name = rank_names[self.freq[which][1]] if name == 'Six': return 'Sixes' else: return name + 's' def description(self): """ Return string description given self.type and self.freq """ if self.type == 'Four of a Kind': return 'Four ' + self.pluralFreq(0) elif self.type == 'Three of a Kind': return 'Three ' + self.pluralFreq(0) elif self.type == 'Pair': return 'Two ' + self.pluralFreq(0) elif self.type == 'Two Pair': return self.pluralFreq(0) + ' over ' + self.pluralFreq(1) elif self.type == 'High': return rank_names[self.cards[0].getRank()] + ' High' else: return self.type def hand_value(self): return PokerHand.types[self.type] def calculate_type(self): """ Return and store self.type string describing type of hand. self.freq must be already set via calculate_frequencies. """ is_flush = self.is_flush(); is_straight = self.is_straight(); if (is_flush and is_straight): self.type = 'Royal Flush' elif self.freq[0][0] == 4: self.type = 'Four of a Kind' elif self.freq[0][0] == 3 and self.freq[1][0] == 2: self.type = 'Full House' elif is_flush: self.type = 'Flush' elif is_straight: self.type = 'Straight' elif self.freq[0][0] == 3: self.type = 'Three of a Kind' elif self.freq[0][0] == 2 and self.freq[1][0] == 2: self.type = 'Two Pair' elif self.freq[0][0] == 2: self.type = 'Pair' else: self.type = 'High' return self.type def calculate_frequencies(self): """ Compute (frequency, rank) pairs, sorted by highest frequency. Save result in self.freq and return it. """ # Here's how it works: # A five, a ten, and three aces will get grouped into # (a) frequencies = { 10:1, 5:1, 1:3 } # rank: frequency # (b) freq.items converts to [ (10,1), (5,1), (1,3) ] # (c) swapping each tuple becomes [ (1,10), (1,5), (3,1) ] # (d) sorting low-to-high and reversing : [ (3,1), (1,5), (1,10) ] # which means "3 aces, 1 five, 1 ten". freq = {} for card in self.cards: rank = card.getRank() freq[rank] = freq.get(rank,0) + 1 self.freq = freq.items() for i in range(len(self.freq)): self.freq[i] = ( self.freq[i][1], self.freq[i][0] ) self.freq.sort() self.freq.reverse() return self.freq def __cmp__(self, other): # See http://en.wikipedia.org/wiki/Rank_of_hands_%28poker%29 if self.hand_value() != other.hand_value(): return cmp(self.hand_value(), other.hand_value()) else: for i in range(len(self.freq)): if self.freq[i][1] != other.freq[i][1]: return cmp(self.freq[i][1], other.freq[i][1]) return 0 def run_tests(): """ Print out stuff to see if all this works. """ print "\n-- test hand printing from each type --" hands = [ '1 s, 13 s, 12 s, 11 s, 10 s', '8 h, 9 h, 10 h, 11 h, 12 h', '2 s, 3 s, 4 c, 5 c, 6 d', '2 d, 4 d, 6 d, 8 d, 10 d', '3 d, 3 c, 3 h, 12 s, 12 d', '3 c, 4 h, 7 h, 7 d, 7 c', '10 h, 10 d, 12 h, 12 s, 1 c', '6 h, 6 d, 10 c, 12 s, 7 h', '1 d, 2 d, 4 h, 5 d, 8 c', '1 d, 5 d, 4 c, 3 h, 2 d', ] for hand in hands: print PokerHand(string=hand) # print "\n-- test hand ordering --" two_hands = [ ('6 h, 6 d, 10 s, 11 c, 12 d', '4 h, 4 d, 10 c, 11 d, 1 d' ), ('6 h, 6 d, 4 h, 4 d, 3 s', '6 s, 6 c, 4 s, 4 c, 2 s' ), ('6 h, 5 c, 4 h, 3 h, 2 h', '6 d, 5 d, 4 d, 3 c, 2 d' ), ('1 h, 2 h, 3 c, 4 h, 5 h', '6 h, 6 d, 6 c, 6 s, 10 c'), ('1 h, 2 h, 3 h, 4 h, 5 h', '6 h, 6 d, 6 c, 6 s, 10 c'), ] for two_hand in two_hands: first = PokerHand(string=two_hand[0]) second = PokerHand(string=two_hand[1]) if first > second: comparison = "\n beats\n" elif first < second: comparison = "\n loses to\n" else: comparison = "\n ties\n" print str(first)+comparison+" "+str(second) # n_random = 10 print "\n-- test", n_random, "random hands --" for i in range(n_random): print PokerHand() # print "\n-- test 10 hands from the same deck --" deck = PokerDeck() hands = [] for i in range(10): hands.append(PokerHand(deck=deck)) hands.sort() hands.reverse() print "After dealing there are", len(deck), "cards left in the deck." print "Best to worst hands:" for hand in hands: print hand # n_many = 10000 print "\n-- Generating", n_many, "hands and four-hand games ..." hands = [] winning_hands = [] for i in range(n_many): hands.append(PokerHand()) four_hands = [] deck = PokerDeck() for j in range(4): four_hands.append(PokerHand(deck=deck)) four_hands.sort() winning_hands.append(four_hands[-1]) if i % 1000 == 0: print " i = ",i print "done. Sorting..." hands.sort() hands.reverse() print "best:", hands[0] print "median:", hands[n_many/2] print "worst:", hands[-1] winning_hands.sort() winning_hands.reverse() print "median winner of four hands:" print " ", winning_hands[n_many/2] if __name__ == '__main__': run_tests() """ ========= output ===================================== $ ./poker.py -- test hand printing from each type -- Royal Flush : Ace of Spades, King of Spades, Queen of Spades, Jack of Spades, Ten of Spades Straight Flush : Queen of Hearts, Jack of Hearts, Ten of Hearts, Nine of Hearts, Eight of Hearts Straight : Six of Diamonds, Five of Clubs, Four of Clubs, Three of Spades, Two of Spades Flush : Ten of Diamonds, Eight of Diamonds, Six of Diamonds, Four of Diamonds, Two of Diamonds Full House : Queen of Spades, Queen of Diamonds, Three of Hearts, Three of Diamonds, Three of Clubs Three Threes : Seven of Hearts, Seven of Diamonds, Seven of Clubs, Four of Hearts, Three of Clubs Queens over Tens : Ace of Clubs, Queen of Spades, Queen of Hearts, Ten of Hearts, Ten of Diamonds Pair of Sixes : Queen of Spades, Ten of Clubs, Seven of Hearts, Six of Hearts, Six of Diamonds Ace High : Ace of Diamonds, Eight of Clubs, Five of Diamonds, Four of Hearts, Two of Diamonds Straight : Ace of Diamonds, Five of Diamonds, Four of Clubs, Three of Hearts, Two of Diamonds -- test hand ordering -- Pair of Sixes : Queen of Diamonds, Jack of Clubs, Ten of Spades, Six of Hearts, Six of Diamonds beats Pair of Fours : Ace of Diamonds, Jack of Diamonds, Ten of Clubs, Four of Hearts, Four of Diamonds Sixs over Fours : Six of Hearts, Six of Diamonds, Four of Hearts, Four of Diamonds, Three of Spades beats Sixs over Fours : Six of Spades, Six of Clubs, Four of Spades, Four of Clubs, Two of Spades Straight : Six of Hearts, Five of Clubs, Four of Hearts, Three of Hearts, Two of Hearts ties Straight : Six of Diamonds, Five of Diamonds, Four of Diamonds, Three of Clubs, Two of Diamonds Straight : Ace of Hearts, Five of Hearts, Four of Hearts, Three of Clubs, Two of Hearts loses to Four of a Kind : Ten of Clubs, Six of Spades, Six of Hearts, Six of Diamonds, Six of Clubs Straight Flush : Ace of Hearts, Five of Hearts, Four of Hearts, Three of Hearts, Two of Hearts beats Four of a Kind : Ten of Clubs, Six of Spades, Six of Hearts, Six of Diamonds, Six of Clubs -- test 10 random hands -- Queen High : Queen of Spades, Jack of Hearts, Nine of Spades, Five of Hearts, Two of Clubs Pair of Nines : Nine of Hearts, Nine of Diamonds, Eight of Spades, Four of Diamonds, Three of Clubs Pair of Sixes : King of Diamonds, Queen of Clubs, Six of Spades, Six of Diamonds, Four of Diamonds Queen High : Queen of Clubs, Ten of Hearts, Nine of Spades, Five of Spades, Three of Hearts King High : King of Hearts, Jack of Clubs, Ten of Diamonds, Five of Spades, Two of Clubs Ace High : Ace of Clubs, King of Clubs, Queen of Hearts, Nine of Spades, Eight of Clubs Pair of Jacks : King of Hearts, Jack of Diamonds, Jack of Clubs, Ten of Spades, Four of Hearts King High : King of Clubs, Queen of Spades, Seven of Diamonds, Five of Clubs, Four of Diamonds Jack High : Jack of Spades, Ten of Hearts, Nine of Spades, Five of Hearts, Four of Diamonds Jack High : Jack of Hearts, Ten of Hearts, Six of Diamonds, Three of Spades, Two of Spades -- test 10 hands from the same deck -- After dealing there are 2 cards left in the deck. Best to worst hands: Pair of Jacks : Queen of Hearts, Jack of Hearts, Jack of Diamonds, Seven of Clubs, Four of Spades Pair of Nines : Ace of Clubs, Nine of Hearts, Nine of Diamonds, Eight of Spades, Five of Hearts Pair of Fours : Ace of Spades, Ten of Spades, Four of Diamonds, Four of Clubs, Two of Hearts Ace High : Ace of Hearts, King of Spades, Jack of Spades, Seven of Spades, Five of Diamonds Ace High : Ace of Diamonds, Seven of Hearts, Four of Hearts, Three of Spades, Two of Spades King High : King of Clubs, Queen of Clubs, Eight of Clubs, Seven of Diamonds, Three of Hearts King High : King of Diamonds, Ten of Hearts, Eight of Hearts, Six of Diamonds, Two of Diamonds King High : King of Hearts, Nine of Clubs, Eight of Diamonds, Six of Spades, Three of Clubs Queen High : Queen of Diamonds, Ten of Clubs, Nine of Spades, Six of Clubs, Five of Clubs Queen High : Queen of Spades, Ten of Diamonds, Six of Hearts, Five of Spades, Three of Diamonds -- Generating 10000 hands and four-hand games ... i = 0 i = 1000 i = 2000 i = 3000 i = 4000 i = 5000 i = 6000 i = 7000 i = 8000 i = 9000 done. Sorting... best: Four of a Kind : Ace of Spades, Ace of Hearts, Ace of Diamonds, Ace of Clubs, Jack of Clubs median: Ace High : Ace of Spades, King of Hearts, Queen of Clubs, Jack of Spades, Seven of Diamonds worst: Seven High : Seven of Clubs, Five of Hearts, Four of Spades, Three of Clubs, Two of Clubs median winner of four hands: Pair of Queens : Queen of Hearts, Queen of Diamonds, Jack of Spades, Seven of Hearts, Six of Diamonds """