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.itervalues():
			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.itervalues():
			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.itervalues():
			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.itervalues():
			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.itervalues():
			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.itervalues():
			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))