Expanded my entry in contributors.txt.
[fpdb-dooglus.git] / pyfpdb / Hand.py
blob493a02188c6c60cbe08878c501046060b822e3f0
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2011 Carl Gherardi
5 #This program is free software: you can redistribute it and/or modify
6 #it under the terms of the GNU Affero General Public License as published by
7 #the Free Software Foundation, version 3 of the License.
9 #This program is distributed in the hope that it will be useful,
10 #but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 #GNU General Public License for more details.
14 #You should have received a copy of the GNU Affero General Public License
15 #along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #In the "official" distribution you can find the license in agpl-3.0.txt.
18 import L10n
19 _ = L10n.get_translation()
21 # TODO: get writehand() encoding correct
23 import re
24 import sys
25 import traceback
26 import os
27 import os.path
28 from decimal_wrapper import Decimal
29 import operator
30 import time,datetime
31 from copy import deepcopy
32 import pprint
34 import logging
35 # logging has been set up in fpdb.py or HUD_main.py, use their settings:
36 log = logging.getLogger("parser")
39 import Configuration
40 from Exceptions import *
41 import DerivedStats
42 import Card
44 class Hand(object):
46 ###############################################################3
47 # Class Variables
48 UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
49 LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
50 SYMBOL = {'USD': '$', 'CAD': '$', 'EUR': u'$', 'GBP': '$', 'T$': '', 'play': ''}
51 MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
52 ACTION = {'ante': 1, 'small blind': 2, 'secondsb': 3, 'big blind': 4, 'both': 5, 'calls': 6, 'raises': 7,
53 'bets': 8, 'stands pat': 9, 'folds': 10, 'checks': 11, 'discards': 12, 'bringin': 13, 'completes': 14}
56 def __init__(self, config, sitename, gametype, handText, builtFrom = "HHC"):
57 #log.debug( _("Hand.init(): handText is ") + str(handText) )
58 self.config = config
59 self.saveActions = self.config.get_import_parameters().get('saveActions')
60 self.callHud = self.config.get_import_parameters().get("callFpdbHud")
61 self.cacheSessions = self.config.get_import_parameters().get("cacheSessions")
62 #log = Configuration.get_logger("logging.conf", "db", log_dir=self.config.dir_log)
63 self.sitename = sitename
64 self.siteId = self.config.get_site_id(sitename)
65 self.stats = DerivedStats.DerivedStats(self)
66 self.gametype = gametype
67 self.startTime = 0
68 self.handText = handText
69 self.handid = 0
70 self.cancelled = False
71 self.dbid_hands = 0
72 self.dbid_pids = None
73 self.dbid_hpid = None
74 self.dbid_gt = 0
75 self.tablename = ""
76 self.hero = ""
77 self.maxseats = None
78 self.counted_seats = 0
79 self.buttonpos = 0
80 self.runItTimes = 0
82 #tourney stuff
83 self.tourNo = None
84 self.tourneyId = None
85 self.tourneyTypeId = None
86 self.buyin = None
87 self.buyinCurrency = None
88 self.buyInChips = None
89 self.fee = None # the Database code is looking for this one .. ?
90 self.level = None
91 self.mixed = None
92 self.speed = "Normal"
93 self.isRebuy = False
94 self.isAddOn = False
95 self.isKO = False
96 self.koBounty = None
97 self.isMatrix = False
98 self.isShootout = False
99 self.added = None
100 self.addedCurrency = None
101 self.tourneyComment = None
103 self.seating = []
104 self.players = []
105 self.posted = []
106 self.tourneysPlayersIds = {}
108 # Collections indexed by street names
109 self.bets = {}
110 self.lastBet = {}
111 self.streets = {}
112 self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
113 self.board = {} # dict from street names to community cards
114 self.holecards = {}
115 self.discards = {}
116 self.showdownStrings = {}
117 for street in self.allStreets:
118 self.streets[street] = "" # portions of the handText, filled by markStreets()
119 self.actions[street] = []
120 for street in self.actionStreets:
121 self.bets[street] = {}
122 self.lastBet[street] = 0
123 self.board[street] = []
124 for street in self.holeStreets:
125 self.holecards[street] = {} # dict from player names to holecards
126 self.discards[street] = {} # dict from player names to dicts by street ... of tuples ... of discarded holecards
127 # Collections indexed by player names
128 self.stacks = {}
129 self.collected = [] #list of ?
130 self.collectees = {} # dict from player names to amounts collected (?)
132 # Sets of players
133 self.folded = set()
134 self.dealt = set() # 'dealt to' line to be printed
135 self.shown = set() # cards were shown
136 self.mucked = set() # cards were mucked at showdown
138 # Things to do with money
139 self.pot = Pot()
140 self.totalpot = None
141 self.totalcollected = None
142 self.rake = None
143 # currency symbol for this hand
144 self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
145 self.pot.setSym(self.sym)
146 self.is_duplicate = False # i.e. don't update hudcache if true
148 def __str__(self):
149 vars = ( (_("BB"), self.bb),
150 (_("SB"), self.sb),
151 (_("BUTTONPOS"), self.buttonpos),
152 (_("HAND NO."), self.handid),
153 (_("SITE"), self.sitename),
154 (_("TABLE NAME"), self.tablename),
155 (_("HERO"), self.hero),
156 (_("MAXSEATS"), self.maxseats),
157 (_("LEVEL"), self.level),
158 (_("MIXED"), self.mixed),
159 (_("LASTBET"), self.lastBet),
160 (_("ACTION STREETS"), self.actionStreets),
161 (_("STREETS"), self.streets),
162 (_("ALL STREETS"), self.allStreets),
163 (_("COMMUNITY STREETS"), self.communityStreets),
164 (_("HOLE STREETS"), self.holeStreets),
165 (_("COUNTED SEATS"), self.counted_seats),
166 (_("DEALT"), self.dealt),
167 (_("SHOWN"), self.shown),
168 (_("MUCKED"), self.mucked),
169 (_("TOTAL POT"), self.totalpot),
170 (_("TOTAL COLLECTED"), self.totalcollected),
171 (_("RAKE"), self.rake),
172 (_("START TIME"), self.startTime),
173 (_("TOURNAMENT NO"), self.tourNo),
174 (_("TOURNEY ID"), self.tourneyId),
175 (_("TOURNEY TYPE ID"), self.tourneyTypeId),
176 (_("BUYIN"), self.buyin),
177 (_("BUYIN CURRENCY"), self.buyinCurrency),
178 (_("BUYIN CHIPS"), self.buyInChips),
179 (_("FEE"), self.fee),
180 (_("IS REBUY"), self.isRebuy),
181 (_("IS ADDON"), self.isAddOn),
182 (_("IS KO"), self.isKO),
183 (_("KO BOUNTY"), self.koBounty),
184 (_("IS MATRIX"), self.isMatrix),
185 (_("IS SHOOTOUT"), self.isShootout),
186 (_("TOURNEY COMMENT"), self.tourneyComment),
189 structs = ( (_("PLAYERS"), self.players),
190 (_("STACKS"), self.stacks),
191 (_("POSTED"), self.posted),
192 (_("POT"), self.pot),
193 (_("SEATING"), self.seating),
194 (_("GAMETYPE"), self.gametype),
195 (_("ACTION"), self.actions),
196 (_("COLLECTEES"), self.collectees),
197 (_("BETS"), self.bets),
198 (_("BOARD"), self.board),
199 (_("DISCARDS"), self.discards),
200 (_("HOLECARDS"), self.holecards),
201 (_("TOURNEYS PLAYER IDS"), self.tourneysPlayersIds),
203 str = ''
204 for (name, var) in vars:
205 str = str + "\n%s = " % name + pprint.pformat(var)
207 for (name, struct) in structs:
208 str = str + "\n%s =\n" % name + pprint.pformat(struct, 4)
209 return str
211 def addHoleCards(self, street, player, open=[], closed=[], shown=False, mucked=False, dealt=False):
212 """\
213 Assigns observed holecards to a player.
214 cards list of card bigrams e.g. ['2h','Jc']
215 player (string) name of player
216 shown whether they were revealed at showdown
217 mucked whether they were mucked at showdown
218 dealt whether they were seen in a 'dealt to' line
220 # log.debug("addHoleCards %s %s" % (open + closed, player))
221 try:
222 self.checkPlayerExists(player)
223 except FpdbParseError, e:
224 print _("[ERROR] Tried to add holecards for unknown player: %s") % (player,)
225 return
227 if dealt: self.dealt.add(player)
228 if shown: self.shown.add(player)
229 if mucked: self.mucked.add(player)
231 self.holecards[street][player] = [open, closed]
233 def prepInsert(self, db, printtest = False):
234 #####
235 # Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables
236 # These functions are intended for prep insert eventually
237 #####
238 self.gametype['maxSeats'] = self.maxseats #TODO: move up to individual parsers
239 self.gametype['ante'] = 0 #TODO store actual ante
240 self.dbid_pids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
241 self.dbid_gt = db.getSqlGameTypeId(self.siteId, self.gametype, printdata = printtest)
243 #Gametypes
244 hilo = "h"
245 if self.gametype['category'] in ['studhilo', 'omahahilo']:
246 hilo = "s"
247 elif self.gametype['category'] in ['razz','27_3draw','badugi', '27_1draw']:
248 hilo = "l"
250 self.gametyperow = (self.siteId, self.gametype['currency'], self.gametype['type'], self.gametype['base'],
251 self.gametype['category'], self.gametype['limitType'], hilo,
252 int(Decimal(self.gametype['sb'])*100), int(Decimal(self.gametype['bb'])*100),
253 int(Decimal(self.gametype['bb'])*100), int(Decimal(self.gametype['bb'])*200), int(self.gametype['maxSeats']), int(self.gametype['ante']))
254 # Note: the above data is calculated in db.getGameTypeId
255 # Only being calculated above so we can grab the testdata
257 if self.tourNo!=None:
258 self.tourneyTypeId = db.getSqlTourneyTypeIDs(self)
259 self.tourneyId = db.getSqlTourneyIDs(self)
260 self.tourneysPlayersIds = db.getSqlTourneysPlayersIDs(self)
262 def assembleHand(self):
263 self.stats.getStats(self)
264 self.hands = self.stats.getHands()
265 self.handsplayers = self.stats.getHandsPlayers()
267 def getHandId(self, db, id):
268 if db.isDuplicate(self.dbid_gt, self.hands['siteHandNo']):
269 #log.info(_("Hand.insert(): hid #: %s is a duplicate") % hh['siteHandNo'])
270 self.is_duplicate = True # i.e. don't update hudcache
271 next = id
272 raise FpdbHandDuplicate(self.hands['siteHandNo'])
273 else:
274 self.dbid_hands = id
275 self.hands['id'] = self.dbid_hands
276 next = id +1
277 return next
279 def insertHands(self, db, hbulk, fileId, doinsert = False, printtest = False):
280 """ Function to insert Hand into database
281 Should not commit, and do minimal selects. Callers may want to cache commits
282 db: a connected Database object"""
283 self.hands['gametypeId'] = self.dbid_gt
284 self.hands['seats'] = len(self.dbid_pids)
285 self.hands['fileId'] = fileId
286 hbulk = db.storeHand(self.hands, hbulk, doinsert, printtest)
287 return hbulk
289 def insertHandsPlayers(self, db, hpbulk, doinsert = False, printtest = False):
290 """ Function to inserts HandsPlayers into database"""
291 hpbulk = db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, self.handsplayers, hpbulk, doinsert, printtest)
292 return hpbulk
294 def insertHandsActions(self, db, habulk, doinsert = False, printtest = False):
295 """ Function to inserts HandsActions into database"""
296 handsactions = self.stats.getHandsActions()
297 habulk = db.storeHandsActions(self.dbid_hands, self.dbid_pids, handsactions, habulk, doinsert, printtest)
298 return habulk
300 def updateHudCache(self, db, hcbulk, doinsert = False):
301 """ Function to update the HudCache"""
302 if self.callHud:
303 hcbulk = db.storeHudCache(self.dbid_gt, self.dbid_pids, self.startTime, self.handsplayers, hcbulk, doinsert)
304 return hcbulk
306 def updateSessionsCache(self, db, sc, gsc, tz, doinsert = False):
307 """ Function to update the SessionsCache"""
308 if self.cacheSessions:
309 self.heros = db.getHeroIds(self.dbid_pids, self.sitename)
310 sc = db.prepSessionsCache(self.dbid_hands, self.dbid_pids, self.startTime, sc, self.heros, doinsert)
311 gsc = db.storeSessionsCache(self.dbid_hands, self.dbid_pids, self.startTime, self.gametype
312 ,self.dbid_gt, self.handsplayers, sc, gsc, tz, self.heros, doinsert)
313 if doinsert and sc['bk'] and gsc['bk']:
314 self.hands['sc'] = sc
315 self.hands['gsc'] = gsc
316 else:
317 self.hands['sc'] = None
318 self.hands['gsc'] = None
319 return sc, gsc
321 def select(self, db, handId):
322 """ Function to create Hand object from database """
323 c = db.get_cursor()
324 q = """SELECT
325 hp.seatno,
326 round(hp.winnings / 100.0,2) as winnings,
327 p.name,
328 round(hp.startCash / 100.0,2) as chips,
329 hp.card1,hp.card2,hp.card3,hp.card4,
330 hp.position
331 FROM
332 HandsPlayers as hp,
333 Players as p
334 WHERE
335 hp.handId = %s
336 and p.id = hp.playerId
337 ORDER BY
338 hp.seatno
340 q = q.replace('%s', db.sql.query['placeholder'])
342 # PlayerStacks
343 c.execute(q, (handId,))
344 for (seat, winnings, name, chips, card1, card2, card3, card4, position) in c.fetchall():
345 #print "DEBUG: addPlayer(%s, %s, %s)" %(seat,name,str(chips))
346 self.addPlayer(seat,name,str(chips))
347 #print "DEBUG: card1: %s" % card1
348 # map() should work, but is returning integers... FIXME later
349 #cardlist = map(Card.valueSuitFromCard, [card1, card2, card3, card4])
350 cardlist = [Card.valueSuitFromCard(card1), Card.valueSuitFromCard(card2), Card.valueSuitFromCard(card3), Card.valueSuitFromCard(card4)]
351 #print "DEUBG: cardlist: '%s'" % cardlist
352 if cardlist[0] == '':
353 pass
354 elif self.gametype['category'] == 'holdem':
355 self.addHoleCards('PREFLOP', name, closed=cardlist[0:2], shown=False, mucked=False, dealt=True)
356 elif self.gametype['category'] == 'omaha':
357 self.addHoleCards('PREFLOP', name, closed=cardlist, shown=False, mucked=False, dealt=True)
358 if winnings > 0:
359 self.addCollectPot(name, str(winnings))
360 if position == 'B':
361 self.buttonpos = seat
364 # HandInfo
365 q = """SELECT *
366 FROM Hands
367 WHERE id = %s
369 q = q.replace('%s', db.sql.query['placeholder'])
370 c.execute(q, (handId,))
372 # NOTE: This relies on row_factory = sqlite3.Row (set in connect() params)
373 # Need to find MySQL and Postgres equivalents
374 # MySQL maybe: cursorclass=MySQLdb.cursors.DictCursor
375 #res = c.fetchone()
377 # Using row_factory is global, and affects the rest of fpdb. The following 2 line achieves
378 # a similar result
379 res = [dict(line) for line in [zip([ column[0] for column in c.description], row) for row in c.fetchall()]]
380 res = res[0]
382 #res['tourneyId'] #res['seats'] #res['rush']
383 self.tablename = res['tableName']
384 self.handid = res['siteHandNo']
385 #print "DBEUG: res['startTime']: %s" % res['startTime']
386 self.startTime = datetime.datetime.strptime(res['startTime'], "%Y-%m-%d %H:%M:%S+00:00")
388 cards = map(Card.valueSuitFromCard, [res['boardcard1'], res['boardcard2'], res['boardcard3'], res['boardcard4'], res['boardcard5']])
389 #print "DEBUG: res['boardcard1']: %s" % res['boardcard1']
390 #print "DEBUG: cards: %s" % cards
391 if cards[0]:
392 self.setCommunityCards('FLOP', cards[0:3])
393 if cards[3]:
394 self.setCommunityCards('TURN', [cards[3]])
395 if cards[4]:
396 self.setCommunityCards('RIVER', [cards[4]])
397 # playersVpi | playersAtStreet1 | playersAtStreet2 | playersAtStreet3 |
398 # playersAtStreet4 | playersAtShowdown | street0Raises | street1Raises |
399 # street2Raises | street3Raises | street4Raises | street1Pot | street2Pot |
400 # street3Pot | street4Pot | showdownPot | comment | commentTs | texture
403 # Actions
404 q = """SELECT
405 ha.actionNo,
406 p.name,
407 ha.street,
408 ha.actionId,
409 ha.allIn,
410 round(ha.amount / 100.0,2) as bet
411 FROM
412 HandsActions as ha,
413 HandsPlayers as hp,
414 Players as p,
415 Hands as h
416 WHERE
417 h.id = %s
418 AND ha.handId = h.id
419 AND ha.playerId = hp.playerid
420 AND hp.playerId = p.id
421 AND h.id = hp.handId
422 ORDER BY
423 ha.id ASC
426 q = q.replace('%s', db.sql.query['placeholder'])
427 c.execute(q, (handId,))
428 res = [dict(line) for line in [zip([ column[0] for column in c.description], row) for row in c.fetchall()]]
429 for row in res:
430 name = row['name']
431 street = row['street']
432 act = row['actionId']
433 # allin True/False if row['allIn'] == 0
434 bet = row['bet']
435 street = self.allStreets[int(street)+1]
436 #print "DEBUG: name: '%s' street: '%s' act: '%s' bet: '%s'" %(name, street, act, bet)
437 if act == 2: # Small Blind
438 self.addBlind(name, 'small blind', str(bet))
439 elif act == 4: # Big Blind
440 self.addBlind(name, 'big blind', str(bet))
441 elif act == 6: # Call
442 self.addCall(street, name, str(bet))
443 elif act == 8: # Bet
444 self.addBet(street, name, str(bet))
445 elif act == 10: # Fold
446 self.addFold(street, name)
447 elif act == 11: # Check
448 self.addCheck(street, name)
449 elif act == 7: # Raise
450 self.addRaiseBy(street, name, str(bet))
451 else:
452 print "DEBUG: unknown action: '%s'" % act
454 self.totalPot()
455 self.rake = self.totalpot - self.totalcollected
456 self.writeHand()
458 #hhc.readShowdownActions(self)
459 #hc.readShownCards(self)
462 def addPlayer(self, seat, name, chips):
463 """\
464 Adds a player to the hand, and initialises data structures indexed by player.
465 seat (int) indicating the seat
466 name (string) player name
467 chips (string) the chips the player has at the start of the hand (can be None)
468 If a player has None chips he won't be added."""
469 log.debug("addPlayer: %s %s (%s)" % (seat, name, chips))
470 if chips is not None:
471 chips = chips.replace(u',', u'') #some sites have commas
472 self.players.append([seat, name, chips, 0, 0])
473 self.stacks[name] = Decimal(chips)
474 self.pot.addPlayer(name)
475 for street in self.actionStreets:
476 self.bets[street][name] = []
477 #self.holecards[name] = {} # dict from street names.
478 #self.discards[name] = {} # dict from street names.
481 def addPlayerRank(self, name, winnings, rank):
482 """\
483 name (string) player name
484 winnings (int) winnings
485 rank (int) rank the player finished the tournament"""
486 log.debug("addPlayerRank: %s %s (%s)" % (name, winnings, rank))
487 for player in self.players:
488 if player[1] == name:
489 player[3]=rank
490 player[4]=winnings
492 def addStreets(self, match):
493 # go through m and initialise actions to empty list for each street.
494 if match:
495 self.streets.update(match.groupdict())
496 log.debug("markStreets:\n"+ str(self.streets))
497 else:
498 tmp = self.handText[0:100]
499 log.error(_("markstreets didn't match - Assuming hand %s was cancelled") % self.handid)
500 self.cancelled = True
501 raise FpdbParseError(_("markStreets appeared to fail: First 100 chars: '%s'") % tmp)
503 def checkPlayerExists(self,player):
504 if player not in [p[1] for p in self.players]:
505 print (_("DEBUG:") + " checkPlayerExists: " + _("%s fail on hand number %s") % (player, self.handid))
506 raise FpdbParseError("checkPlayerExists: " + _("%s fail on hand number %s") % (player, self.handid))
508 def setCommunityCards(self, street, cards):
509 log.debug("setCommunityCards %s %s" %(street, cards))
510 self.board[street] = [self.card(c) for c in cards]
511 # print "DEBUG: self.board: %s" % self.board
513 def card(self,c):
514 """upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
515 for k,v in self.UPS.items():
516 c = c.replace(k,v)
517 return c
519 def addAllIn(self, street, player, amount):
520 """\
521 For sites (currently only Carbon Poker) which record "all in" as a special action, which can mean either "calls and is all in" or "raises all in".
523 self.checkPlayerExists(player)
524 amount = amount.replace(u',', u'') #some sites have commas
525 Ai = Decimal(amount)
526 Bp = self.lastBet[street]
527 Bc = sum(self.bets[street][player])
528 C = Bp - Bc
529 if Ai <= C:
530 self.addCall(street, player, amount)
531 elif Bp == 0:
532 self.addBet(street, player, amount)
533 else:
534 Rb = Ai - C
535 self._addRaise(street, player, C, Rb, Ai)
537 def addAnte(self, player, ante):
538 log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
539 if player is not None:
540 ante = ante.replace(u',', u'') #some sites have commas
541 ante = Decimal(ante)
542 self.bets['BLINDSANTES'][player].append(ante)
543 self.stacks[player] -= ante
544 act = (player, 'ante', ante, self.stacks[player]==0)
545 self.actions['BLINDSANTES'].append(act)
546 # self.pot.addMoney(player, ante)
547 self.pot.addCommonMoney(player, ante)
548 #I think the antes should be common money, don't have enough hand history to check
550 def addBlind(self, player, blindtype, amount):
551 # if player is None, it's a missing small blind.
552 # The situation we need to cover are:
553 # Player in small blind posts
554 # - this is a bet of 1 sb, as yet uncalled.
555 # Player in the big blind posts
556 # - this is a call of 1 sb and a raise to 1 bb
558 log.debug("addBlind: %s posts %s, %s" % (player, blindtype, amount))
559 if player is not None:
560 amount = amount.replace(u',', u'') #some sites have commas
561 amount = Decimal(amount)
562 self.stacks[player] -= amount
563 act = (player, blindtype, amount, self.stacks[player]==0)
564 self.actions['BLINDSANTES'].append(act)
566 if blindtype == 'both':
567 # work with the real amount. limit games are listed as $1, $2, where
568 # the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2....
569 amount = Decimal(self.bb)
570 sb = Decimal(self.sb)
571 self.bets['BLINDSANTES'][player].append(sb)
572 self.pot.addCommonMoney(player, sb)
574 if blindtype == 'secondsb':
575 amount = Decimal(0)
576 sb = Decimal(self.sb)
577 self.bets['BLINDSANTES'][player].append(sb)
578 self.pot.addCommonMoney(player, sb)
580 street = 'BLAH'
582 if self.gametype['base'] == 'hold':
583 street = 'PREFLOP'
584 elif self.gametype['base'] == 'draw':
585 street = 'DEAL'
587 self.bets[street][player].append(amount)
588 self.pot.addMoney(player, amount)
589 self.lastBet[street] = amount
590 self.posted = self.posted + [[player,blindtype]]
594 def addCall(self, street, player=None, amount=None):
595 if amount:
596 amount = amount.replace(u',', u'') #some sites have commas
597 log.debug(_("%s %s calls %s") %(street, player, amount))
598 # Potentially calculate the amount of the call if not supplied
599 # corner cases include if player would be all in
600 if amount is not None:
601 amount = Decimal(amount)
602 self.bets[street][player].append(amount)
603 #self.lastBet[street] = amount
604 self.stacks[player] -= amount
605 #print "DEBUG %s calls %s, stack %s" % (player, amount, self.stacks[player])
606 act = (player, 'calls', amount, self.stacks[player] == 0)
607 self.actions[street].append(act)
608 self.pot.addMoney(player, amount)
610 def addRaiseBy(self, street, player, amountBy):
611 """\
612 Add a raise by amountBy on [street] by [player]
614 #Given only the amount raised by, the amount of the raise can be calculated by
615 # working out how much this player has already in the pot
616 # (which is the sum of self.bets[street][player])
617 # and how much he needs to call to match the previous player
618 # (which is tracked by self.lastBet)
619 # let Bp = previous bet
620 # Bc = amount player has committed so far
621 # Rb = raise by
622 # then: C = Bp - Bc (amount to call)
623 # Rt = Bp + Rb (raise to)
625 amountBy = amountBy.replace(u',', u'') #some sites have commas
626 self.checkPlayerExists(player)
627 Rb = Decimal(amountBy)
628 Bp = self.lastBet[street]
629 Bc = sum(self.bets[street][player])
630 C = Bp - Bc
631 Rt = Bp + Rb
633 self._addRaise(street, player, C, Rb, Rt)
634 #~ self.bets[street][player].append(C + Rb)
635 #~ self.stacks[player] -= (C + Rb)
636 #~ self.actions[street] += [(player, 'raises', Rb, Rt, C, self.stacks[player]==0)]
637 #~ self.lastBet[street] = Rt
639 def addCallandRaise(self, street, player, amount):
640 """\
641 For sites which by "raises x" mean "calls and raises putting a total of x in the por". """
642 self.checkPlayerExists(player)
643 amount = amount.replace(u',', u'') #some sites have commas
644 CRb = Decimal(amount)
645 Bp = self.lastBet[street]
646 Bc = sum(self.bets[street][player])
647 C = Bp - Bc
648 Rb = CRb - C
649 Rt = Bp + Rb
651 self._addRaise(street, player, C, Rb, Rt)
653 def addRaiseTo(self, street, player, amountTo):
654 """\
655 Add a raise on [street] by [player] to [amountTo]
657 #CG - No idea if this function has been test/verified
658 self.checkPlayerExists(player)
659 amountTo = amountTo.replace(u',', u'') #some sites have commas
660 Bp = self.lastBet[street]
661 Bc = sum(self.bets[street][player])
662 Rt = Decimal(amountTo)
663 C = Bp - Bc
664 Rb = Rt - C - Bc
665 self._addRaise(street, player, C, Rb, Rt)
667 def _addRaise(self, street, player, C, Rb, Rt, action = 'raises'):
668 log.debug(_("%s %s raise %s") %(street, player, Rt))
669 self.bets[street][player].append(C + Rb)
670 self.stacks[player] -= (C + Rb)
671 act = (player, action, Rb, Rt, C, self.stacks[player]==0)
672 self.actions[street].append(act)
673 self.lastBet[street] = Rt # TODO check this is correct
674 self.pot.addMoney(player, C+Rb)
678 def addBet(self, street, player, amount):
679 log.debug(_("%s %s bets %s") %(street, player, amount))
680 amount = amount.replace(u',', u'') #some sites have commas
681 amount = Decimal(amount)
682 self.checkPlayerExists(player)
683 self.bets[street][player].append(amount)
684 self.stacks[player] -= amount
685 #print "DEBUG %s bets %s, stack %s" % (player, amount, self.stacks[player])
686 act = (player, 'bets', amount, self.stacks[player]==0)
687 self.actions[street].append(act)
688 self.lastBet[street] = amount
689 self.pot.addMoney(player, amount)
692 def addStandsPat(self, street, player, cards):
693 self.checkPlayerExists(player)
694 act = (player, 'stands pat')
695 self.actions[street].append(act)
696 if cards:
697 cards = cards.split(' ')
698 self.addHoleCards(street, player, open=[], closed=cards)
701 def addFold(self, street, player):
702 log.debug(_("%s %s folds") % (street, player))
703 self.checkPlayerExists(player)
704 self.folded.add(player)
705 self.pot.addFold(player)
706 self.actions[street].append((player, 'folds'))
709 def addCheck(self, street, player):
710 #print "DEBUG: %s %s checked" % (street, player)
711 logging.debug(_("%s %s checks") % (street, player))
712 self.checkPlayerExists(player)
713 self.actions[street].append((player, 'checks'))
716 def addCollectPot(self,player, pot):
717 log.debug("%s collected %s" % (player, pot))
718 self.checkPlayerExists(player)
719 self.collected = self.collected + [[player, pot]]
720 if player not in self.collectees:
721 self.collectees[player] = Decimal(pot)
722 else:
723 self.collectees[player] += Decimal(pot)
726 def addShownCards(self, cards, player, holeandboard=None, shown=True, mucked=False, string=None):
727 """\
728 For when a player shows cards for any reason (for showdown or out of choice).
729 Card ranks will be uppercased
731 log.debug(_("addShownCards %s hole=%s all=%s") % (player, cards, holeandboard))
732 if cards is not None:
733 self.addHoleCards(cards,player,shown, mucked)
734 if string is not None:
735 self.showdownStrings[player] = string
736 elif holeandboard is not None:
737 holeandboard = set([self.card(c) for c in holeandboard])
738 board = set([c for s in self.board.values() for c in s])
739 self.addHoleCards(holeandboard.difference(board),player,shown, mucked)
741 def totalPot(self):
742 """If all bets and blinds have been added, totals up the total pot size"""
744 # This gives us the total amount put in the pot
745 if self.totalpot is None:
746 self.pot.end()
747 self.totalpot = self.pot.total
749 # This gives us the amount collected, i.e. after rake
750 if self.totalcollected is None:
751 self.totalcollected = 0;
752 #self.collected looks like [[p1,amount][px,amount]]
753 for entry in self.collected:
754 self.totalcollected += Decimal(entry[1])
756 def getGameTypeAsString(self):
757 """\
758 Map the tuple self.gametype onto the pokerstars string describing it
760 # currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
761 gs = {"holdem" : "Hold'em",
762 "omahahi" : "Omaha",
763 "omahahilo" : "Omaha Hi/Lo",
764 "razz" : "Razz",
765 "studhi" : "7 Card Stud",
766 "studhilo" : "7 Card Stud Hi/Lo",
767 "fivedraw" : "5 Card Draw",
768 "27_1draw" : "Single Draw 2-7 Lowball",
769 "27_3draw" : "Triple Draw 2-7 Lowball",
770 "5studhi" : "5 Card Stud",
771 "badugi" : "Badugi"
773 ls = {"nl" : "No Limit",
774 "pl" : "Pot Limit",
775 "fl" : "Limit",
776 "cn" : "Cap No Limit",
777 "cp" : "Cap Pot Limit"
780 log.debug("gametype: %s" %(self.gametype))
781 retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
782 return retstring
785 def writeHand(self, fh=sys.__stdout__):
786 print >>fh, "Override me"
788 def printHand(self):
789 self.writeHand(sys.stdout)
791 def actionString(self, act, street=None):
792 if act[1] == 'folds':
793 return ("%s: folds " %(act[0]))
794 elif act[1] == 'checks':
795 return ("%s: checks " %(act[0]))
796 elif act[1] == 'calls':
797 return ("%s: calls %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
798 elif act[1] == 'bets':
799 return ("%s: bets %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
800 elif act[1] == 'raises':
801 return ("%s: raises %s%s to %s%s%s" %(act[0], self.sym, act[2], self.sym, act[3], ' and is all-in' if act[5] else ''))
802 elif act[1] == 'completea':
803 return ("%s: completes to %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
804 elif act[1] == 'posts':
805 if(act[2] == "small blind"):
806 return ("%s: posts small blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
807 elif(act[2] == "big blind"):
808 return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
809 elif(act[2] == "both"):
810 return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
811 elif(act[2] == "ante"):
812 return ("%s: posts the ante %s%s%s" %(act[0], self.sym, act[3], ' and is all-in' if act[4] else ''))
813 elif act[1] == 'bringin':
814 return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
815 elif act[1] == 'discards':
816 return ("%s: discards %s %s%s" %(act[0], act[2], 'card' if act[2] == 1 else 'cards' , " [" + " ".join(self.discards[street][act[0]]) + "]" if self.hero == act[0] else ''))
817 elif act[1] == 'stands pat':
818 return ("%s: stands pat" %(act[0]))
820 def getStakesAsString(self):
821 """Return a string of the stakes of the current hand."""
822 return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb)
824 def getStreetTotals(self):
825 pass
827 def writeGameLine(self):
828 """Return the first HH line for the current hand."""
829 gs = "PokerStars Game #%s: " % self.handid
831 if self.tourNo is not None and self.mixed is not None: # mixed tournament
832 gs = gs + "Tournament #%s, %s %s (%s) - Level %s (%s) - " % (self.tourNo, self.buyin, self.MS[self.mixed], self.getGameTypeAsString(), self.level, self.getStakesAsString())
833 elif self.tourNo is not None: # all other tournaments
834 gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
835 self.buyin, self.getGameTypeAsString(), self.level, self.getStakesAsString())
836 elif self.mixed is not None: # all other mixed games
837 gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
838 self.getGameTypeAsString(), self.getStakesAsString())
839 else: # non-mixed cash games
840 gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
842 try:
843 timestr = datetime.datetime.strftime(self.startTime, '%Y/%m/%d %H:%M:%S ET')
844 except TypeError:
845 print _("*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:"), self.startTime
846 print _("*** Make sure your HandHistoryConverter is setting hand.startTime properly!")
847 print _("*** Game String:"), gs
848 return gs
849 else:
850 return gs + timestr
852 def writeTableLine(self):
853 table_string = "Table "
854 if self.gametype['type'] == 'tour':
855 table_string = table_string + "\'%s %s\' %s-max" % (self.tourNo, self.tablename, self.maxseats)
856 else:
857 table_string = table_string + "\'%s\' %s-max" % (self.tablename, self.maxseats)
858 if self.gametype['currency'] == 'play':
859 table_string = table_string + " (Play Money)"
860 if self.buttonpos != None and self.buttonpos != 0:
861 table_string = table_string + " Seat #%s is the button" % self.buttonpos
862 return table_string
865 def writeHand(self, fh=sys.__stdout__):
866 # PokerStars format.
867 print >>fh, self.writeGameLine()
868 print >>fh, self.writeTableLine()
871 class HoldemOmahaHand(Hand):
872 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
873 self.config = config
874 if gametype['base'] != 'hold':
875 pass # or indeed don't pass and complain instead
876 log.debug("HoldemOmahaHand")
877 self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER']
878 self.holeStreets = ['PREFLOP']
879 self.communityStreets = ['FLOP', 'TURN', 'RIVER']
880 self.actionStreets = ['BLINDSANTES','PREFLOP','FLOP','TURN','RIVER']
881 Hand.__init__(self, self.config, sitename, gametype, handText, builtFrom = "HHC")
882 self.sb = gametype['sb']
883 self.bb = gametype['bb']
885 #Populate a HoldemOmahaHand
886 #Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
887 # which then invokes a 'addXXX' callback
888 if builtFrom == "HHC":
889 hhc.readHandInfo(self)
890 if self.gametype['type'] == 'tour':
891 self.tablename = "%s %s" % (self.tourNo, self.tablename)
892 hhc.readPlayerStacks(self)
893 hhc.compilePlayerRegexs(self)
894 hhc.markStreets(self)
896 if self.cancelled:
897 return
899 hhc.readBlinds(self)
901 hhc.readAntes(self)
902 hhc.readButton(self)
903 hhc.readHeroCards(self)
904 hhc.readShowdownActions(self)
905 # Read actions in street order
906 for street, text in self.streets.iteritems():
907 if text and (street is not "PREFLOP"): #TODO: the except PREFLOP shouldn't be necessary, but regression-test-files/cash/Everleaf/Flop/NLHE-10max-USD-0.01-0.02-201008.2Way.All-in.pre.txt fails without it
908 hhc.readCommunityCards(self, street)
909 for street in self.actionStreets:
910 if self.streets[street]:
911 hhc.readAction(self, street)
912 self.pot.markTotal(street)
913 hhc.readCollectPot(self)
914 hhc.readShownCards(self)
915 self.totalPot() # finalise it (total the pot)
916 hhc.getRake(self)
917 if self.maxseats is None:
918 self.maxseats = hhc.guessMaxSeats(self)
919 hhc.readOther(self)
920 #print "\nHand:\n"+str(self)
921 elif builtFrom == "DB":
922 #if handid is not None:
923 # self.select(handid) # Will need a handId
924 #else:
925 # log.warning(_("HoldemOmahaHand.__init__:Can't assemble hand from db without a handid"))
926 print "DEBUG: HoldemOmaha hand initialised for select()"
927 else:
928 log.warning(_("HoldemOmahaHand.__init__:Neither HHC nor DB+handid provided"))
929 pass
932 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
933 if player == self.hero: # we have hero's cards just update shown/mucked
934 if shown: self.shown.add(player)
935 if mucked: self.mucked.add(player)
936 else:
937 if len(cards) in (2, 4): # avoid adding board by mistake (Everleaf problem)
938 self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
939 elif len(cards) == 5: # cards holds a winning hand, not hole cards
940 # filter( lambda x: x not in b, a ) # calcs a - b where a and b are lists
941 # so diff is set to the winning hand minus the board cards, if we're lucky that leaves the hole cards
942 diff = filter( lambda x: x not in self.board['FLOP']+self.board['TURN']+self.board['RIVER'], cards )
943 if len(diff) == 2 and self.gametype['category'] in ('holdem'):
944 self.addHoleCards('PREFLOP', player, open=[], closed=diff, shown=shown, mucked=mucked, dealt=dealt)
945 if string is not None:
946 self.showdownStrings[player] = string
948 def getStreetTotals(self):
949 # street1Pot INT, /* pot size at flop/street4 */
950 # street2Pot INT, /* pot size at turn/street5 */
951 # street3Pot INT, /* pot size at river/street6 */
952 # street4Pot INT, /* pot size at sd/street7 */
953 # showdownPot INT, /* pot size at sd/street7 */
954 tmp1 = self.pot.getTotalAtStreet('FLOP')
955 tmp2 = self.pot.getTotalAtStreet('TURN')
956 tmp3 = self.pot.getTotalAtStreet('RIVER')
957 tmp4 = 0
958 tmp5 = 0
959 return (tmp1,tmp2,tmp3,tmp4,tmp5)
961 def join_holecards(self, player, asList=False):
962 """With asList = True it returns the set cards for a player including down cards if they aren't know"""
963 hcs = [u'0x', u'0x', u'0x', u'0x']
965 for street in self.holeStreets:
966 if player in self.holecards[street].keys():
967 hcs[0] = self.holecards[street][player][1][0]
968 hcs[1] = self.holecards[street][player][1][1]
969 try:
970 hcs[2] = self.holecards[street][player][1][2]
971 hcs[3] = self.holecards[street][player][1][3]
972 except IndexError:
973 pass
975 if asList == False:
976 return " ".join(hcs)
977 else:
978 return hcs
981 def writeHTMLHand(self):
982 from nevow import tags as T
983 from nevow import flat
984 players_who_act_preflop = (([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
985 players_stacks = [x for x in self.players if x[1] in players_who_act_preflop]
986 action_streets = [x for x in self.actionStreets if len(self.actions[x]) > 0]
987 def render_stack(context,data):
988 pat = context.tag.patternGenerator('list_item')
989 for player in data:
990 x = "Seat %s: %s (%s%s in chips) " %(player[0], player[1],
991 self.sym, player[2])
992 context.tag[ pat().fillSlots('playerStack', x)]
993 return context.tag
995 def render_street(context,data):
996 pat = context.tag.patternGenerator('list_item')
997 for street in data:
998 lines = []
999 if street in self.holeStreets and self.holecards[street]:
1000 lines.append(
1001 T.ol(class_='dealclosed', data=street,
1002 render=render_deal) [
1003 T.li(pattern='list_item')[ T.slot(name='deal') ]
1006 if street in self.communityStreets and self.board[street]:
1007 lines.append(
1008 T.ol(class_='community', data=street,
1009 render=render_deal_community)[
1010 T.li(pattern='list_item')[ T.slot(name='deal') ]
1013 if street in self.actionStreets and self.actions[street]:
1014 lines.append(
1015 T.ol(class_='actions', data=self.actions[street], render=render_action) [
1016 T.li(pattern='list_item')[ T.slot(name='action') ]
1019 if lines:
1020 context.tag[ pat().fillSlots('street', [ T.h3[ street ] ]+lines)]
1021 return context.tag
1023 def render_deal(context,data):
1024 # data is streetname
1025 # we can have open+closed, or just open, or just closed.
1027 if self.holecards[data]:
1028 for player in self.holecards[data]:
1029 somestuff = 'dealt to %s %s' % (player, self.holecards[data][player])
1030 pat = context.tag.patternGenerator('list_item')
1031 context.tag[ pat().fillSlots('deal', somestuff)]
1032 return context.tag
1034 def render_deal_community(context,data):
1035 # data is streetname
1036 if self.board[data]:
1037 somestuff = '[' + ' '.join(self.board[data]) + ']'
1038 pat = context.tag.patternGenerator('list_item')
1039 context.tag[ pat().fillSlots('deal', somestuff)]
1040 return context.tag
1041 def render_action(context,data):
1042 pat = context.tag.patternGenerator('list_item')
1043 for act in data:
1044 x = self.actionString(act)
1045 context.tag[ pat().fillSlots('action', x)]
1046 return context.tag
1048 s = T.p[
1049 T.h1[
1050 T.span(class_='site')["%s Game #%s]" % ('PokerStars', self.handid)],
1051 T.span(class_='type_limit')[ "%s ($%s/$%s)" %(self.getGameTypeAsString(), self.sb, self.bb) ],
1052 T.span(class_='date')[ datetime.datetime.strftime(self.startTime,'%Y/%m/%d - %H:%M:%S ET') ]
1054 T.h2[ "Table '%s' %d-max Seat #%s is the button" %(self.tablename,
1055 self.maxseats, self.buttonpos)],
1056 T.ol(class_='stacks', data = players_stacks, render=render_stack)[
1057 T.li(pattern='list_item')[ T.slot(name='playerStack') ]
1059 T.ol(class_='streets', data = self.allStreets,
1060 render=render_street)[
1061 T.li(pattern='list_item')[ T.slot(name='street')]
1064 import tidy
1066 options = dict(input_xml=True,
1067 output_xhtml=True,
1068 add_xml_decl=False,
1069 doctype='omit',
1070 indent='auto',
1071 tidy_mark=False)
1073 return str(tidy.parseString(flat.flatten(s), **options))
1076 def writeHand(self, fh=sys.__stdout__):
1077 # PokerStars format.
1078 super(HoldemOmahaHand, self).writeHand(fh)
1080 players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
1081 log.debug(self.actions['PREFLOP'])
1082 for player in [x for x in self.players if x[1] in players_who_act_preflop]:
1083 #Only print stacks of players who do something preflop
1084 print >>fh, ("Seat %s: %s ($%.2f in chips) " %(player[0], player[1], float(player[2])))
1086 if self.actions['BLINDSANTES']:
1087 for act in self.actions['BLINDSANTES']:
1088 print >>fh, self.actionString(act)
1090 print >>fh, ("*** HOLE CARDS ***")
1091 for player in self.dealt:
1092 print >>fh, ("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1])))
1093 if self.hero == "":
1094 for player in self.shown.difference(self.dealt):
1095 print >>fh, ("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1])))
1097 if self.actions['PREFLOP']:
1098 for act in self.actions['PREFLOP']:
1099 print >>fh, self.actionString(act)
1101 if self.board['FLOP']:
1102 print >>fh, ("*** FLOP *** [%s]" %( " ".join(self.board['FLOP'])))
1103 if self.actions['FLOP']:
1104 for act in self.actions['FLOP']:
1105 print >>fh, self.actionString(act)
1107 if self.board['TURN']:
1108 print >>fh, ("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN'])))
1109 if self.actions['TURN']:
1110 for act in self.actions['TURN']:
1111 print >>fh, self.actionString(act)
1113 if self.board['RIVER']:
1114 print >>fh, ("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) ))
1115 if self.actions['RIVER']:
1116 for act in self.actions['RIVER']:
1117 print >>fh, self.actionString(act)
1120 #Some sites don't have a showdown section so we have to figure out if there should be one
1121 # The logic for a showdown is: at the end of river action there are at least two players in the hand
1122 # we probably don't need a showdown section in pseudo stars format for our filtering purposes
1123 if self.shown:
1124 print >>fh, ("*** SHOW DOWN ***")
1125 for name in self.shown:
1126 # TODO: legacy importer can't handle only one holecard here, make sure there are 2 for holdem, 4 for omaha
1127 # TOOD: If HoldHand subclass supports more than omahahi, omahahilo, holdem, add them here
1128 numOfHoleCardsNeeded = None
1129 if self.gametype['category'] in ('omahahi','omahahilo'):
1130 numOfHoleCardsNeeded = 4
1131 elif self.gametype['category'] in ('holdem'):
1132 numOfHoleCardsNeeded = 2
1133 if len(self.holecards['PREFLOP'][name]) == numOfHoleCardsNeeded:
1134 print >>fh, ("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards['PREFLOP'][name][1])))
1136 # Current PS format has the lines:
1137 # Uncalled bet ($111.25) returned to s0rrow
1138 # s0rrow collected $5.15 from side pot
1139 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1140 # stervels collected $45.35 from main pot
1141 # Immediately before the summary.
1142 # The current importer uses those lines for importing winning rather than the summary
1143 for name in self.pot.returned:
1144 print >>fh, ("Uncalled bet (%s%s) returned to %s" %(self.sym, self.pot.returned[name],name))
1145 for entry in self.collected:
1146 print >>fh, ("%s collected %s%s from x pot" %(entry[0], self.sym, entry[1]))
1148 print >>fh, ("*** SUMMARY ***")
1149 print >>fh, "%s | Rake %s%.2f" % (self.pot, self.sym, self.rake)
1151 board = []
1152 for street in ["FLOP", "TURN", "RIVER"]:
1153 board += self.board[street]
1154 if board: # sometimes hand ends preflop without a board
1155 print >>fh, ("Board [%s]" % (" ".join(board)))
1157 for player in [x for x in self.players if x[1] in players_who_act_preflop]:
1158 seatnum = player[0]
1159 name = player[1]
1160 if name in self.collectees and name in self.shown:
1161 print >>fh, ("Seat %d: %s showed [%s] and won (%s%s)" % (seatnum, name, " ".join(self.holecards['PREFLOP'][name][1]), self.sym, self.collectees[name]))
1162 elif name in self.collectees:
1163 print >>fh, ("Seat %d: %s collected (%s%s)" % (seatnum, name, self.sym, self.collectees[name]))
1164 #~ elif name in self.shown:
1165 #~ print >>fh, _("Seat %d: %s showed [%s]" % (seatnum, name, " ".join(self.holecards[name]['PREFLOP'])))
1166 elif name in self.folded:
1167 print >>fh, ("Seat %d: %s folded" % (seatnum, name))
1168 else:
1169 if name in self.shown:
1170 print >>fh, ("Seat %d: %s showed [%s] and lost with..." % (seatnum, name, " ".join(self.holecards['PREFLOP'][name][1])))
1171 elif name in self.mucked:
1172 print >>fh, ("Seat %d: %s mucked [%s] " % (seatnum, name, " ".join(self.holecards['PREFLOP'][name][1])))
1173 else:
1174 print >>fh, ("Seat %d: %s mucked" % (seatnum, name))
1176 print >>fh, "\n\n"
1178 class DrawHand(Hand):
1179 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC"):
1180 self.config = config
1181 if gametype['base'] != 'draw':
1182 pass # or indeed don't pass and complain instead
1183 self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
1184 self.allStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
1185 self.holeStreets = ['DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
1186 self.actionStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE', 'DRAWTWO', 'DRAWTHREE']
1187 self.communityStreets = []
1188 Hand.__init__(self, self.config, sitename, gametype, handText)
1189 self.sb = gametype['sb']
1190 self.bb = gametype['bb']
1191 # Populate the draw hand.
1192 if builtFrom == "HHC":
1193 hhc.readHandInfo(self)
1194 if self.gametype['type'] == 'tour':
1195 self.tablename = "%s %s" % (self.tourNo, self.tablename)
1196 hhc.readPlayerStacks(self)
1197 hhc.compilePlayerRegexs(self)
1198 hhc.markStreets(self)
1199 # markStreets in Draw may match without dealing cards
1200 if self.streets['DEAL'] == None:
1201 raise FpdbParseError(_("DrawHand.__init__: street 'DEAL' is empty. Hand cancelled? HandID: '%s'") % self.handid)
1202 hhc.readBlinds(self)
1203 hhc.readAntes(self)
1204 hhc.readButton(self)
1205 hhc.readHeroCards(self)
1206 hhc.readShowdownActions(self)
1207 # Read actions in street order
1208 for street in self.streetList:
1209 if self.streets[street]:
1210 hhc.readAction(self, street)
1211 self.pot.markTotal(street)
1212 hhc.readCollectPot(self)
1213 hhc.readShownCards(self)
1214 self.totalPot() # finalise it (total the pot)
1215 hhc.getRake(self)
1216 if self.maxseats is None:
1217 self.maxseats = hhc.guessMaxSeats(self)
1218 hhc.readOther(self)
1219 elif builtFrom == "DB":
1220 self.select("dummy") # Will need a handId
1222 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
1223 if player == self.hero: # we have hero's cards just update shown/mucked
1224 if shown: self.shown.add(player)
1225 if mucked: self.mucked.add(player)
1226 else:
1227 # TODO: Probably better to find the last street with action and add the hole cards to that street
1228 self.addHoleCards('DRAWTHREE', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
1229 if string is not None:
1230 self.showdownStrings[player] = string
1233 def discardDrawHoleCards(self, cards, player, street):
1234 log.debug("discardDrawHoleCards '%s' '%s' '%s'" % (cards, player, street))
1235 self.discards[street][player] = set([cards])
1238 def addDiscard(self, street, player, num, cards):
1239 self.checkPlayerExists(player)
1240 if cards:
1241 act = (player, 'discards', Decimal(num), cards)
1242 self.discardDrawHoleCards(cards, player, street)
1243 else:
1244 act = (player, 'discards', Decimal(num))
1245 self.actions[street].append(act)
1247 def holecardsAsSet(self, street, player):
1248 """Return holdcards: (oc, nc) as set()"""
1249 (nc,oc) = self.holecards[street][player]
1250 nc = set(nc)
1251 oc = set(oc)
1252 return (nc, oc)
1254 def getStreetTotals(self):
1255 # street1Pot INT, /* pot size at flop/street4 */
1256 # street2Pot INT, /* pot size at turn/street5 */
1257 # street3Pot INT, /* pot size at river/street6 */
1258 # street4Pot INT, /* pot size at sd/street7 */
1259 # showdownPot INT, /* pot size at sd/street7 */
1260 return (0,0,0,0,0)
1262 def join_holecards(self, player, asList=False):
1263 """With asList = True it returns the set cards for a player including down cards if they aren't know"""
1264 # FIXME: This should actually return
1265 holecards = [u'0x']*20
1267 for i, street in enumerate(self.holeStreets):
1268 if player in self.holecards[street].keys():
1269 allhole = self.holecards[street][player][1] + self.holecards[street][player][0]
1270 for c in range(len(allhole)):
1271 idx = c + (i*5)
1272 holecards[idx] = allhole[c]
1274 if asList == False:
1275 return " ".join(holecards)
1276 else:
1277 return holecards
1280 def writeHand(self, fh=sys.__stdout__):
1281 # PokerStars format.
1282 # HH output should not be translated
1283 super(DrawHand, self).writeHand(fh)
1285 players_who_act_ondeal = set(([x[0] for x in self.actions['DEAL']]+[x[0] for x in self.actions['BLINDSANTES']]))
1287 for player in [x for x in self.players if x[1] in players_who_act_ondeal]:
1288 #Only print stacks of players who do something on deal
1289 print >>fh, (("Seat %s: %s (%s%s in chips) ") % (player[0], player[1], self.sym, player[2]))
1291 if 'BLINDSANTES' in self.actions:
1292 for act in self.actions['BLINDSANTES']:
1293 print >>fh, ("%s: %s %s %s%s" % (act[0], act[1], act[2], self.sym, act[3]))
1295 if 'DEAL' in self.actions:
1296 print >>fh, ("*** DEALING HANDS ***")
1297 for player in [x[1] for x in self.players if x[1] in players_who_act_ondeal]:
1298 if 'DEAL' in self.holecards:
1299 if self.holecards['DEAL'].has_key(player):
1300 (nc,oc) = self.holecards['DEAL'][player]
1301 print >>fh, ("Dealt to %s: [%s]") % (player, " ".join(nc))
1302 for act in self.actions['DEAL']:
1303 print >>fh, self.actionString(act, 'DEAL')
1305 if 'DRAWONE' in self.actions:
1306 print >>fh, ("*** FIRST DRAW ***")
1307 for act in self.actions['DRAWONE']:
1308 print >>fh, self.actionString(act, 'DRAWONE')
1309 if act[0] == self.hero and act[1] == 'discards':
1310 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1311 dc = self.discards['DRAWONE'][act[0]]
1312 kc = oc - dc
1313 print >>fh, (("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc)))
1315 if 'DRAWTWO' in self.actions:
1316 print >>fh, ("*** SECOND DRAW ***")
1317 for act in self.actions['DRAWTWO']:
1318 print >>fh, self.actionString(act, 'DRAWTWO')
1319 if act[0] == self.hero and act[1] == 'discards':
1320 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1321 dc = self.discards['DRAWTWO'][act[0]]
1322 kc = oc - dc
1323 print >>fh, (("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc)))
1325 if 'DRAWTHREE' in self.actions:
1326 print >>fh, ("*** THIRD DRAW ***")
1327 for act in self.actions['DRAWTHREE']:
1328 print >>fh, self.actionString(act, 'DRAWTHREE')
1329 if act[0] == self.hero and act[1] == 'discards':
1330 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1331 dc = self.discards['DRAWTHREE'][act[0]]
1332 kc = oc - dc
1333 print >>fh, (("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc)))
1335 if 'SHOWDOWN' in self.actions:
1336 print >>fh, ("*** SHOW DOWN ***")
1337 #TODO: Complete SHOWDOWN
1339 # Current PS format has the lines:
1340 # Uncalled bet ($111.25) returned to s0rrow
1341 # s0rrow collected $5.15 from side pot
1342 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1343 # stervels collected $45.35 from main pot
1344 # Immediately before the summary.
1345 # The current importer uses those lines for importing winning rather than the summary
1346 for name in self.pot.returned:
1347 print >>fh, ("Uncalled bet (%s%s) returned to %s" % (self.sym, self.pot.returned[name],name))
1348 for entry in self.collected:
1349 print >>fh, ("%s collected %s%s from x pot" % (entry[0], self.sym, entry[1]))
1351 print >>fh, ("*** SUMMARY ***")
1352 print >>fh, "%s | Rake %s%.2f" % (self.pot, self.sym, self.rake)
1353 print >>fh, "\n\n"
1357 class StudHand(Hand):
1358 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC"):
1359 self.config = config
1360 if gametype['base'] != 'stud':
1361 pass # or indeed don't pass and complain instead
1363 self.allStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1364 self.communityStreets = []
1365 self.actionStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1367 self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
1368 self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1369 Hand.__init__(self, self.config, sitename, gametype, handText)
1370 self.sb = gametype['sb']
1371 self.bb = gametype['bb']
1372 #Populate the StudHand
1373 #Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
1374 # which then invokes a 'addXXX' callback
1375 if builtFrom == "HHC":
1376 hhc.readHandInfo(self)
1377 if self.gametype['type'] == 'tour':
1378 self.tablename = "%s %s" % (self.tourNo, self.tablename)
1379 hhc.readPlayerStacks(self)
1380 hhc.compilePlayerRegexs(self)
1381 hhc.markStreets(self)
1382 hhc.readAntes(self)
1383 hhc.readBringIn(self)
1384 hhc.readHeroCards(self)
1385 # Read actions in street order
1386 for street in self.actionStreets:
1387 if street == 'BLINDSANTES': continue # OMG--sometime someone folds in the ante round
1388 if self.streets[street]:
1389 log.debug(street + self.streets[street])
1390 hhc.readAction(self, street)
1391 self.pot.markTotal(street)
1392 hhc.readCollectPot(self)
1393 hhc.readShownCards(self) # not done yet
1394 self.totalPot() # finalise it (total the pot)
1395 hhc.getRake(self)
1396 if self.maxseats is None:
1397 self.maxseats = hhc.guessMaxSeats(self)
1398 hhc.readOther(self)
1399 elif builtFrom == "DB":
1400 self.select("dummy") # Will need a handId
1402 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
1403 if player == self.hero: # we have hero's cards just update shown/mucked
1404 if shown: self.shown.add(player)
1405 if mucked: self.mucked.add(player)
1406 else:
1407 self.addHoleCards('THIRD', player, open=[cards[2]], closed=cards[0:2], shown=shown, mucked=mucked)
1408 self.addHoleCards('FOURTH', player, open=[cards[3]], closed=[cards[2]], shown=shown, mucked=mucked)
1409 self.addHoleCards('FIFTH', player, open=[cards[4]], closed=cards[2:4], shown=shown, mucked=mucked)
1410 self.addHoleCards('SIXTH', player, open=[cards[5]], closed=cards[2:5], shown=shown, mucked=mucked)
1411 if len(cards) > 6:
1412 self.addHoleCards('SEVENTH', player, open=[], closed=[cards[6]], shown=shown, mucked=mucked)
1413 if string is not None:
1414 self.showdownStrings[player] = string
1417 def addPlayerCards(self, player, street, open=[], closed=[]):
1418 """\
1419 Assigns observed cards to a player.
1420 player (string) name of player
1421 street (string) the street name (in streetList)
1422 open list of card bigrams e.g. ['2h','Jc'], dealt face up
1423 closed likewise, but known only to player
1425 log.debug("addPlayerCards %s, o%s x%s" % (player, open, closed))
1426 try:
1427 self.checkPlayerExists(player)
1428 self.holecards[street][player] = (open, closed)
1429 except FpdbParseError, e:
1430 print _("[ERROR] Tried to add holecards for unknown player: %s") % (player,)
1432 # TODO: def addComplete(self, player, amount):
1433 def addComplete(self, street, player, amountTo):
1434 # assert street=='THIRD'
1435 # This needs to be called instead of addRaiseTo, and it needs to take account of self.lastBet['THIRD'] to determine the raise-by size
1436 """\
1437 Add a complete on [street] by [player] to [amountTo]
1439 log.debug(_("%s %s completes %s") % (street, player, amountTo))
1440 amountTo = amountTo.replace(u',', u'') #some sites have commas
1441 self.checkPlayerExists(player)
1442 Bp = self.lastBet['THIRD']
1443 Bc = sum(self.bets[street][player])
1444 Rt = Decimal(amountTo)
1445 C = Bp - Bc
1446 Rb = Rt - C
1447 self._addRaise(street, player, C, Rb, Rt, 'completes')
1448 #~ self.bets[street][player].append(C + Rb)
1449 #~ self.stacks[player] -= (C + Rb)
1450 #~ act = (player, 'raises', Rb, Rt, C, self.stacks[player]==0)
1451 #~ self.actions[street].append(act)
1452 #~ self.lastBet[street] = Rt # TODO check this is correct
1453 #~ self.pot.addMoney(player, C+Rb)
1455 def addBringIn(self, player, bringin):
1456 if player is not None:
1457 log.debug(_("Bringin: %s, %s") % (player , bringin))
1458 bringin = bringin.replace(u',', u'') #some sites have commas
1459 bringin = Decimal(bringin)
1460 self.bets['THIRD'][player].append(bringin)
1461 self.stacks[player] -= bringin
1462 act = (player, 'bringin', bringin, self.stacks[player]==0)
1463 self.actions['THIRD'].append(act)
1464 self.lastBet['THIRD'] = bringin
1465 self.pot.addMoney(player, bringin)
1467 def getStreetTotals(self):
1468 # street1Pot INT, /* pot size at flop/street4 */
1469 # street2Pot INT, /* pot size at turn/street5 */
1470 # street3Pot INT, /* pot size at river/street6 */
1471 # street4Pot INT, /* pot size at sd/street7 */
1472 # showdownPot INT, /* pot size at sd/street7 */
1473 return (0,0,0,0,0)
1476 def writeHand(self, fh=sys.__stdout__):
1477 # PokerStars format.
1478 # HH output should not be translated
1479 super(StudHand, self).writeHand(fh)
1481 players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']])
1483 for player in [x for x in self.players if x[1] in players_who_post_antes]:
1484 #Only print stacks of players who do something preflop
1485 print >>fh, ("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2]))
1487 if 'BLINDSANTES' in self.actions:
1488 for act in self.actions['BLINDSANTES']:
1489 print >>fh, ("%s: posts the ante %s%s" %(act[0], self.sym, act[3]))
1491 if 'THIRD' in self.actions:
1492 dealt = 0
1493 #~ print >>fh, ("*** 3RD STREET ***")
1494 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1495 if self.holecards['THIRD'].has_key(player):
1496 (open, closed) = self.holecards['THIRD'][player]
1497 dealt+=1
1498 if dealt==1:
1499 print >>fh, ("*** 3RD STREET ***")
1500 # print >>fh, ("Dealt to %s:%s%s") % (player, " [" + " ".join(closed) + "] " if closed else " ", "[" + " ".join(open) + "]" if open else "")
1501 print >>fh, self.writeHoleCards('THIRD', player)
1502 for act in self.actions['THIRD']:
1503 #FIXME: Need some logic here for bringin vs completes
1504 print >>fh, self.actionString(act)
1506 if 'FOURTH' in self.actions:
1507 dealt = 0
1508 #~ print >>fh, ("*** 4TH STREET ***")
1509 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1510 if player in self.holecards['FOURTH']:
1511 dealt+=1
1512 if dealt==1:
1513 print >>fh, ("*** 4TH STREET ***")
1514 print >>fh, self.writeHoleCards('FOURTH', player)
1515 for act in self.actions['FOURTH']:
1516 print >>fh, self.actionString(act)
1518 if 'FIFTH' in self.actions:
1519 dealt = 0
1520 #~ print >>fh, ("*** 5TH STREET ***")
1521 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1522 if self.holecards['FIFTH'].has_key(player):
1523 dealt+=1
1524 if dealt==1:
1525 print >>fh, ("*** 5TH STREET ***")
1526 print >>fh, self.writeHoleCards('FIFTH', player)
1527 for act in self.actions['FIFTH']:
1528 print >>fh, self.actionString(act)
1530 if 'SIXTH' in self.actions:
1531 dealt = 0
1532 #~ print >>fh, ("*** 6TH STREET ***")
1533 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1534 if self.holecards['SIXTH'].has_key(player):
1535 dealt += 1
1536 if dealt == 1:
1537 print >>fh, ("*** 6TH STREET ***")
1538 print >>fh, self.writeHoleCards('SIXTH', player)
1539 for act in self.actions['SIXTH']:
1540 print >>fh, self.actionString(act)
1542 if 'SEVENTH' in self.actions:
1543 # OK. It's possible that they're all in at an earlier street, but only closed cards are dealt.
1544 # Then we have no 'dealt to' lines, no action lines, but still 7th street should appear.
1545 # The only way I can see to know whether to print this line is by knowing the state of the hand
1546 # i.e. are all but one players folded; is there an allin showdown; and all that.
1547 print >>fh, ("*** RIVER ***")
1548 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1549 if self.holecards['SEVENTH'].has_key(player):
1550 if self.writeHoleCards('SEVENTH', player):
1551 print >>fh, self.writeHoleCards('SEVENTH', player)
1552 for act in self.actions['SEVENTH']:
1553 print >>fh, self.actionString(act)
1555 #Some sites don't have a showdown section so we have to figure out if there should be one
1556 # The logic for a showdown is: at the end of river action there are at least two players in the hand
1557 # we probably don't need a showdown section in pseudo stars format for our filtering purposes
1558 if 'SHOWDOWN' in self.actions:
1559 print >>fh, ("*** SHOW DOWN ***")
1560 # TODO: print showdown lines.
1562 # Current PS format has the lines:
1563 # Uncalled bet ($111.25) returned to s0rrow
1564 # s0rrow collected $5.15 from side pot
1565 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1566 # stervels collected $45.35 from main pot
1567 # Immediately before the summary.
1568 # The current importer uses those lines for importing winning rather than the summary
1569 for name in self.pot.returned:
1570 print >>fh, ("Uncalled bet (%s%s) returned to %s" %(self.sym, self.pot.returned[name],name))
1571 for entry in self.collected:
1572 print >>fh, ("%s collected %s%s from x pot" %(entry[0], self.sym, entry[1]))
1574 print >>fh, ("*** SUMMARY ***")
1575 print >>fh, "%s | Rake %s%.2f" % (self.pot, self.sym, self.rake)
1576 # TODO: side pots
1578 board = []
1579 for s in self.board.values():
1580 board += s
1581 if board: # sometimes hand ends preflop without a board
1582 print >>fh, ("Board [%s]" % (" ".join(board)))
1584 for player in [x for x in self.players if x[1] in players_who_post_antes]:
1585 seatnum = player[0]
1586 name = player[1]
1587 if name in self.collectees and name in self.shown:
1588 print >>fh, ("Seat %d: %s showed [%s] and won (%s%s)" % (seatnum, name, self.join_holecards(name), self.sym, self.collectees[name]))
1589 elif name in self.collectees:
1590 print >>fh, ("Seat %d: %s collected (%s%s)" % (seatnum, name, self.sym, self.collectees[name]))
1591 elif name in self.shown:
1592 print >>fh, ("Seat %d: %s showed [%s]" % (seatnum, name, self.join_holecards(name)))
1593 elif name in self.mucked:
1594 print >>fh, ("Seat %d: %s mucked [%s]" % (seatnum, name, self.join_holecards(name)))
1595 elif name in self.folded:
1596 print >>fh, ("Seat %d: %s folded" % (seatnum, name))
1597 else:
1598 print >>fh, ("Seat %d: %s mucked" % (seatnum, name))
1600 print >>fh, "\n\n"
1603 def writeHoleCards(self, street, player):
1604 hc = "Dealt to %s [" % player
1605 if street == 'THIRD':
1606 if player == self.hero:
1607 return hc + " ".join(self.holecards[street][player][1]) + " " + " ".join(self.holecards[street][player][0]) + ']'
1608 else:
1609 return hc + " ".join(self.holecards[street][player][0]) + ']'
1611 if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
1612 return hc + " ".join(self.holecards[street][player][1]) + "] [" + " ".join(self.holecards[street][player][0]) + "]"
1614 def join_holecards(self, player, asList=False):
1615 """Function returns a string for the stud writeHand method by default
1616 With asList = True it returns the set cards for a player including down cards if they aren't know"""
1617 holecards = []
1618 for street in self.holeStreets:
1619 if self.holecards[street].has_key(player):
1620 if street == 'THIRD':
1621 holecards = holecards + self.holecards[street][player][1] + self.holecards[street][player][0]
1622 elif street == 'SEVENTH':
1623 if player == self.hero:
1624 holecards = holecards + self.holecards[street][player][0]
1625 else:
1626 holecards = holecards + self.holecards[street][player][1]
1627 else:
1628 holecards = holecards + self.holecards[street][player][0]
1630 if asList == False:
1631 return " ".join(holecards)
1632 else:
1633 if player == self.hero or len(holecards) == 7:
1634 return holecards
1635 elif len(holecards) <= 4:
1636 #Non hero folded before showdown, add first two downcards
1637 holecards = [u'0x', u'0x'] + holecards
1638 else:
1639 log.warning(_("join_holecards: # of holecards should be either < 4, 4 or 7 - 5 and 6 should be impossible for anyone who is not a hero"))
1640 log.warning(_("join_holcards: holecards(%s): %s") %(player, holecards))
1641 if holecards == [u'0x', u'0x']:
1642 log.warning(_("join_holecards: Player '%s' appears not to have been dealt a card"))
1643 # If a player is listed but not dealt a card in a cash game this can occur
1644 # Noticed in FTP Razz hand. Return 3 empty cards in this case
1645 holecards = [u'0x', u'0x', u'0x']
1646 return holecards
1649 class Pot(object):
1652 def __init__(self):
1653 self.contenders = set()
1654 self.committed = {}
1655 self.streettotals = {}
1656 self.common = {}
1657 self.total = None
1658 self.returned = {}
1659 self.sym = u'$' # this is the default currency symbol
1661 def setSym(self, sym):
1662 self.sym = sym
1664 def addPlayer(self,player):
1665 self.committed[player] = Decimal(0)
1666 self.common[player] = Decimal(0)
1668 def addFold(self, player):
1669 # addFold must be called when a player folds
1670 self.contenders.discard(player)
1672 def addCommonMoney(self, player, amount):
1673 self.common[player] += amount
1675 def addMoney(self, player, amount):
1676 # addMoney must be called for any actions that put money in the pot, in the order they occur
1677 self.contenders.add(player)
1678 self.committed[player] += amount
1680 def markTotal(self, street):
1681 self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values())
1683 def getTotalAtStreet(self, street):
1684 if street in self.streettotals:
1685 return self.streettotals[street]
1686 return 0
1688 def end(self):
1689 self.total = sum(self.committed.values()) + sum(self.common.values())
1691 # Return any uncalled bet.
1692 committed = sorted([ (v,k) for (k,v) in self.committed.items()])
1693 #print "DEBUG: committed: %s" % committed
1694 #ERROR below. lastbet is correct in most cases, but wrong when
1695 # additional money is committed to the pot in cash games
1696 # due to an additional sb being posted. (Speculate that
1697 # posting sb+bb is also potentially wrong)
1698 lastbet = committed[-1][0] - committed[-2][0]
1699 if lastbet > 0: # uncalled
1700 returnto = committed[-1][1]
1701 #print "DEBUG: returning %f to %s" % (lastbet, returnto)
1702 self.total -= lastbet
1703 self.committed[returnto] -= lastbet
1704 self.returned[returnto] = lastbet
1707 # Work out side pots
1708 commitsall = sorted([(v,k) for (k,v) in self.committed.items() if v >0])
1710 self.pots = []
1711 try:
1712 while len(commitsall) > 0:
1713 commitslive = [(v,k) for (v,k) in commitsall if k in self.contenders]
1714 v1 = commitslive[0][0]
1715 self.pots += [sum([min(v,v1) for (v,k) in commitsall])]
1716 commitsall = [((v-v1),k) for (v,k) in commitsall if v-v1 >0]
1717 except IndexError, e:
1718 log.error(_("Pot.end(): Major failure while calculating pot: '%s'") % e)
1719 raise FpdbParseError(_("Pot.end(): Major failure while calculating pot: '%s'") % e)
1721 # TODO: I think rake gets taken out of the pots.
1722 # so it goes:
1723 # total pot x. main pot y, side pot z. | rake r
1724 # and y+z+r = x
1725 # for example:
1726 # Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2
1728 def __str__(self):
1729 if self.sym is None:
1730 self.sym = "C"
1731 if self.total is None:
1732 print (_("DEBUG:") + " " + _("call Pot.end() before printing pot total"))
1733 # NB if I'm sure end() is idempotent, call it here.
1734 raise FpdbParseError(_("Error in printing Hand object"))
1736 ret = "Total pot %s%.2f" % (self.sym, self.total)
1737 if len(self.pots) < 2:
1738 return ret;
1739 ret += " Main pot %s%.2f" % (self.sym, self.pots[0])
1741 return ret + ''.join([ (" Side pot %s%.2f." % (self.sym, self.pots[x]) ) for x in xrange(1, len(self.pots)) ])