e1b1d06ec7
Remove some debug code, and structure cli interface in the same way as other fpdb modules
301 lines
6.8 KiB
Python
Executable File
301 lines
6.8 KiB
Python
Executable File
#!/usr/bin/python
|
|
# -*- coding: iso-8859-15
|
|
#
|
|
# stove.py
|
|
# Simple Hold'em equity calculator
|
|
# Copyright (C) 2007-2008 Mika Boströ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.
|
|
#
|
|
#TODO: gettextify
|
|
|
|
import sys, random
|
|
import pokereval
|
|
|
|
SUITS = ['h', 'd', 's', 'c']
|
|
|
|
ANY = 0
|
|
SUITED = 1
|
|
OFFSUIT = 2
|
|
|
|
ev = pokereval.PokerEval()
|
|
|
|
|
|
class Stove:
|
|
def __init__(self):
|
|
self.hand = None
|
|
self.board = None
|
|
self.range = None
|
|
|
|
def set_board_with_list(self, board):
|
|
pass
|
|
|
|
def set_board_with_string(self, board):
|
|
pass
|
|
|
|
|
|
class Cards:
|
|
def __init__(self, c1, c2):
|
|
self.c1 = c1
|
|
self.c2 = c2
|
|
|
|
def get(self):
|
|
return [c1, 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
|
|
|
|
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, 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))
|
|
print 'Enumerated %d possible plays.' % self.n_hands
|
|
print 'Your hand: (%s %s)' % (hand.c1, hand.c2)
|
|
print 'Against the range: %s\n' % cards_from_range(range)
|
|
print ' Win Lose Tie'
|
|
print ' %5.2f%% %5.2f%% %5.2f%%' % (win_pct, lose_pct, tie_pct)
|
|
|
|
|
|
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
|
|
|
|
def cards_from_range(range):
|
|
s = '{'
|
|
for h in range:
|
|
if h.c1 == '__' and h.c2 == '__':
|
|
s += 'random, '
|
|
else:
|
|
s += '%s%s, ' % (h.c1, h.c2)
|
|
s = s.rstrip(', ')
|
|
s += '}'
|
|
return s
|
|
|
|
|
|
# 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]))
|
|
|
|
# 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
|
|
|
|
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:
|
|
range.append(Cards(c1, c2))
|
|
return range
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(args, container):
|
|
# args[0] is the path being executed; need 3 more args
|
|
if len(args) < 4:
|
|
return False
|
|
|
|
board = Board()
|
|
|
|
# Board
|
|
b = args[1].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]
|
|
|
|
# Our pocket cards
|
|
cc = args[2].strip().split()
|
|
c1 = cc[0]
|
|
c2 = cc[1]
|
|
pocket_cards = Cards(c1, c2)
|
|
|
|
# Villain's range
|
|
range = Range()
|
|
hands_in_range = args[3].strip().split(',')
|
|
for h in hands_in_range:
|
|
_h = h.strip()
|
|
if len(_h) > 3:
|
|
cc = _h.split()
|
|
r1 = cc[0]
|
|
r2 = cc[1]
|
|
vp = Cards(r1, r2)
|
|
range.add(vp)
|
|
else:
|
|
range.expand(expand_hands(_h, pocket_cards, board))
|
|
|
|
container.hand = pocket_cards
|
|
container.range = range
|
|
container.board = board
|
|
|
|
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.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.range.get())
|
|
|
|
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())
|