347 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
		
			14 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
 | |
| 
 | |
| 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'] = False
 | |
|             self.handsplayers[player[1]]['sawShowdown'] = False
 | |
|             self.handsplayers[player[1]]['wonAtSD']     = False
 | |
|             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
 | |
| 
 | |
|         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'] = 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'] = True
 | |
|             if self.handsplayers[player]['sawShowdown'] == True:
 | |
|                 self.handsplayers[player]['wonAtSD'] = True
 | |
| 
 | |
|         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):
 | |
|                 self.handsplayers[player[1]]['card%s' % i] = 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
 | |
|         # tourneyTypeId,
 | |
|         # startCards,
 | |
|         # street0_3BChance,street0_3BDone,
 | |
|         # otherRaisedStreet1-4
 | |
|         # foldToOtherRaisedStreet1-4
 | |
|         # stealAttemptChance,stealAttempted,
 | |
|         # foldBbToStealChance,foldedBbToSteal,
 | |
|         # foldSbToStealChance,foldedSbToSteal,
 | |
|         # foldToStreet1-4CBChance, foldToStreet1-4CBDone,
 | |
|         # street1-4CheckCallRaiseChance, street1-4CheckCallRaiseDone,
 | |
| 
 | |
|         # 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)
 | |
| 
 | |
|         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
 | |
|         for i, street in enumerate(hand.actionStreets[2:], start=1):
 | |
|             name = self.lastBetOrRaiser(hand.actionStreets[i])
 | |
|             if name:
 | |
|                 chance = self.noBetsBefore(hand.actionStreets[i+1], name)
 | |
|                 self.handsplayers[name]['street%dCBChance' %i] = True
 | |
|                 if chance == True:
 | |
|                     self.handsplayers[name]['street%dCBDone' %i] = self.betStreet(hand.actionStreets[i+1], 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
 |