fpdb/pyfpdb/Stove.py

309 lines
6.9 KiB
Python
Raw Normal View History

#!/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.
#
import sys, random
import pokereval
SUITS = ['h', 'd', 's', 'c']
ANY = 0
SUITED = 1
OFFSUIT = 2
ev = pokereval.PokerEval()
holder = None
class Holder:
def __init__(self):
self.hand = None
self.board = None
self.range = None
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))
holder.hand = pocket_cards
holder.range = range
holder.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())
holder = Holder()
if not parse_args(sys.argv, holder):
usage(sys.argv[0])
sys.exit(2)
odds_for_range(holder)
# debugs
#print '%s, %s' % ( holder.hand.c1, holder.hand.c2)
#print '%s %s %s %s %s' % (holder.board.b1, holder.board.b2,
# holder.board.b3, holder.board.b4, holder.board.b5)
#while True:
# try:
# vl = holder.range.get()
# v = vl.pop()
# print '\t%s %s' % (v.c1, v.c2)
# except IndexError:
# break