Use debian 2.7 only
[fpbd-bostik.git] / pyfpdb / Stove.py
blobd3d909eb770d01853964eb8f092eccd9c4b5e5dc
1 #!/usr/bin/env python
2 # -*- coding: iso-8859-15
4 # stove.py
5 # Simple Hold'em equity calculator
6 # Copyright (C) 2007-2011 Mika Boström <bostik@iki.fi>
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, version 3 of the License.
12 # TODO gettextify usage print
14 import L10n
15 _ = L10n.get_translation()
17 import sys, random
18 import re
19 import pokereval
21 SUITS = ['h', 'd', 's', 'c']
23 CONNECTORS = ['32', '43', '54', '65', '76', '87', '98', 'T9', 'JT', 'QJ', 'KQ', 'AK']
25 ANY = 0
26 SUITED = 1
27 OFFSUIT = 2
29 ev = pokereval.PokerEval()
32 class Stove:
33 def __init__(self):
34 self.hand = None
35 self.board = None
36 self.h_range = None
38 def set_board_with_list(self, board):
39 pass
41 def set_board_string(self, string):
42 board = Board()
44 # Board
45 b = string.strip().split()
46 if len(b) > 4:
47 board.b5 = b[4]
48 if len(b) > 3:
49 board.b4 = b[3]
50 if len(b) > 2:
51 board.b1 = b[0]
52 board.b2 = b[1]
53 board.b3 = b[2]
55 self.board = board
57 def set_hero_cards_string(self, string):
58 # Our pocket cards
59 cc = string.strip().split()
60 c1 = cc[0]
61 c2 = cc[1]
62 pocket_cards = Cards(c1, c2)
63 self.hand = pocket_cards
65 def set_villain_range_string(self, string):
66 # Villain's range
67 h_range = Range()
68 hands_in_range = string.strip().split(',')
69 for h in hands_in_range:
70 _h = h.strip()
71 h_range.expand(expand_hands(_h, self.hand, self.board))
73 self.h_range = h_range
76 class Cards:
77 def __init__(self, c1, c2):
78 self.c1 = c1
79 self.c2 = c2
81 def get(self):
82 return [self.c1, self.c2]
84 class Board:
85 def __init__(self, b1=None, b2=None, b3=None, b4=None, b5=None):
86 self.b1 = b1
87 self.b2 = b2
88 self.b3 = b3
89 self.b4 = b4
90 self.b5 = b5
92 def get(self):
93 b = []
94 if self.b3 is not None:
95 b.append(self.b1)
96 b.append(self.b2)
97 b.append(self.b3)
98 else:
99 b.extend(["__", "__", "__"])
101 if self.b4 is not None:
102 b.append(self.b4)
103 else:
104 b.append("__")
106 if self.b5 is not None:
107 b.append(self.b5)
108 else:
109 b.append("__")
111 return b
113 class Range:
114 def __init__(self):
115 self.__hands = set()
117 def add(self, hand):
118 self.__hands.add(hand)
120 def expand(self, hands):
121 self.__hands.update(set(hands))
123 def get(self):
124 return sorted(self.__hands)
128 class EV:
129 def __init__(self, plays, win, tie, lose):
130 self.n_hands = plays
131 self.n_wins = win
132 self.n_ties = tie
133 self.n_losses = lose
136 class SumEV:
137 def __init__(self):
138 self.n_hands = 0
139 self.n_wins = 0
140 self.n_ties = 0
141 self.n_losses = 0
142 self.output = ""
144 def add(self, ev):
145 self.n_hands += ev.n_hands
146 self.n_wins += ev.n_wins
147 self.n_ties += ev.n_ties
148 self.n_losses += ev.n_losses
150 def show(self, hand, h_range):
151 win_pct = 100 * (float(self.n_wins) / float(self.n_hands))
152 lose_pct = 100 * (float(self.n_losses) / float(self.n_hands))
153 tie_pct = 100 * (float(self.n_ties) / float(self.n_hands))
154 self.output = """
155 Enumerated %d possible plays.
156 Your hand: (%s %s)
157 Against the range: %s
158 Win Lose Tie
159 %5.2f%% %5.2f%% %5.2f%%
160 """ % (self.n_hands, hand.c1, hand.c2, cards_from_range(h_range), win_pct, lose_pct, tie_pct)
162 print self.output
165 # Expands hand abbreviations such as JJ and AK to full hand ranges.
166 # Takes into account cards already known to be in player's hand and/or
167 # board.
168 def expand_hands(abbrev, hand, board):
169 selection = -1
170 known_cards = set()
171 known_cards.update(set([hand.c2, hand.c2]))
172 known_cards.update(set([board.b1, board.b2, board.b3, board.b4, board.b5]))
174 re.search('[2-9TJQKA]{2}(s|o)',abbrev)
176 if re.search('^[2-9TJQKA]{2}(s|o)$',abbrev): #AKs or AKo
177 return standard_expand(abbrev, hand, known_cards)
178 elif re.search('^[2-9TJQKA]{2}(s|o)\+$',abbrev): #76s+ or 76o+
179 return iterative_expand(abbrev, hand, known_cards)
180 #elif: AhXh
181 #elif: Ah6h+A
183 def iterative_expand(abbrev, hand, known_cards):
184 r1 = abbrev[0]
185 r2 = abbrev[1]
187 h_range = []
188 considered = set()
190 idx = CONNECTORS.index('%s%s' % (r1, r2))
192 ltr = abbrev[2]
194 h_range = []
195 for h in CONNECTORS[idx:]:
196 abr = "%s%s" % (h, ltr)
197 h_range += standard_expand(abr, hand, known_cards)
199 return h_range
202 def standard_expand(abbrev, hand, known_cards):
203 # Card ranks may be different
204 r1 = abbrev[0]
205 r2 = abbrev[1]
206 # There may be a specifier: 's' for 'suited'; 'o' for 'off-suit'
207 if len(abbrev) == 3:
208 ltr = abbrev[2]
209 if ltr == 'o':
210 selection = OFFSUIT
211 elif ltr == 's':
212 selection = SUITED
213 else:
214 selection = ANY
216 h_range = []
217 considered = set()
218 for s1 in SUITS:
219 c1 = r1 + s1
220 if c1 in known_cards:
221 continue
222 considered.add(c1)
223 for s2 in SUITS:
224 c2 = r2 + s2
225 if selection == SUITED and s1 != s2:
226 continue
227 elif selection == OFFSUIT and s1 == s2:
228 continue
229 if c2 not in considered and c2 not in known_cards:
230 h_range.append(Cards(c1, c2))
231 return h_range
234 def parse_args(args, container):
235 # args[0] is the path being executed; need 3 more args
236 if len(args) < 4:
237 return False
239 container.set_board_string(args[1])
240 container.set_hero_cards_string(args[2])
241 container.set_villain_range_string(args[3])
243 return True
246 def odds_for_hand(hand1, hand2, board, iterations):
247 res = ev.poker_eval(game='holdem',
248 pockets = [
249 hand1,
250 hand2
252 dead = [],
253 board = board,
254 iterations = iterations
257 plays = int(res['info'][0])
258 eval = res['eval'][0]
260 win = int(eval['winhi'])
261 lose = int(eval['losehi'])
262 tie = int(eval['tiehi'])
264 _ev = EV(plays, win, tie, lose)
265 return _ev
268 def odds_for_range(holder):
269 sev = SumEV()
270 monte_carlo = False
272 # Construct board list
273 b = []
274 board = holder.board
275 if board.b3 is not None:
276 b.extend([board.b1, board.b2, board.b3])
277 else:
278 b.extend(['__', '__', '__'])
279 monte_carlo = True
280 if board.b4 is not None:
281 b.append(board.b4)
282 else:
283 b.append("__")
284 if board.b5 is not None:
285 b.append(board.b5)
286 else:
287 b.append("__")
289 if monte_carlo:
290 print _('No board given. Using Monte-Carlo simulation...')
291 iters = random.randint(25000, 125000)
292 else:
293 iters = -1
294 for h in holder.h_range.get():
295 e = odds_for_hand(
296 [holder.hand.c1, holder.hand.c2],
297 [h.c1, h.c2],
299 iterations=iters
301 sev.add(e)
303 sev.show(holder.hand, holder.h_range.get())
304 return sev
306 def usage(me):
307 print """Texas Hold'Em odds calculator
308 Calculates odds against a range of hands.
310 To use: %s '<board cards>' '<your hand>' '<opponent's range>' [...]
312 Separate cards with space.
313 Separate hands in range with commas.
314 """ % me
316 def cards_from_range(h_range):
317 s = '{'
318 for h in h_range:
319 if h.c1 == '__' and h.c2 == '__':
320 s += 'random, '
321 else:
322 s += '%s%s, ' % (h.c1, h.c2)
323 s = s.rstrip(', ')
324 s += '}'
325 return s
327 def main(argv=None):
328 stove = Stove()
329 if not parse_args(sys.argv, stove):
330 usage(sys.argv[0])
331 sys.exit(2)
332 odds_for_range(stove)
334 if __name__ == '__main__':
335 sys.exit(main())