#!/usr/bin/env python """ Much expanded version of exercise 14, page 380 of Zelle's "Intro Python" Jim Mahoney, Nov 7 2006 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). The descriptions are a bit wordy but hey - you have a big monitor, right? So 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) print card.getSuit() # returns 'c', 'd', 'h', or 's' print card # e.g. 'Three of Clubs' order = card1 < card2 # ordering by (i) rank, (ii) suit sort(cards) # sort a list of cards # -- 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 order = hand1 < hand2 # using (losing hand < winning hand) sort(hands) # sort from losing to winning 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' """ 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(rank2, rank1) else: return cmp(card2.getSuit(), card1.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: """ Five playing cards with sorting by whether they win 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() self.categorize() 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 categorize(self): """ Return string describing what sort of hand it is. Also, save that string as self.category """ self.calculate_frequencies() is_flush = self.is_flush(); is_straight = self.is_straight(); if (is_flush and is_straight): if self.cards[4].getRank() == 10: self.category = 'Royal Flush' self.description = self.category self.category_value = 10 else: self.category = 'Straight Flush' self.description = self.category self.category_value = 9 elif self.freq[0][0] == 4: self.category = 'Four of a Kind' self.description = self.category self.category_value = 8 elif self.freq[0][0] == 3 and self.freq[1][0] == 2: self.category = 'Full House' self.description = self.category self.category_value = 7 elif is_flush: self.category = 'Flush' self.description = self.category self.category_value = 6 elif is_straight: self.category = 'Straight' self.description = self.category self.category_value = 5 elif self.freq[0][0] == 3: self.category = 'Three of a Kind' self.description = 'Three ' + rank_names[self.freq[0][0]] + 's' self.category_value = 4 elif self.freq[0][0] == 2 and self.freq[1][0] == 2: self.category = 'Two Pair' self.description = rank_names[self.freq[0][1]] + 's over ' + \ rank_names[self.freq[1][1]] + 's' self.category_value = 3 elif self.freq[0][0] == 2: self.category = 'Pair' name = rank_names[self.freq[0][1]] if name == 'Six': plural = 'Sixes' else: plural = name + 's' self.description = 'Pair of ' + plural self.category_value = 2 else: self.category = rank_names[self.cards[0].getRank()] + ' High' self.description = self.category self.category_value = 1 return self.category def calculate_frequencies(self): """ Compute (frequency, rank) pairs, sorted by highest frequency. Save result self.freq and self.highest, 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) getitems 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() self.high = self.freq[0][0] def __cmp__(self, other): # See http://en.wikipedia.org/wiki/Rank_of_hands_%28poker%29 if self.category_value != other.category_value: return cmp(self.category_value, other.category_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 category --" 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 category -- 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 $ """