author W. Trevor King Tue, 7 Dec 2010 20:09:36 +0000 (15:09 -0500) committer W. Trevor King Tue, 7 Dec 2010 20:17:02 +0000 (15:17 -0500)
Brute force implementation depends on
`pbot.deck.SevenChooseFiveHand.score()`, so it takes a while if there
are unknown public cards.

 pbotlib/odds.py [new file with mode: 0644] patch | blob

diff --git a/pbotlib/odds.py b/pbotlib/odds.py
new file mode 100644 (file)
index 0000000..6015ea8
--- /dev/null
@@ -0,0 +1,66 @@
+"""Calculate the probability of various poker hands
+"""
+
+from pbot.combinations import xunique_combinations
+from pbot.deck import new_deck, SevenChooseFiveHand
+
+
+class Odds (object):
+    """Calculate the odds of winning given the current game state.
+
+    >>> from pprint import pprint
+    >>> from pbot.deck import unpp_card
+
+    The only thing that can beat 4 aces is a straight flush.  With
+    spaced out public cards, that is impossible, so we only get wins
+    in that case.  The total number of opposing hands is
+    `choose(52-7,2)=990`.
+
+    >>> o = Odds(hole=[unpp_card(c) for c in ['As', 'Ah']],
+    ...          public=[unpp_card(c) for c in ['Ad', 'Ac', 'Ts', '7h', '4d']])
+    >>> pprint(o.results)
+    {'losses': 0, 'ties': 0, 'wins': 990}
+
+    However, with properly aligned public cards, the straight flush is
+    within reach, so we get losses whenever the opposing hole contains
+    `Tc`.  This leaves `52-8=44` options for the other hole card.
+
+    >>> o.public = [unpp_card(c) for c in ['Ad', 'Ac', 'Kc', 'Qc', 'Jc']]
+    >>> pprint(o.calculate())
+    {'losses': 44, 'ties': 0, 'wins': 946}
+
+    The odds caclulator will cycle through unknown public cards, but
+    this can be slow.
+
+    >>> o.public.pop()
+    >>> pprint(o.calculate())
+    {'losses': 134, 'ties': 0, 'wins': 47476}
+    """
+    deck = new_deck()
+
+    def __init__(self, hole, public=None):
+        self.hole = hole
+        if public == None:
+            public = []
+        self.public = public
+        self.results = self.calculate()
+
+    def calculate(self):
+        known = self.hole + self.public
+        hidden = [c for c in self.deck if c not in known]
+        remaining = 5 - len(self.public)
+        results = {'wins': 0, 'ties': 0, 'losses': 0}
+        for new_public in xunique_combinations(hidden, remaining):
+            p = self.public + new_public
+            h = [c for c in hidden if c not in p]
+            hand = SevenChooseFiveHand(self.hole + p)
+            for hole in xunique_combinations(hidden, 2):
+                other_hand = SevenChooseFiveHand(hole + p)
+                result = cmp(hand, other_hand)
+                if result == 1:
+                    results['wins'] += 1
+                elif result == 0:
+                    results['ties'] += 1
+                else:
+                    results['losses'] += 1
+        return results