""" simplified_poker.py Card, Hand, Deck, and PokerHand classes which implement a simplified version of poker. This is an example of what can be done with classes and lists, using the concepts we've done so far in class. These objects can * generate and print poker hands, * recognize and sort hands which have * no multiple cards, * a pair, * three of a kind, * four of a kind * use suits correctly to break ties The code here does *not* understand two pair, straights, flushes, or full houses. (That's the "simplified" part, eh?) Running it looks like this. $ python simplified_poker.py -- simplified poker -- 10 hands : There are 2 cards left in the deck. 10 poker hands, low to high There are 2 cards left in the deck. Note that * The Ace High in spades beats the Ace High in hearts (since the ordering of suits is alphabetic, i.e. clubs, diamonds, hearts, spades). * The 'pair of fives' is actually two pair, which this code doesn't recognize. Jim M | Nov 2013 """ import random # These definitions are used by several of the classes, # so they're here outside of any one class. suits = ('d', 'c', 'h', 's') # i.e. Diamonds, Clubs, Hearts, Spades ranks = range(2,15) # 2 ... 10, Jack-King is 11-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', } # For "Two of a kind", "Three of a kind", and so on. of_a_kind_names = ('zero', 'one', 'Pair of ', 'Three ', 'Four ') class Card(object): """ A playing card, Ace high. """ def __init__(self, rank=0, suit=''): if rank == 1: rank = 14 # if ace given as 1, make it 14 (higher than king) if rank == 0: rank = random.choice(ranks) if suit == '': suit = random.choice(suits) if rank not in ranks: raise Exception("illegal Card rank '"+str(rank)+"'") if suit not in suits: raise Exception("illegal Card suit '"+str(suit)+"'") self.rank = rank self.suit = 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 using (i) rank and (ii) suit. """ # Notes # (1) this function automatically enables cards.sort() # and all the comparison operators like <, >, ==, etc. """ # (2) The method here uses python's tuple ording, namely # that (4,'h')>(2,'s') and (2,'s')>(2,'c'). # (This could be done with "if" statements, too.) return cmp((card1.rank, card1.suit), (card2.rank, card2.suit)) class Deck(object): """ A deck of 52 playing cards. """ def __init__(self, shuffle=True): self.cards = [] for r in ranks: for s in suits: self.cards.append(Card(rank=r, suit=s)) if shuffle: self.shuffle() def __len__(self): # This implements the Python len() function. return len(self.cards) def __str__(self): return "A deck of {} cards".format(len(self.cards)) def shuffle(self): random.shuffle(self.cards) def deal_one(self): """ Return top (last) card from deck, and remove it from the deck """ return self.cards.pop() def deal(self, n): """ Return list of n cards from removed from top (end) of deck """ card_list = [] for i in range(n): card_list.append(self.deal_one()) return card_list def hand(self, n): """ Return a hand of n (default 5) cards """ return Hand(self.deal(n=5)) def pokerhand(self): return PokerHand(self.deal(n=5)) def pokerhands(self, n): """ Return a list of n PokerHands """ hands = [] for i in range(n): hands.append(self.pokerhand()) return hands class Hand(object): """ A generic hand of cards """ # i.e. not specific to any game. def __init__(self, cards=[]): self.cards = cards self.sort() def sort(self): self.cards.sort() def cards_string(self): """ Return i.e. 'Two of Diamonds, King of Spades' """ result = '' for c in self.cards: result += str(c) + ", " return result[:-2] def __str__(self): return '' class PokerHand(Hand): """ A 5-card poker hand """ def __init__(self, cards=[]): Hand.__init__(self, cards) self.analyze() def __str__(self): return '<' + self.description() + ': ' + self.cards_string() + '>' def count_rank(self, r): """ Return count of how many cards have rank r """ count = 0 for c in self.cards: if c.rank == r: count = count + 1 return count def best_of_a_kind(self): """ Return (n,rank) where n=(1..4) for one to four of a kind """ # By looking at the ranks from lowest to highest, # and using >= in the comparison, the highest "of a kind" # is the one returned. best_count = 0 best_rank = '' for r in ranks: count = self.count_rank(r) if count >= best_count: best_count = count best_rank = r return (best_count, best_rank) def reorder(self): """ Move the "of a kind" cards to the front""" # In other words, if there are two fours, put those cards first. # The approach here is to copy all the cards into two new lists, # and then replace the list of cards with those two lists. # If for example original ranks of self.cards are [10, 8, 8, 4, 2], # after reordering they will be [8, 8, 10, 4, 2], so that # when compared later, the suit of the 8 is used to break ties # with other pairs of eights. best = [] rest = [] for c in self.cards: if c.rank == self.kind_rank: best.append(c) else: rest.append(c) self.cards = best + rest def description(self): """ Return e.g. 'Pair of Twos'""" best_rank = rank_names[self.cards[0].rank] if self.of_a_kind == 1: return best_rank + ' High' # e.g. "Eight High" else: return of_a_kind_names[self.of_a_kind] + best_rank + 's' def analyze(self): """ Determine and remember what sort of hand this is. """ self.cards.reverse() # highest (best) cards first (self.of_a_kind, self.kind_rank) = self.best_of_a_kind() self.reorder() # "of a kind" cards to front self.order = [self.of_a_kind, self.cards] # for __cmp__ def __cmp__(card1, card2): return cmp(card1.order, card2.order) def main(): # Notice # # * how little coding is needed here, since # the object APIs to do all the heavy work, and # # * that several standard python functions # (i.e. len(), str(), .sort()) can be applied # to these newly defined Card, Deck, etc object classes. # print "-- simplified poker --" n_hands = 10 d = Deck() print "{} hands :".format(n_hands) for i in range(n_hands): print " " + str(d.hand(n=5)) print "There are {} cards left in the deck.".format(len(d)) print print "{} poker hands, low to high".format(n_hands) d = Deck() hands = d.pokerhands(n_hands) hands.sort() # uses PokerHand __cmp__ for comparisons for h in hands: print " " + str(h) print "There are {} cards left in the deck.".format(len(d)) print if __name__ == '__main__': main()