a8_1.py

from PokerHand import PokerHand

class MyPokerHand(PokerHand):
    """Custom PokerHand subclass for computing hand probabilities."""

    def rank_hist(self):
        """Compute a histogram of the ranks (analog to "suit_hist")."""
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    # The "has_*" methods return whether a hand has a
    # particular feature, regardless of the presence
    # of a better feature.  For example, both
    # "has_three_of_a_kind" and "has_full_house" will
    # return True for a hand with a full house.
    # 
    # Note that "has_*" methods assume that the suit
    # and rank histograms have already been computed.

    def has_pair(self):
        """Return whether this hand contains a pair."""
        for count in self.ranks.values():
            if count >= 2:
                return True
        return False

    def has_two_pair(self):
        """Return whether this hand contains two pairs."""
        pair_count = 0
        for count in self.ranks.values():
            if count >= 2:
                pair_count += 1
        # With hands of more than five cards, there may be
        # more than two pairs, so check >= instead of ==.
        return pair_count >= 2

    def has_three_of_a_kind(self):
        """Return whether this hand contains three of a kind."""
        for count in self.ranks.values():
            if count >= 3:
                return True
        return False

    def has_straight(self):
        """Return whether this hand contains 5-card straight,
        including A-K-Q-J-10."""
        # First check for ace-high straight
        found = True
        if 1 not in self.ranks:
            # No ace, can't have ace-high straight
            found = False
        for rank in range(10, 14):
            # Check for 10-J-Q-K
            if rank not in self.ranks:
                found = False
        if found:
            return True

        # Check for king-high straight and down
        for maxRank in range(13, 4, -1):
            found = True
            for decrement in range(5):
                rank = maxRank - decrement
                if rank not in self.ranks:
                    found = False
                    break
            if found:
                return True
        return False

    def has_flush(self):
        """Return whether this hand contains 5-card flush."""
        for count in self.suits.values():
            if count >= 5:
                return True
        return False

    def has_full_house(self):
        """Return whether this hand contains full house."""
        three_count = 0        # number of ranks whose count >= 3
        two_count = 0        # number of ranks whose count == 2
        for count in self.ranks.values():
            if count >= 3:
                three_count += 1
            elif count >= 2:
                two_count += 1
        return (three_count >= 2) or (three_count == 1 and two_count > 0)

    def has_four_of_a_kind(self):
        """Return whether this hand contains four of a kind."""
        for count in self.ranks.values():
            if count >= 4:
                return True
        return False

    def has_straight_flush(self):
        """Return whether this hand contains straigh flush."""
        # Divide up the hand by suits.  If one of the suited
        # hands has a straight, it must be a straight flush.
        # Note how we reuse existing code rather than duplicating.
        suited_hands = [ MyPokerHand(), MyPokerHand(), MyPokerHand(),
                    MyPokerHand() ]
        for card in self.cards:
            suited_hands[card.suit].add_card(card)
        for hand in suited_hands:
            hand.rank_hist()
            if hand.has_straight():
                return True
        return False

    def classify(self):
        """Classify a hand by the best feature it contains."""
        # Compute histograms used by "has_*" methods
        self.rank_hist()
        self.suit_hist()
        # Check for hands in order from best to worst.
        if self.has_straight_flush():
            self.label = "straight flush"
        elif self.has_four_of_a_kind():
            self.label = "four of a kind"
        elif self.has_full_house():
            self.label = "full house"
        elif self.has_flush():
            self.label = "flush"
        elif self.has_straight():
            self.label = "straight"
        elif self.has_three_of_a_kind():
            self.label = "three of a kind"
        elif self.has_two_pair():
            self.label = "two pair"
        elif self.has_pair():
            self.label = "pair"
        else:
            self.label = "high card"

def collect_stats(hand_size=5, sample_size=1000):
    """Collect feature occurrence histogram and return
    as a dictionary whose keys are feature labels and
    whose values are counts.."""
    # "sample_size" needs to be about 10000 for the probabilities
    # to approach those reported in the Wikipedia page.
    from Card import Deck
    stats = {}
    for i in range(sample_size):
        deck = Deck()
        deck.shuffle()
        hand = MyPokerHand()
        deck.move_cards(hand, hand_size)
        hand.classify()
        stats[hand.label] = stats.get(hand.label, 0) + 1
    return stats

def print_stats(stats):
    """Print statistics from a collection run."""
    labels = [
        "straight flush",
        "four of a kind",
        "full house",
        "flush",
        "straight",
        "three of a kind",
        "two pair",
        "pair",
        "high card",
    ]
    width = max([ len(label) for label in labels ])
    format = "%%%ds: %%8.4f" % width
    total = float(sum(stats.values()))
    for label in labels:
        count = stats.get(label, 0)
        pct = count / total * 100.0
        print(format % (label, pct))

if __name__ == "__main__":
    print_stats(collect_stats(sample_size=10000))