#!/usr/bin/env python # -*- coding: iso-8859-15 # # stove.py # Simple Hold'em equity calculator # Copyright (C) 2007-2011 Mika Boström # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # TODO gettextify usage print import L10n _ = L10n.get_translation() import sys, random import re import pokereval SUITS = ['h', 'd', 's', 'c'] CONNECTORS = ['32', '43', '54', '65', '76', '87', '98', 'T9', 'JT', 'QJ', 'KQ', 'AK'] ANY = 0 SUITED = 1 OFFSUIT = 2 ev = pokereval.PokerEval() class Stove: def __init__(self): self.hand = None self.board = None self.h_range = None def set_board_with_list(self, board): pass def set_board_string(self, string): board = Board() # Board b = string.strip().split() if len(b) > 4: board.b5 = b[4] if len(b) > 3: board.b4 = b[3] if len(b) > 2: board.b1 = b[0] board.b2 = b[1] board.b3 = b[2] self.board = board def set_hero_cards_string(self, string): # Our pocket cards cc = string.strip().split() c1 = cc[0] c2 = cc[1] pocket_cards = Cards(c1, c2) self.hand = pocket_cards def set_villain_range_string(self, string): # Villain's range h_range = Range() hands_in_range = string.strip().split(',') for h in hands_in_range: _h = h.strip() h_range.expand(expand_hands(_h, self.hand, self.board)) self.h_range = h_range class Cards: def __init__(self, c1, c2): self.c1 = c1 self.c2 = c2 def get(self): return [self.c1, self.c2] class Board: def __init__(self, b1=None, b2=None, b3=None, b4=None, b5=None): self.b1 = b1 self.b2 = b2 self.b3 = b3 self.b4 = b4 self.b5 = b5 def get(self): b = [] if self.b3 is not None: b.append(self.b1) b.append(self.b2) b.append(self.b3) else: b.extend(["__", "__", "__"]) if self.b4 is not None: b.append(self.b4) else: b.append("__") if self.b5 is not None: b.append(self.b5) else: b.append("__") return b class Range: def __init__(self): self.__hands = set() def add(self, hand): self.__hands.add(hand) def expand(self, hands): self.__hands.update(set(hands)) def get(self): return sorted(self.__hands) class EV: def __init__(self, plays, win, tie, lose): self.n_hands = plays self.n_wins = win self.n_ties = tie self.n_losses = lose class SumEV: def __init__(self): self.n_hands = 0 self.n_wins = 0 self.n_ties = 0 self.n_losses = 0 self.output = "" def add(self, ev): self.n_hands += ev.n_hands self.n_wins += ev.n_wins self.n_ties += ev.n_ties self.n_losses += ev.n_losses def show(self, hand, h_range): win_pct = 100 * (float(self.n_wins) / float(self.n_hands)) lose_pct = 100 * (float(self.n_losses) / float(self.n_hands)) tie_pct = 100 * (float(self.n_ties) / float(self.n_hands)) self.output = """ Enumerated %d possible plays. Your hand: (%s %s) Against the range: %s Win Lose Tie %5.2f%% %5.2f%% %5.2f%% """ % (self.n_hands, hand.c1, hand.c2, cards_from_range(h_range), win_pct, lose_pct, tie_pct) print self.output # Expands hand abbreviations such as JJ and AK to full hand ranges. # Takes into account cards already known to be in player's hand and/or # board. def expand_hands(abbrev, hand, board): selection = -1 known_cards = set() known_cards.update(set([hand.c2, hand.c2])) known_cards.update(set([board.b1, board.b2, board.b3, board.b4, board.b5])) re.search('[2-9TJQKA]{2}(s|o)',abbrev) if re.search('^[2-9TJQKA]{2}(s|o)$',abbrev): #AKs or AKo return standard_expand(abbrev, hand, known_cards) elif re.search('^[2-9TJQKA]{2}(s|o)\+$',abbrev): #76s+ or 76o+ return iterative_expand(abbrev, hand, known_cards) #elif: AhXh #elif: Ah6h+A def iterative_expand(abbrev, hand, known_cards): r1 = abbrev[0] r2 = abbrev[1] h_range = [] considered = set() idx = CONNECTORS.index('%s%s' % (r1, r2)) ltr = abbrev[2] h_range = [] for h in CONNECTORS[idx:]: abr = "%s%s" % (h, ltr) h_range += standard_expand(abr, hand, known_cards) return h_range def standard_expand(abbrev, hand, known_cards): # Card ranks may be different r1 = abbrev[0] r2 = abbrev[1] # There may be a specifier: 's' for 'suited'; 'o' for 'off-suit' if len(abbrev) == 3: ltr = abbrev[2] if ltr == 'o': selection = OFFSUIT elif ltr == 's': selection = SUITED else: selection = ANY h_range = [] considered = set() for s1 in SUITS: c1 = r1 + s1 if c1 in known_cards: continue considered.add(c1) for s2 in SUITS: c2 = r2 + s2 if selection == SUITED and s1 != s2: continue elif selection == OFFSUIT and s1 == s2: continue if c2 not in considered and c2 not in known_cards: h_range.append(Cards(c1, c2)) return h_range def parse_args(args, container): # args[0] is the path being executed; need 3 more args if len(args) < 4: return False container.set_board_string(args[1]) container.set_hero_cards_string(args[2]) container.set_villain_range_string(args[3]) return True def odds_for_hand(hand1, hand2, board, iterations): res = ev.poker_eval(game='holdem', pockets = [ hand1, hand2 ], dead = [], board = board, iterations = iterations ) plays = int(res['info'][0]) eval = res['eval'][0] win = int(eval['winhi']) lose = int(eval['losehi']) tie = int(eval['tiehi']) _ev = EV(plays, win, tie, lose) return _ev def odds_for_range(holder): sev = SumEV() monte_carlo = False # Construct board list b = [] board = holder.board if board.b3 is not None: b.extend([board.b1, board.b2, board.b3]) else: b.extend(['__', '__', '__']) monte_carlo = True if board.b4 is not None: b.append(board.b4) else: b.append("__") if board.b5 is not None: b.append(board.b5) else: b.append("__") if monte_carlo: print _('No board given. Using Monte-Carlo simulation...') iters = random.randint(25000, 125000) else: iters = -1 for h in holder.h_range.get(): e = odds_for_hand( [holder.hand.c1, holder.hand.c2], [h.c1, h.c2], b, iterations=iters ) sev.add(e) sev.show(holder.hand, holder.h_range.get()) return sev def usage(me): print """Texas Hold'Em odds calculator Calculates odds against a range of hands. To use: %s '' '' '' [...] Separate cards with space. Separate hands in range with commas. """ % me def cards_from_range(h_range): s = '{' for h in h_range: if h.c1 == '__' and h.c2 == '__': s += 'random, ' else: s += '%s%s, ' % (h.c1, h.c2) s = s.rstrip(', ') s += '}' return s def main(argv=None): stove = Stove() if not parse_args(sys.argv, stove): usage(sys.argv[0]) sys.exit(2) odds_for_range(stove) if __name__ == '__main__': sys.exit(main())