""" jims_poker.py A python implementation of objects to simlulate poker. See exercise 14 in chap 11 of Zelle's "Intro Python" v3. In places this code is sophisticated for a beginner programmer, so only dig into the details if you are are looking for a challenge. Think of it as an example of what can be done with the ideas that we've been discussing, rather than what I expect you to be able to at this point in your programming development. It 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) 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 : ... print(hand.category) # e.g. 'Two Pair' print(hand.description) # e.g. 'Sixes over Fours' version 1 Nov 7 2006 version 2 Nov 18 2019 - moved some globals (suits, ranks, etc) into PokerCard Tested with Jim Mahoney | cs.marlboro.college | MIT License """ from random import randint def cmp(a, b): """ a simple implementation of python2' cmp() """ # See https://stackoverflow.com/questions/8276983/ return (a > b) - (a < b) class PokerCard: """ A playing card. Ace is high. """ suits = ('d', 'c', 'h', 's') # i.e. Diamonds, Clubs, Hearts, Spades ranks = list(range(2, 15)) # 2 ... 10, Jack=11, Queen=12, King=13, Ace=14 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', } 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 = PokerCard.suits[randint(0,3)] if rank not in PokerCard.ranks: raise Exception("illegal card rank '"+str(rank)+"'") if suit not in PokerCard.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 "{} of {}".format(PokerCard.rank_names[int(self.rank)], PokerCard.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()) # python 3 needs all 6 of these to implement comparisons. (Sigh.) # See stackoverflow.com/questions/8276983/ # why-cant-i-use-the-method-cmp-in-python-3-as-for-python-2 def __eq__(self, other): return self._cmp_(other) == 0 def __ne__(self, other): return self._cmp_(other) != 0 def __gt__(self, other): return self._cmp_(other) > 0 def __lt__(self, other): return self._cmp_(other) < 0 def __ge__(self, other): return self._cmp_(other) >= 0 def __le__(self, other): return self._cmp_(other) <= 0 class PokerDeck: """ 52 playing cards """ def __init__(self): self.cards = [] for rank in PokerCard.ranks: for suit in PokerCard.suits: self.cards.append(PokerCard(rank, suit)) # or just: 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 ' + \ PokerCard.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 = '{}s over {}s'.format( PokerCard.rank_names[self.freq[0][1]], PokerCard.rank_names[self.freq[1][1]]) self.category_value = 3 elif self.freq[0][0] == 2: self.category = 'Pair' name = PokerCard.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 = '{} High'.format( PokerCard.rank_names[self.cards[0].getRank()]) 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 = list(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 # See stackoverflow.com/questions/8276983/ def __eq__(self, other): return self._cmp_(other) == 0 def __ne__(self, other): return self._cmp_(other) != 0 def __gt__(self, other): return self._cmp_(other) > 0 def __lt__(self, other): return self._cmp_(other) < 0 def __ge__(self, other): return self._cmp_(other) >= 0 def __le__(self, other): return self._cmp_(other) <= 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 ===================================== $ python full_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 """