2010-11-18 06:09:38 +01:00
|
|
|
|
#!/usr/bin/python
|
|
|
|
|
# -*- coding: iso-8859-15
|
|
|
|
|
#
|
|
|
|
|
# stove.py
|
|
|
|
|
# Simple Hold'em equity calculator
|
|
|
|
|
# Copyright (C) 2007-2008 Mika Bostr<74>m <bostik@iki.fi>
|
|
|
|
|
#
|
|
|
|
|
# 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.
|
|
|
|
|
#
|
2011-02-01 02:04:47 +01:00
|
|
|
|
# TODO gettextify usage print
|
2010-12-06 05:51:53 +01:00
|
|
|
|
|
|
|
|
|
import L10n
|
|
|
|
|
_ = L10n.get_translation()
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
import sys, random
|
2011-02-07 10:10:50 +01:00
|
|
|
|
import re
|
2010-11-18 06:09:38 +01:00
|
|
|
|
import pokereval
|
|
|
|
|
|
|
|
|
|
SUITS = ['h', 'd', 's', 'c']
|
|
|
|
|
|
2011-02-07 10:10:50 +01:00
|
|
|
|
CONNECTORS = ['32', '43', '54', '65', '76', '87', '98', 'T9', 'JT', 'QJ', 'KQ', 'AK']
|
|
|
|
|
|
2010-11-18 06:09:38 +01:00
|
|
|
|
ANY = 0
|
|
|
|
|
SUITED = 1
|
|
|
|
|
OFFSUIT = 2
|
|
|
|
|
|
|
|
|
|
ev = pokereval.PokerEval()
|
|
|
|
|
|
|
|
|
|
|
2010-11-30 08:16:01 +01:00
|
|
|
|
class Stove:
|
2010-11-18 06:09:38 +01:00
|
|
|
|
def __init__(self):
|
|
|
|
|
self.hand = None
|
|
|
|
|
self.board = None
|
2010-12-03 04:46:15 +01:00
|
|
|
|
self.h_range = None
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
2010-11-30 08:16:01 +01:00
|
|
|
|
def set_board_with_list(self, board):
|
|
|
|
|
pass
|
|
|
|
|
|
2010-11-30 08:33:13 +01:00
|
|
|
|
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
|
2010-12-03 04:46:15 +01:00
|
|
|
|
h_range = Range()
|
2010-11-30 08:33:13 +01:00
|
|
|
|
hands_in_range = string.strip().split(',')
|
|
|
|
|
for h in hands_in_range:
|
|
|
|
|
_h = h.strip()
|
2011-02-07 10:10:50 +01:00
|
|
|
|
h_range.expand(expand_hands(_h, self.hand, self.board))
|
2010-11-30 08:33:13 +01:00
|
|
|
|
|
2010-12-03 04:46:15 +01:00
|
|
|
|
self.h_range = h_range
|
2010-11-30 08:16:01 +01:00
|
|
|
|
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
class Cards:
|
|
|
|
|
def __init__(self, c1, c2):
|
|
|
|
|
self.c1 = c1
|
|
|
|
|
self.c2 = c2
|
|
|
|
|
|
|
|
|
|
def get(self):
|
2011-02-07 10:10:50 +01:00
|
|
|
|
return [self.c1, self.c2]
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2011-02-07 10:10:50 +01:00
|
|
|
|
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
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
|
2011-01-19 09:02:19 +01:00
|
|
|
|
self.output = ""
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2010-12-03 04:46:15 +01:00
|
|
|
|
def show(self, hand, h_range):
|
2010-11-18 06:09:38 +01:00
|
|
|
|
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))
|
2011-01-19 09:02:19 +01:00
|
|
|
|
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)
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
2011-01-19 09:02:19 +01:00
|
|
|
|
print self.output
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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]))
|
|
|
|
|
|
2011-02-07 10:10:50 +01:00
|
|
|
|
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):
|
2010-11-18 06:09:38 +01:00
|
|
|
|
# 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
|
|
|
|
|
|
2010-12-03 04:46:15 +01:00
|
|
|
|
h_range = []
|
2010-11-18 06:09:38 +01:00
|
|
|
|
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:
|
2010-12-03 04:46:15 +01:00
|
|
|
|
h_range.append(Cards(c1, c2))
|
|
|
|
|
return h_range
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(args, container):
|
|
|
|
|
# args[0] is the path being executed; need 3 more args
|
|
|
|
|
if len(args) < 4:
|
|
|
|
|
return False
|
|
|
|
|
|
2010-11-30 08:33:13 +01:00
|
|
|
|
container.set_board_string(args[1])
|
|
|
|
|
container.set_hero_cards_string(args[2])
|
|
|
|
|
container.set_villain_range_string(args[3])
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def odds_for_hand(hand1, hand2, board, iterations):
|
|
|
|
|
res = ev.poker_eval(game='holdem',
|
|
|
|
|
pockets = [
|
|
|
|
|
hand1,
|
|
|
|
|
hand2
|
|
|
|
|
],
|
|
|
|
|
dead = [],
|
|
|
|
|
board = board,
|
|
|
|
|
iterations = iterations
|
|
|
|
|
)
|
2011-02-07 10:10:50 +01:00
|
|
|
|
|
2010-11-18 06:09:38 +01:00
|
|
|
|
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:
|
2010-12-06 05:51:53 +01:00
|
|
|
|
print _('No board given. Using Monte-Carlo simulation...')
|
2010-11-18 06:09:38 +01:00
|
|
|
|
iters = random.randint(25000, 125000)
|
|
|
|
|
else:
|
|
|
|
|
iters = -1
|
2010-12-03 04:46:15 +01:00
|
|
|
|
for h in holder.h_range.get():
|
2010-11-18 06:09:38 +01:00
|
|
|
|
e = odds_for_hand(
|
|
|
|
|
[holder.hand.c1, holder.hand.c2],
|
|
|
|
|
[h.c1, h.c2],
|
|
|
|
|
b,
|
|
|
|
|
iterations=iters
|
|
|
|
|
)
|
|
|
|
|
sev.add(e)
|
|
|
|
|
|
2010-12-03 04:46:15 +01:00
|
|
|
|
sev.show(holder.hand, holder.h_range.get())
|
2011-01-19 09:02:19 +01:00
|
|
|
|
return sev
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
2010-12-03 04:14:51 +01:00
|
|
|
|
def usage(me):
|
|
|
|
|
print """Texas Hold'Em odds calculator
|
|
|
|
|
Calculates odds against a range of hands.
|
|
|
|
|
|
|
|
|
|
To use: %s '<board cards>' '<your hand>' '<opponent's range>' [...]
|
|
|
|
|
|
|
|
|
|
Separate cards with space.
|
|
|
|
|
Separate hands in range with commas.
|
|
|
|
|
""" % me
|
|
|
|
|
|
2010-12-03 04:46:15 +01:00
|
|
|
|
def cards_from_range(h_range):
|
2010-12-03 04:14:51 +01:00
|
|
|
|
s = '{'
|
2010-12-03 04:46:15 +01:00
|
|
|
|
for h in h_range:
|
2010-12-03 04:14:51 +01:00
|
|
|
|
if h.c1 == '__' and h.c2 == '__':
|
|
|
|
|
s += 'random, '
|
|
|
|
|
else:
|
|
|
|
|
s += '%s%s, ' % (h.c1, h.c2)
|
|
|
|
|
s = s.rstrip(', ')
|
|
|
|
|
s += '}'
|
|
|
|
|
return s
|
|
|
|
|
|
2010-11-30 08:16:01 +01:00
|
|
|
|
def main(argv=None):
|
|
|
|
|
stove = Stove()
|
|
|
|
|
if not parse_args(sys.argv, stove):
|
|
|
|
|
usage(sys.argv[0])
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
odds_for_range(stove)
|
2010-11-18 06:09:38 +01:00
|
|
|
|
|
2010-11-30 08:16:01 +01:00
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
sys.exit(main())
|