fpdb/pyfpdb/DerivedStats.py
Worros ff88823c1a [NEWIMPORT] Fix syntax to be 2.5 compatible.
Python 2.6 enumerate() function contains a useful 'start' paramater,
apparently this did not exist in 2.5.

Patch frim Mika Bostrom
2009-12-24 03:09:13 +08:00

367 lines
16 KiB
Python

#!/usr/bin/python
#Copyright 2008 Carl Gherardi
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
#fpdb modules
import Card
from decimal import Decimal
DEBUG = False
if DEBUG:
import pprint
pp = pprint.PrettyPrinter(indent=4)
class DerivedStats():
def __init__(self, hand):
self.hand = hand
self.hands = {}
self.handsplayers = {}
def getStats(self, hand):
for player in hand.players:
self.handsplayers[player[1]] = {}
#Init vars that may not be used, but still need to be inserted.
# All stud street4 need this when importing holdem
self.handsplayers[player[1]]['winnings'] = 0
self.handsplayers[player[1]]['rake'] = 0
self.handsplayers[player[1]]['totalProfit'] = 0
self.handsplayers[player[1]]['street4Seen'] = False
self.handsplayers[player[1]]['street4Aggr'] = False
self.handsplayers[player[1]]['wonWhenSeenStreet1'] = 0.0
self.handsplayers[player[1]]['sawShowdown'] = False
self.handsplayers[player[1]]['wonAtSD'] = 0.0
for i in range(5):
self.handsplayers[player[1]]['street%dCalls' % i] = 0
self.handsplayers[player[1]]['street%dBets' % i] = 0
for i in range(1,5):
self.handsplayers[player[1]]['street%dCBChance' %i] = False
self.handsplayers[player[1]]['street%dCBDone' %i] = False
#FIXME - Everything below this point is incomplete.
self.handsplayers[player[1]]['position'] = 2
self.handsplayers[player[1]]['tourneyTypeId'] = 1
self.handsplayers[player[1]]['startCards'] = 0
self.handsplayers[player[1]]['street0_3BChance'] = False
self.handsplayers[player[1]]['street0_3BDone'] = False
self.handsplayers[player[1]]['stealAttemptChance'] = False
self.handsplayers[player[1]]['stealAttempted'] = False
self.handsplayers[player[1]]['foldBbToStealChance'] = False
self.handsplayers[player[1]]['foldBbToStealChance'] = False
self.handsplayers[player[1]]['foldSbToStealChance'] = False
self.handsplayers[player[1]]['foldedSbToSteal'] = False
self.handsplayers[player[1]]['foldedBbToSteal'] = False
for i in range(1,5):
self.handsplayers[player[1]]['otherRaisedStreet%d' %i] = False
self.handsplayers[player[1]]['foldToOtherRaisedStreet%d' %i] = False
self.handsplayers[player[1]]['foldToStreet%dCBChance' %i] = False
self.handsplayers[player[1]]['foldToStreet%dCBDone' %i] = False
self.handsplayers[player[1]]['street%dCheckCallRaiseChance' %i] = False
self.handsplayers[player[1]]['street%dCheckCallRaiseDone' %i] = False
self.assembleHands(self.hand)
self.assembleHandsPlayers(self.hand)
if DEBUG:
print "Hands:"
pp.pprint(self.hands)
print "HandsPlayers:"
pp.pprint(self.handsplayers)
def getHands(self):
return self.hands
def getHandsPlayers(self):
return self.handsplayers
def assembleHands(self, hand):
self.hands['tableName'] = hand.tablename
self.hands['siteHandNo'] = hand.handid
self.hands['gametypeId'] = None # Leave None, handled later after checking db
self.hands['handStart'] = hand.starttime # format this!
self.hands['importTime'] = None
self.hands['seats'] = self.countPlayers(hand)
self.hands['maxSeats'] = hand.maxseats
self.hands['texture'] = None # No calculation done for this yet.
# This (i think...) is correct for both stud and flop games, as hand.board['street'] disappears, and
# those values remain default in stud.
boardcards = []
for street in hand.communityStreets:
boardcards += hand.board[street]
boardcards += [u'0x', u'0x', u'0x', u'0x', u'0x']
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
self.hands['boardcard1'] = cards[0]
self.hands['boardcard2'] = cards[1]
self.hands['boardcard3'] = cards[2]
self.hands['boardcard4'] = cards[3]
self.hands['boardcard5'] = cards[4]
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % hand.getStreetTotals()
totals = hand.getStreetTotals()
totals = [int(100*i) for i in totals]
self.hands['street1Pot'] = totals[0]
self.hands['street2Pot'] = totals[1]
self.hands['street3Pot'] = totals[2]
self.hands['street4Pot'] = totals[3]
self.hands['showdownPot'] = totals[4]
self.vpip(hand) # Gives playersVpi (num of players vpip)
#print "DEBUG: vpip: %s" %(self.hands['playersVpi'])
self.playersAtStreetX(hand) # Gives playersAtStreet1..4 and Showdown
#print "DEBUG: playersAtStreet 1:'%s' 2:'%s' 3:'%s' 4:'%s'" %(self.hands['playersAtStreet1'],self.hands['playersAtStreet2'],self.hands['playersAtStreet3'],self.hands['playersAtStreet4'])
self.streetXRaises(hand) # Empty function currently
def assembleHandsPlayers(self, hand):
#street0VPI/vpip already called in Hand
# sawShowdown is calculated in playersAtStreetX, as that calculation gives us a convenient list of names
#hand.players = [[seat, name, chips],[seat, name, chips]]
for player in hand.players:
self.handsplayers[player[1]]['seatNo'] = player[0]
self.handsplayers[player[1]]['startCash'] = int(100 * Decimal(player[2]))
for i, street in enumerate(hand.actionStreets[2:]):
self.seen(self.hand, i+1)
for i, street in enumerate(hand.actionStreets[1:]):
self.aggr(self.hand, i)
self.calls(self.hand, i)
self.bets(self.hand, i)
# Winnings is a non-negative value of money collected from the pot, which already includes the
# rake taken out. hand.collectees is Decimal, database requires cents
for player in hand.collectees:
self.handsplayers[player]['winnings'] = int(100 * hand.collectees[player])
#FIXME: This is pretty dodgy, rake = hand.rake/#collectees
# You can really only pay rake when you collect money, but
# different sites calculate rake differently.
# Should be fine for split-pots, but won't be accurate for multi-way pots
self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees)
if self.handsplayers[player]['street1Seen'] == True:
self.handsplayers[player]['wonWhenSeenStreet1'] = 1.0
if self.handsplayers[player]['sawShowdown'] == True:
self.handsplayers[player]['wonAtSD'] = 1.0
for player in hand.pot.committed:
self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player]))
self.calcCBets(hand)
for player in hand.players:
hcs = hand.join_holecards(player[1], asList=True)
hcs = hcs + [u'0x', u'0x', u'0x', u'0x', u'0x']
#for i, card in enumerate(hcs[:7], 1): #Python 2.6 syntax
# self.handsplayers[player[1]]['card%s' % i] = Card.encodeCard(card)
for i, card in enumerate(hcs[:7]):
self.handsplayers[player[1]]['card%s' % (i+1)] = Card.encodeCard(card)
# position,
#Stud 3rd street card test
# denny501: brings in for $0.02
# s0rrow: calls $0.02
# TomSludge: folds
# Soroka69: calls $0.02
# rdiezchang: calls $0.02 (Seat 8)
# u.pressure: folds (Seat 1)
# 123smoothie: calls $0.02
# gashpor: calls $0.02
# Additional stats
# 3betSB, 3betBB
# Squeeze, Ratchet?
def getPosition(hand, seat):
"""Returns position value like 'B', 'S', 0, 1, ..."""
# Flop/Draw games with blinds
# Need a better system???
# -2 BB - B (all)
# -1 SB - S (all)
# 0 Button
# 1 Cutoff
# 2 Hijack
def assembleHudCache(self, hand):
pass
def vpip(self, hand):
vpipers = set()
for act in hand.actions[hand.actionStreets[1]]:
if act[1] in ('calls','bets', 'raises'):
vpipers.add(act[0])
self.hands['playersVpi'] = len(vpipers)
for player in hand.players:
if player[1] in vpipers:
self.handsplayers[player[1]]['street0VPI'] = True
else:
self.handsplayers[player[1]]['street0VPI'] = False
def playersAtStreetX(self, hand):
""" playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4/draw1 */"""
# self.actions[street] is a list of all actions in a tuple, contining the player name first
# [ (player, action, ....), (player2, action, ...) ]
# The number of unique players in the list per street gives the value for playersAtStreetXXX
# FIXME?? - This isn't couting people that are all in - at least showdown needs to reflect this
self.hands['playersAtStreet1'] = 0
self.hands['playersAtStreet2'] = 0
self.hands['playersAtStreet3'] = 0
self.hands['playersAtStreet4'] = 0
self.hands['playersAtShowdown'] = 0
alliners = set()
for (i, street) in enumerate(hand.actionStreets[2:]):
actors = set()
for action in hand.actions[street]:
if len(action) > 2 and action[-1]: # allin
alliners.add(action[0])
actors.add(action[0])
if len(actors)==0 and len(alliners)<2:
alliners = set()
self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors))
actions = hand.actions[hand.actionStreets[-1]]
pas = set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners)
self.hands['playersAtShowdown'] = len(pas)
if self.hands['playersAtShowdown'] > 1:
for player in pas:
self.handsplayers[player]['sawShowdown'] = True
def streetXRaises(self, hand):
# self.actions[street] is a list of all actions in a tuple, contining the action as the second element
# [ (player, action, ....), (player2, action, ...) ]
# No idea what this value is actually supposed to be
# In theory its "num small bets paid to see flop/street4, including blind" which makes sense for limit. Not so useful for nl
# Leaving empty for the moment,
for i in range(5): self.hands['street%dRaises' % i] = 0
for (i, street) in enumerate(hand.actionStreets[1:]):
self.hands['street%dRaises' % i] = len(filter( lambda action: action[1] in ('raises','bets'), hand.actions[street]))
def calcCBets(self, hand):
# Continuation Bet chance, action:
# Had the last bet (initiative) on previous street, got called, close street action
# Then no bets before the player with initiatives first action on current street
# ie. if player on street-1 had initiative
# and no donkbets occurred
# XXX: enumerate(list, start=x) is python 2.6 syntax; 'start'
# came there
#for i, street in enumerate(hand.actionStreets[2:], start=1):
for i, street in enumerate(hand.actionStreets[2:]:
name = self.lastBetOrRaiser(hand.actionStreets[i+1])
if name:
chance = self.noBetsBefore(hand.actionStreets[i+2], name)
self.handsplayers[name]['street%dCBChance' % (i+1)] = True
if chance == True:
self.handsplayers[name]['street%dCBDone' % (i+1)] = self.betStreet(hand.actionStreets[i+2], name)
def seen(self, hand, i):
pas = set()
for act in hand.actions[hand.actionStreets[i+1]]:
pas.add(act[0])
for player in hand.players:
if player[1] in pas:
self.handsplayers[player[1]]['street%sSeen' % i] = True
else:
self.handsplayers[player[1]]['street%sSeen' % i] = False
def aggr(self, hand, i):
aggrers = set()
for act in hand.actions[hand.actionStreets[i]]:
if act[1] in ('completes', 'raises'):
aggrers.add(act[0])
for player in hand.players:
if player[1] in aggrers:
self.handsplayers[player[1]]['street%sAggr' % i] = True
else:
self.handsplayers[player[1]]['street%sAggr' % i] = False
def calls(self, hand, i):
callers = []
for act in hand.actions[hand.actionStreets[i+1]]:
if act[1] in ('calls'):
self.handsplayers[act[0]]['street%sCalls' % i] = 1 + self.handsplayers[act[0]]['street%sCalls' % i]
# CG - I'm sure this stat is wrong
# Best guess is that raise = 2 bets
def bets(self, hand, i):
betters = []
for act in hand.actions[hand.actionStreets[i+1]]:
if act[1] in ('bets'):
self.handsplayers[act[0]]['street%sBets' % i] = 1 + self.handsplayers[act[0]]['street%sBets' % i]
def countPlayers(self, hand):
pass
def pfba(self, actions, f=None, l=None):
"""Helper method. Returns set of PlayersFilteredByActions
f - forbidden actions
l - limited to actions
"""
players = set()
for action in actions:
if l is not None and action[1] not in l: continue
if f is not None and action[1] in f: continue
players.add(action[0])
return players
def noBetsBefore(self, street, player):
"""Returns true if there were no bets before the specified players turn, false otherwise"""
betOrRaise = False
for act in self.hand.actions[street]:
#Must test for player first in case UTG
if act[0] == player:
betOrRaise = True
break
if act[1] in ('bets', 'raises'):
break
return betOrRaise
def betStreet(self, street, player):
"""Returns true if player bet/raised the street as their first action"""
betOrRaise = False
for act in self.hand.actions[street]:
if act[0] == player and act[1] in ('bets', 'raises'):
betOrRaise = True
else:
break
return betOrRaise
def lastBetOrRaiser(self, street):
"""Returns player name that placed the last bet or raise for that street.
None if there were no bets or raises on that street"""
lastbet = None
for act in self.hand.actions[street]:
if act[1] in ('bets', 'raises'):
lastbet = act[0]
return lastbet