Coverage for Hand.py: 10%
1394 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +0000
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.
8#
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.
13#
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.
17from __future__ import print_function
23#import L10n
24#_ = L10n.get_translation()
26# TODO: get writehand() encoding correct
28import sys
29from decimal_wrapper import Decimal
30import datetime
32import pprint
34import logging
35# logging has been set up in fpdb.py or HUD_main.py, use their settings:
36log = logging.getLogger("parser")
38import Configuration
39from Exceptions import FpdbHandDuplicate, FpdbHandPartial, FpdbParseError
40import DerivedStats
41import Card
42Configuration.set_logfile("fpdb-log.txt")
44class Hand(object):
45 UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K', 'S':'s', 'C':'c', 'H':'h', 'D':'d'}
46 LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
47 SYMBOL = {'USD': '$', 'CAD': 'C$', 'EUR': u'€', 'GBP': u'£', 'SEK': 'kr.', 'RSD': u'РСД', 'mBTC': u'ⓑ', 'INR': u'₹', 'CNY': u'¥', 'T$': '', 'play': ''}
48 MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
49 ACTION = {'ante': 1, 'small blind': 2, 'secondsb': 3, 'big blind': 4, 'both': 5, 'calls': 6, 'raises': 7,
50 'bets': 8, 'stands pat': 9, 'folds': 10, 'checks': 11, 'discards': 12, 'bringin': 13, 'completes': 14,
51 'straddle': 15, 'button blind': 16, 'cashout': 17}
53 def __init__(self, config, sitename, gametype, handText, builtFrom = "HHC"):
54 self.config = config
55 self.saveActions = self.config.get_import_parameters().get('saveActions')
56 self.callHud = self.config.get_import_parameters().get("callFpdbHud")
57 self.cacheSessions = self.config.get_import_parameters().get("cacheSessions")
58 self.publicDB = self.config.get_import_parameters().get("publicDB")
59 self.sitename = sitename
60 self.siteId = self.config.get_site_id(sitename)
61 self.stats = DerivedStats.DerivedStats()
62 self.gametype = gametype
63 self.startTime = 0
64 self.handText = handText
65 self.handid = 0
66 self.in_path = None
67 self.cancelled = False
68 self.dbid_hands = 0
69 self.dbid_pids = None
70 self.dbid_hpid = None
71 self.dbid_gt = 0
72 self.tablename = ""
73 self.hero = ""
74 self.maxseats = None
75 self.counted_seats = 0
76 self.buttonpos = 0
77 self.runItTimes = 0
78 self.uncalledbets = False
79 self.checkForUncalled = False
80 self.adjustCollected = False
81 self.cashedOut = False
83 # tourney stuff
84 self.tourNo = None
85 self.tourneyId = None
86 self.tourneyName = None
87 self.tourneyTypeId = None
88 self.buyin = None
89 self.buyinCurrency = None
90 self.buyInChips = None
91 self.fee = None # the Database code is looking for this one .. ?
92 self.level = None
93 self.mixed = None
94 self.speed = "Normal"
95 self.isSng = False
96 self.isRebuy = False
97 self.rebuyCost = 0
98 self.isAddOn = False
99 self.addOnCost = 0
100 self.isKO = False
101 self.koBounty = 0
102 self.isProgressive = False
103 self.isMatrix = False
104 self.isShootout = False
105 self.isFast = False
106 self.stack = "Regular"
107 self.isStep = False
108 self.stepNo = 0
109 self.isChance = False
110 self.chanceCount = 0
111 self.isMultiEntry = False
112 self.isReEntry = False
113 self.isNewToGame = False
114 self.isHomeGame = False
115 self.isSplit = False
116 self.isFifty50 = False
117 self.isTime = False
118 self.timeAmt = 0
119 self.isSatellite = False
120 self.isDoubleOrNothing = False
121 self.isCashOut = False
122 self.isOnDemand = False
123 self.isFlighted = False
124 self.isGuarantee = False
125 self.guaranteeAmt = 0
126 self.added = None
127 self.addedCurrency = None
128 self.entryId = 1
130 self.seating = []
131 self.players = []
132 # Cache used for checkPlayerExists.
133 self.player_exists_cache = set()
134 self.posted = []
135 self.tourneysPlayersIds = {}
137 # Collections indexed by street names
138 self.bets = {}
139 self.lastBet = {}
140 self.streets = {}
141 self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']]
142 self.board = {} # dict from street names to community cards
143 self.holecards = {}
144 self.discards = {}
145 self.showdownStrings = {}
146 for street in self.allStreets:
147 self.streets[street] = "" # portions of the handText, filled by markStreets()
148 self.actions[street] = []
149 for street in self.actionStreets:
150 self.bets[street] = {}
151 self.lastBet[street] = 0
152 self.board[street] = []
153 for street in self.holeStreets:
154 self.holecards[street] = {} # dict from player names to holecards
155 for street in self.discardStreets:
156 self.discards[street] = {} # dict from player names to dicts by street ... of tuples ... of discarded holecards
157 # Collections indexed by player names
158 self.rakes = {}
159 self.stacks = {}
160 self.collected = [] #list of ?
161 self.collectees = {} # dict from player names to amounts collected (?)
162 self.koCounts = {}
163 self.endBounty = {}
165 # Sets of players
166 self.folded = set()
167 self.dealt = set() # 'dealt to' line to be printed
168 self.shown = set() # cards were shown
169 self.mucked = set() # cards were mucked at showdown
170 self.sitout = set() # players sitting out or not dealt in (usually tournament)
172 # Things to do with money
173 self.pot = Pot()
174 self.totalpot = None
175 self.totalcollected = None
176 self.rake = None
177 self.roundPenny = False
178 self.fastFold = False
179 # currency symbol for this hand
180 self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
181 self.pot.setSym(self.sym)
182 self.is_duplicate = False # i.e. don't update hudcache if true
184 def __str__(self):
185 vars = ( (("BB"), self.bb),
186 (("SB"), self.sb),
187 (("BUTTON POS"), self.buttonpos),
188 (("HAND NO."), self.handid),
189 (("SITE"), self.sitename),
190 (("TABLE NAME"), self.tablename),
191 (("HERO"), self.hero),
192 (("MAX SEATS"), self.maxseats),
193 (("LEVEL"), self.level),
194 (("MIXED"), self.mixed),
195 (("LAST BET"), self.lastBet),
196 (("ACTION STREETS"), self.actionStreets),
197 (("STREETS"), self.streets),
198 (("ALL STREETS"), self.allStreets),
199 (("COMMUNITY STREETS"), self.communityStreets),
200 (("HOLE STREETS"), self.holeStreets),
201 (("COUNTED SEATS"), self.counted_seats),
202 (("DEALT"), self.dealt),
203 (("SHOWN"), self.shown),
204 (("MUCKED"), self.mucked),
205 (("TOTAL POT"), self.totalpot),
206 (("TOTAL COLLECTED"), self.totalcollected),
207 (("RAKE"), self.rake),
208 (("START TIME"), self.startTime),
209 (("TOURNAMENT NO"), self.tourNo),
210 (("TOURNEY ID"), self.tourneyId),
211 (("TOURNEY TYPE ID"), self.tourneyTypeId),
212 (("BUYIN"), self.buyin),
213 (("BUYIN CURRENCY"), self.buyinCurrency),
214 (("BUYIN CHIPS"), self.buyInChips),
215 (("FEE"), self.fee),
216 (("IS REBUY"), self.isRebuy),
217 (("IS ADDON"), self.isAddOn),
218 (("IS KO"), self.isKO),
219 (("KO BOUNTY"), self.koBounty),
220 (("IS MATRIX"), self.isMatrix),
221 (("IS SHOOTOUT"), self.isShootout),
222 )
224 structs = ( (("PLAYERS"), self.players),
225 (("STACKS"), self.stacks),
226 (("POSTED"), self.posted),
227 (("POT"), self.pot),
228 (("SEATING"), self.seating),
229 (("GAMETYPE"), self.gametype),
230 (("ACTION"), self.actions),
231 (("COLLECTEES"), self.collectees),
232 (("BETS"), self.bets),
233 (("BOARD"), self.board),
234 (("DISCARDS"), self.discards),
235 (("HOLECARDS"), self.holecards),
236 (("TOURNEYS PLAYER IDS"), self.tourneysPlayersIds),
237 )
238 result = ''
239 for (name, var) in vars:
240 result = result + "\n%s = " % name + pprint.pformat(var)
242 for (name, struct) in structs:
243 result = result + "\n%s =\n" % name + pprint.pformat(struct, 4)
244 return result
246 def addHoleCards(self, street, player, open=[], closed=[], shown=False, mucked=False, dealt=False):
247 """ Assigns observed holecards to a player.
248 cards list of card bigrams e.g. ['2h','Jc']
249 player (string) name of player
250 shown whether they were revealed at showdown
251 mucked whether they were mucked at showdown
252 dealt whether they were seen in a 'dealt to' line """
253 log.debug("Hand.addHoleCards open+closed: %s, player: %s, shown: %s, mucked: %s, dealt: %s",
254 open + closed, player, shown, mucked, dealt)
255 self.checkPlayerExists(player, 'addHoleCards')
257 if dealt: self.dealt.add(player)
258 if shown: self.shown.add(player)
259 if mucked: self.mucked.add(player)
261 for i in range(len(closed)):
262 if closed[i] in ('', 'Xx', 'Null', 'null', 'X'):
263 closed[i] = '0x'
265 try:
266 self.holecards[street][player] = [open, closed]
267 except KeyError as e:
268 log.error(("Hand.addHoleCards: '%s': Major failure while adding holecards: '%s'"), self.handid, e)
269 raise FpdbParseError
271 def prepInsert(self, db, printtest = False):
272 #####
273 # Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables
274 # These functions are intended for prep insert eventually
275 #####
276 if self.gametype.get('maxSeats') == None:
277 self.gametype['maxSeats'] = self.maxseats #TODO: move up to individual parsers
278 else:
279 self.maxseats = self.gametype['maxSeats']
280 self.dbid_pids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId, self.hero)
281 self.dbid_gt = db.getSqlGameTypeId(self.siteId, self.gametype, printdata = printtest)
283 # Gametypes
284 hilo = Card.games[self.gametype['category']][2]
286 self.gametyperow = (self.siteId, self.gametype['currency'], self.gametype['type'], self.gametype['base'],
287 self.gametype['category'], self.gametype['limitType'], hilo, self.gametype['mix'],
288 int(Decimal(self.gametype['sb'])*100), int(Decimal(self.gametype['bb'])*100),
289 int(Decimal(self.gametype['bb'])*100), int(Decimal(self.gametype['bb'])*200),
290 int(self.gametype['maxSeats']), int(self.gametype['ante']*100),
291 self.gametype['buyinType'], self.gametype['fast'],
292 self.gametype['newToGame'], self.gametype['homeGame'],
293 self.gametype['split'])
294 # Note: the above data is calculated in db.getGameTypeId
295 # Only being calculated above so we can grab the testdata
296 if self.tourNo is not None:
297 self.tourneyTypeId = db.getSqlTourneyTypeIDs(self)
298 self.tourneyId = db.getSqlTourneyIDs(self)
299 self.tourneysPlayersIds = db.getSqlTourneysPlayersIDs(self)
301 def assembleHand(self):
302 self.stats.getStats(self)
303 self.hands = self.stats.getHands()
304 self.handsplayers = self.stats.getHandsPlayers()
305 self.handsactions = self.stats.getHandsActions()
306 self.handsstove = self.stats.getHandsStove()
307 self.handspots = self.stats.getHandsPots()
309 def getHandId(self, db, id):
310 if db.isDuplicate(self.siteId, self.hands['siteHandNo'], self.hands['heroSeat'], self.publicDB):
311 #log.debug(("Hand.insert(): hid #: %s is a duplicate") % self.hands['siteHandNo'])
312 self.is_duplicate = True # i.e. don't update hudcache
313 next = id
314 if self.publicDB:
315 raise FpdbHandDuplicate("%s-%s-%s" % (str(self.siteId), str(self.hands['siteHandNo']), self.hands['heroSeat']))
316 else:
317 raise FpdbHandDuplicate("%s-%s" % (str(self.siteId), str(self.hands['siteHandNo'])))
318 else:
319 self.dbid_hands = id
320 self.hands['id'] = self.dbid_hands
321 next = id + db.hand_inc
322 return next
324 def insertHands(self, db, fileId, doinsert = False, printtest = False):
325 """ Function to insert Hand into database
326 Should not commit, and do minimal selects. Callers may want to cache commits
327 db: a connected Database object"""
328 self.hands['gametypeId'] = self.dbid_gt
329 self.hands['seats'] = len(self.dbid_pids)
330 self.hands['fileId'] = fileId
331 db.storeHand(self.hands, doinsert, printtest)
332 db.storeBoards(self.dbid_hands, self.hands['boards'], doinsert)
334 def insertHandsPlayers(self, db, doinsert = False, printtest = False):
335 """ Function to inserts HandsPlayers into database"""
336 db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, self.handsplayers, doinsert, printtest)
337 if self.handspots:
338 self.handspots.sort(key=lambda x: x[1])
339 for ht in self.handspots:
340 ht[0] = self.dbid_hands
341 db.storeHandsPots(self.handspots, doinsert)
343 def insertHandsActions(self, db, doinsert = False, printtest = False):
344 """ Function to inserts HandsActions into database"""
345 if self.saveActions:
346 db.storeHandsActions(self.dbid_hands, self.dbid_pids, self.handsactions, doinsert, printtest)
348 def insertHandsStove(self, db, doinsert = False):
349 """ Function to inserts HandsStove into database"""
350 if self.handsstove:
351 for hs in self.handsstove: hs[0] = self.dbid_hands
352 db.storeHandsStove(self.handsstove, doinsert)
354 def updateTourneyResults(self, db):
355 """ Function to update Tourney Bounties if any"""
356 db.updateTourneyPlayerBounties(self)
358 def updateHudCache(self, db, doinsert = False):
359 """ Function to update the HudCache"""
360 if self.callHud:
361 db.storeHudCache(self.dbid_gt, self.gametype, self.dbid_pids, self.startTime, self.handsplayers, doinsert)
363 def updateSessionsCache(self, db, tz, doinsert = False):
364 """ Function to update the Sessions"""
365 if self.cacheSessions:
366 heroes = [list(self.dbid_pids.values())[0]]
367 db.storeSessions(self.dbid_hands, self.dbid_pids, self.startTime, self.tourneyId, heroes, tz, doinsert)
368 db.storeSessionsCache(self.dbid_hands, self.dbid_pids, self.startTime, self.dbid_gt, self.gametype, self.handsplayers, heroes, doinsert)
369 db.storeTourneysCache(self.dbid_hands, self.dbid_pids, self.startTime, self.tourneyId, self.gametype, self.handsplayers, heroes, doinsert)
371 def updateCardsCache(self, db, tz, doinsert = False):
372 """ Function to update the CardsCache"""
373 if self.cacheSessions: # and self.hero in self.dbid_pids:
374 heroes = [list(self.dbid_pids.values())[0]]
375 db.storeCardsCache(self.dbid_hands, self.dbid_pids, self.startTime, self.dbid_gt, self.tourneyTypeId, self.handsplayers, heroes, tz, doinsert)
377 def updatePositionsCache(self, db, tz, doinsert = False):
378 """ Function to update the PositionsCache"""
379 if self.cacheSessions: # and self.hero in self.dbid_pids:
380 heroes = [list(self.dbid_pids.values())[0]]
381 db.storePositionsCache(self.dbid_hands, self.dbid_pids, self.startTime, self.dbid_gt, self.tourneyTypeId, self.handsplayers, self.hands, heroes, tz, doinsert)
383 def select(self, db, handId):
384 """ Function to create Hand object from database """
385 c = db.get_cursor()
386 q = db.sql.query['playerHand']
387 q = q.replace('%s', db.sql.query['placeholder'])
389 c.execute("select heroseat from Hands where id = {}".format(handId))
390 heroSeat = c.fetchone()[0]
392 # PlayerStacks
393 c.execute(q, (handId,))
394 # See NOTE: below on what this does.
396 # Discripter must be set to lowercase as postgres returns all descriptors lower case and SQLight returns them as they are
397 res = [dict(line) for line in [list(zip([ column[0].lower() for column in c.description], row)) for row in c.fetchall()]]
398 for row in res:
399 self.addPlayer(row['seatno'],row['name'],str(row['chips']), str(row['position']), row['sitout'], str(row['bounty']))
400 cardlist = []
401 cardlist.append(Card.valueSuitFromCard(row['card1']))
402 cardlist.append(Card.valueSuitFromCard(row['card2']))
403 cardlist.append(Card.valueSuitFromCard(row['card3']))
404 cardlist.append(Card.valueSuitFromCard(row['card4']))
405 cardlist.append(Card.valueSuitFromCard(row['card5']))
406 cardlist.append(Card.valueSuitFromCard(row['card6']))
407 cardlist.append(Card.valueSuitFromCard(row['card7']))
408 cardlist.append(Card.valueSuitFromCard(row['card8']))
409 cardlist.append(Card.valueSuitFromCard(row['card9']))
410 cardlist.append(Card.valueSuitFromCard(row['card10']))
411 cardlist.append(Card.valueSuitFromCard(row['card11']))
412 cardlist.append(Card.valueSuitFromCard(row['card12']))
413 cardlist.append(Card.valueSuitFromCard(row['card13']))
414 cardlist.append(Card.valueSuitFromCard(row['card14']))
415 cardlist.append(Card.valueSuitFromCard(row['card15']))
416 cardlist.append(Card.valueSuitFromCard(row['card16']))
417 cardlist.append(Card.valueSuitFromCard(row['card17']))
418 cardlist.append(Card.valueSuitFromCard(row['card18']))
419 cardlist.append(Card.valueSuitFromCard(row['card19']))
420 cardlist.append(Card.valueSuitFromCard(row['card20']))
421 # mucked/shown/dealt is not in the database, use mucked for villain and dealt for hero
422 if row['seatno'] == heroSeat:
423 dealt=True
424 mucked=False
425 else:
426 dealt=False
427 mucked=True
428 game = Card.games[self.gametype['category']]
429 if game[0] == 'hold' and cardlist[0] != '':
430 self.addHoleCards('PREFLOP', row['name'], closed=cardlist[0:game[5][0][1]], shown=False, mucked=mucked, dealt=dealt)
431 elif game[0] == 'stud' and cardlist[2] != '':
432 streets = dict((v, k) for (k, v) in list(game[3].items()))
433 for streetidx, hrange in enumerate(game[5]):
434 # FIXME shown/dealt/mucked might need some tweaking
435 self.addHoleCards(streets[streetidx], row['name'], open=[cardlist[hrange[1] - 1]], closed=cardlist[0:hrange[1]-1], shown=False, mucked=False)
436 elif game[0] == 'draw':
437 streets = dict((v, k) for (k, v) in list(game[3].items()))
438 for streetidx, hrange in enumerate(game[5]):
439 self.addHoleCards(streets[streetidx], row['name'], closed=cardlist[hrange[0]:hrange[1]], shown=False, mucked=mucked, dealt=dealt)
440 if row['winnings'] > 0:
441 self.addCollectPot(row['name'], str(row['winnings']))
442 if row['position'] == '0':
443 # position 0 is the button, heads-up there is no position 0
444 self.buttonpos = row['seatno']
445 elif row['position'] == 'B':
446 # Headsup the BB is the button, only set the button position if it's not set before
447 if self.buttonpos is None or self.buttonpos == 0:
448 self.buttonpos = row['seatno']
451 # HandInfo
452 q = db.sql.query['singleHand']
453 q = q.replace('%s', db.sql.query['placeholder'])
454 c.execute(q, (handId,))
456 # NOTE: This relies on row_factory = sqlite3.Row (set in connect() params)
457 # Need to find MySQL and Postgres equivalents
458 # MySQL maybe: cursorclass=MySQLdb.cursors.DictCursor
459 #res = c.fetchone()
461 # Using row_factory is global, and affects the rest of fpdb. The following 2 line achieves
462 # a similar result
464 # Discripter must be set to lowercase as supported dbs differ on what is returned.
465 res = [dict(line) for line in [list(zip([ column[0].lower() for column in c.description], row)) for row in c.fetchall()]]
466 res = res[0]
468 self.tablename = res['tablename']
469 self.handid = res['sitehandno']
470 # FIXME: Need to figure out why some times come out of the DB as %Y-%m-%d %H:%M:%S+00:00,
471 # and others as %Y-%m-%d %H:%M:%S
473 # self.startTime currently unused in the replayer and commented out.
474 # Can't be done like this because not all dbs return the same type for starttime
475 #try:
476 # self.startTime = datetime.datetime.strptime(res['starttime'], "%Y-%m-%d %H:%M:%S+00:00")
477 #except ValueError:
478 # self.startTime = datetime.datetime.strptime(res['starttime'], "%Y-%m-%d %H:%M:%S")
479 # However a startTime is needed for a valid output by writeHand:
480 self.startTime = datetime.datetime.strptime("1970-01-01 12:00:00", "%Y-%m-%d %H:%M:%S")
482 cards = list(map(Card.valueSuitFromCard, [res['boardcard1'], res['boardcard2'], res['boardcard3'], res['boardcard4'], res['boardcard5']]))
483 if cards[0]:
484 self.setCommunityCards('FLOP', cards[0:3])
485 if cards[3]:
486 self.setCommunityCards('TURN', [cards[3]])
487 if cards[4]:
488 self.setCommunityCards('RIVER', [cards[4]])
490 if res['runittwice'] or self.gametype['split']:
491 # Get runItTwice boards
492 q = db.sql.query['singleHandBoards']
493 q = q.replace('%s', db.sql.query['placeholder'])
494 c.execute(q, (handId,))
495 boards = [dict(line) for line in [list(zip([ column[0].lower() for column in c.description], row)) for row in c.fetchall()]]
496 for b in boards:
497 cards = list(map(Card.valueSuitFromCard, [b['boardcard1'], b['boardcard2'], b['boardcard3'], b['boardcard4'], b['boardcard5']]))
498 if cards[0]:
499 street = 'FLOP' + str(b['boardid'])
500 self.setCommunityCards(street, cards[0:3])
501 if 'FLOP' in self.allStreets:
502 self.allStreets.remove('FLOP')
503 self.allStreets.append(street)
504 self.actions[street] = []
505 if cards[3]:
506 street = 'TURN' + str(b['boardid'])
507 self.setCommunityCards(street, [cards[3]])
508 if 'TURN' in self.allStreets:
509 self.allStreets.remove('TURN')
510 self.allStreets.append(street)
511 self.actions[street] = []
512 if cards[4]:
513 street = 'RIVER' + str(b['boardid'])
514 self.setCommunityCards(street, [cards[4]])
515 if 'RIVER' in self.allStreets:
516 self.allStreets.remove('RIVER')
517 self.allStreets.append(street)
518 self.actions[street] = []
520 # playersVpi | playersAtStreet1 | playersAtStreet2 | playersAtStreet3 |
521 # playersAtStreet4 | playersAtShowdown | street0Raises | street1Raises |
522 # street2Raises | street3Raises | street4Raises | street1Pot | street2Pot |
523 # street3Pot | street4Pot | showdownPot | comment | commentTs | texture
525 # Actions
526 q = db.sql.query['handActions']
527 q = q.replace('%s', db.sql.query['placeholder'])
528 c.execute(q, (handId,))
530 # Discripter must be set to lowercase as supported dbs differ on what is returned.
531 res = [dict(line) for line in [list(zip([ column[0].lower() for column in c.description], row)) for row in c.fetchall()]]
532 for row in res:
533 name = row['name']
534 street = row['street']
535 act = row['actionid']
536 # allin True/False if row['allIn'] == 0
537 bet = str(row['bet'])
538 street = self.allStreets[int(street)+1]
539 discards = row['cardsdiscarded']
540 log.debug("Hand.select():: name: '%s' street: '%s' act: '%s' bet: '%s'",
541 name, street, act, bet)
542 if act == 1: # Ante
543 self.addAnte(name, bet)
544 elif act == 2: # Small Blind
545 self.addBlind(name, 'small blind', bet)
546 elif act == 3: # Second small blind
547 self.addBlind(name, 'secondsb', bet)
548 elif act == 4: # Big Blind
549 self.addBlind(name, 'big blind', bet)
550 elif act == 5: # Post both blinds
551 self.addBlind(name, 'both', bet)
552 elif act == 6: # Call
553 self.addCall(street, name, bet)
554 elif act == 7: # Raise
555 self.addRaiseBy(street, name, bet)
556 elif act == 8: # Bet
557 self.addBet(street, name, bet)
558 elif act == 9: # Stands pat
559 self.addStandsPat(street, name, discards)
560 elif act == 10: # Fold
561 self.addFold(street, name)
562 elif act == 11: # Check
563 self.addCheck(street, name)
564 elif act == 12: # Discard
565 self.addDiscard(street, name, row['numdiscarded'], discards)
566 elif act == 13: # Bringin
567 self.addBringIn(name, bet)
568 elif act == 14: # Complete
569 self.addComplete(street, name, bet)
570 elif act == 15:
571 self.addBlind(name, 'straddle', bet)
572 elif act == 16:
573 self.addBlind(name, 'button blind', bet)
574 elif act == 17: # Cashout
575 self.addCashout(street, name)
576 else:
577 print("DEBUG: unknown action: '%s'" % act)
579 self.totalPot()
580 self.rake = self.totalpot - self.totalcollected
582 def addPlayer(self, seat, name, chips, position=None, sitout=False, bounty=None):
583 """ Adds a player to the hand, and initialises data structures indexed by player.
584 seat (int) indicating the seat
585 name (string) player name
586 chips (string) the chips the player has at the start of the hand (can be None)
587 position (string) indicating the position of the player (S,B, 0-7) (optional, not needed on Hand import from Handhistory).
588 If a player has None chips he won't be added."""
590 if len(self.players) > 0 and seat in [p[0] for p in self.players]:
591 raise FpdbHandPartial("addPlayer: " + ("Can't have 2 players in the same seat!") + ": '%s'" % self.handid)
593 log.debug("addPlayer: %s %s (%s)", seat, name, chips)
594 if chips is not None:
595 chips = chips.replace(u',', u'') #some sites have commas
596 self.players.append([seat, name, chips, position, bounty])
597 self.stacks[name] = Decimal(chips)
598 self.pot.addPlayer(name)
599 for street in self.actionStreets:
600 self.bets[street][name] = []
601 if sitout:
602 self.sitout.add(name)
604 def removePlayer(self, name):
605 if self.stacks.get(name):
606 self.players = [p for p in self.players if p[1]!=name]
607 del self.stacks[name]
608 self.pot.removePlayer(name)
609 for street in self.actionStreets:
610 del self.bets[street][name]
611 self.sitout.discard(name)
613 def addStreets(self, match):
614 # go through m and initialise actions to empty list for each street.
615 if match:
616 #print('if match', match)
617 #print("if match.gr:",match.groupdict())
618 print("type self.streets", type(self.streets))
619 self.streets.update(match.groupdict())
620 print('streets:',str(self.streets))
621 log.debug("markStreets:\n"+ str(self.streets))
622 else:
623 tmp = self.handText[0:100]
624 self.cancelled = True
625 raise FpdbHandPartial(("Streets didn't match - Assuming hand '%s' was cancelled.") % (self.handid) + " " + ("First 100 characters: %s") % tmp)
627 def checkPlayerExists(self,player,source = None):
628 # Fast path, because this function is called ALL THE TIME.
629 if player in self.player_exists_cache:
630 return
632 if player not in (p[1] for p in self.players):
633 if source is not None:
634 log.error(("Hand.%s: '%s' unknown player: '%s'"), source, self.handid, player)
635 raise FpdbParseError
636 else:
637 self.player_exists_cache.add(player)
639 def setCommunityCards(self, street, cards):
640 log.debug("setCommunityCards %s %s", street, cards)
641 self.board[street] = [self.card(c) for c in cards]
643 def card(self,c):
644 """upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
645 for k,v in list(self.UPS.items()):
646 c = c.replace(k,v)
647 return c
649 def addAllIn(self, street, player, amount):
650 """ For sites (currently only Merge & Microgaming) which record "all in" as a special action,
651 which can mean either "calls and is all in" or "raises all in"."""
652 self.checkPlayerExists(player, 'addAllIn')
653 amount = amount.replace(u',', u'') #some sites have commas
654 Ai = Decimal(amount)
655 Bp = self.lastBet[street]
656 Bc = sum(self.bets[street][player])
657 C = Bp - Bc
658 if Ai <= C:
659 self.addCall(street, player, amount)
660 elif Bp == 0:
661 self.addBet(street, player, amount)
662 else:
663 Rb = Ai - C
664 Rt = Bp + Rb
665 self._addRaise(street, player, C, Rb, Rt)
667 def addSTP(self, amount):
668 amount = amount.replace(u',', u'') #some sites have commas
669 amount = Decimal(amount)
670 self.pot.setSTP(amount)
672 def addAnte(self, player, ante):
673 log.debug("%s %s antes %s", 'BLINDSANTES', player, ante)
674 if player is not None:
675 ante = ante.replace(u',', u'') #some sites have commas
676 self.checkPlayerExists(player, 'addAnte')
677 ante = Decimal(ante)
678 self.bets['BLINDSANTES'][player].append(ante)
679 self.stacks[player] -= ante
680 act = (player, 'ante', ante, self.stacks[player]==0)
681 self.actions['BLINDSANTES'].append(act)
682 self.pot.addCommonMoney(player, ante)
683 self.pot.addAntes(player, ante)
684 if 'ante' not in list(self.gametype.keys()) or self.gametype['ante'] < ante:
685 self.gametype['ante'] = ante
686# I think the antes should be common money, don't have enough hand history to check
688 def addBlind(self, player, blindtype, amount):
689 # if player is None, it's a missing small blind.
690 # The situation we need to cover are:
691 # Player in small blind posts
692 # - this is a bet of 1 sb, as yet uncalled.
693 # Player in the big blind posts
694 # - this is a call of 1 sb and a raise to 1 bb
695 #
696 log.debug("addBlind: %s posts %s, %s", player, blindtype, amount)
697 if player is not None:
698 self.checkPlayerExists(player, 'addBlind')
699 amount = amount.replace(u',', u'') #some sites have commas
700 amount = Decimal(amount)
701 self.stacks[player] -= amount
702 act = (player, blindtype, amount, self.stacks[player]==0)
703 self.actions['BLINDSANTES'].append(act)
705 if blindtype == 'both':
706 # work with the real amount. limit games are listed as $1, $2, where
707 # the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2....
708 amount = Decimal(str(self.bb))
709 sb = Decimal(str(self.sb))
710 self.bets['BLINDSANTES'][player].append(sb)
711 self.pot.addCommonMoney(player, sb)
713 if blindtype == 'secondsb':
714 amount = Decimal(0)
715 sb = Decimal(str(self.sb))
716 self.bets['BLINDSANTES'][player].append(sb)
717 self.pot.addCommonMoney(player, sb)
719 street = 'BLAH'
721 if self.gametype['category'] == 'aof_omaha':
722 street = 'FLOP'
723 elif self.gametype['base'] == 'hold':
724 street = 'PREFLOP'
725 elif self.gametype['base'] == 'draw':
726 street = 'DEAL'
728 self.bets[street][player].append(amount)
729 self.pot.addMoney(player, amount)
730 if amount>self.lastBet.get(street):
731 self.lastBet[street] = amount
732 self.posted = self.posted + [[player,blindtype]]
735 def addCall(self, street, player=None, amount=None):
736 if amount is not None:
737 amount = amount.replace(u',', u'') #some sites have commas
738 log.debug(("%s %s calls %s"), street, player, amount)
739 # Potentially calculate the amount of the call if not supplied
740 # corner cases include if player would be all in
741 if amount is not None:
742 self.checkPlayerExists(player, 'addCall')
743 amount = Decimal(amount)
744 self.bets[street][player].append(amount)
745 if street in ('PREFLOP', 'DEAL', 'THIRD') and self.lastBet.get(street)<amount:
746 self.lastBet[street] = amount
747 self.stacks[player] -= amount
748 act = (player, 'calls', amount, self.stacks[player] == 0)
749 self.actions[street].append(act)
750 self.pot.addMoney(player, amount)
752 def addCallTo(self, street, player=None, amountTo=None):
753 if amountTo:
754 amountTo = amountTo.replace(u',', u'') #some sites have commas
755 # Potentially calculate the amount of the callTo if not supplied
756 # corner cases include if player would be all in
757 if amountTo is not None:
758 self.checkPlayerExists(player, 'addCallTo')
759 Bc = sum(self.bets[street][player])
760 Ct = Decimal(amountTo)
761 C = Ct - Bc
762 amount = C
763 self.bets[street][player].append(amount)
764 self.stacks[player] -= amount
765 act = (player, 'calls', amount, self.stacks[player] == 0)
766 self.actions[street].append(act)
767 self.pot.addMoney(player, amount)
769 def addRaiseBy(self, street, player, amountBy):
770 """ Add a raise by amountBy on [street] by [player] """
771 # Given only the amount raised by, the amount of the raise can be calculated by
772 # working out how much this player has already in the pot
773 # (which is the sum of self.bets[street][player])
774 # and how much he needs to call to match the previous player
775 # (which is tracked by self.lastBet)
776 # let Bp = previous bet
777 # Bc = amount player has committed so far
778 # Rb = raise by
779 # then: C = Bp - Bc (amount to call)
780 # Rt = Bp + Rb (raise to)
781 #
782 amountBy = amountBy.replace(u',', u'') #some sites have commas
783 self.checkPlayerExists(player, 'addRaiseBy')
784 Rb = Decimal(amountBy)
785 Bp = self.lastBet[street]
786 Bc = sum(self.bets[street][player])
787 C = Bp - Bc
788 Rt = Bp + Rb
790 self._addRaise(street, player, C, Rb, Rt)
792 def addCallandRaise(self, street, player, amount):
793 """ For sites which by "raises x" mean "calls and raises putting a total of x in the por". """
794 self.checkPlayerExists(player, 'addCallandRaise')
795 amount = amount.replace(u',', u'') #some sites have commas
796 CRb = Decimal(amount)
797 Bp = self.lastBet[street]
798 Bc = sum(self.bets[street][player])
799 C = Bp - Bc
800 Rb = CRb - C
801 Rt = Bp + Rb
803 self._addRaise(street, player, C, Rb, Rt)
805 def addRaiseTo(self, street, player, amountTo):
806 """ Add a raise on [street] by [player] to [amountTo] """
807 self.checkPlayerExists(player, 'addRaiseTo')
808 amountTo = amountTo.replace(u',', u'') #some sites have commas
809 Bp = self.lastBet[street]
810 Bc = sum(self.bets[street][player])
811 Rt = Decimal(amountTo)
812 C = Bp - Bc
813 Rb = Rt - C - Bc
814 self._addRaise(street, player, C, Rb, Rt)
816 def _addRaise(self, street, player, C, Rb, Rt, action = 'raises'):
817 log.debug(("%s %s raise %s"), street, player, Rt)
818 self.bets[street][player].append(C + Rb)
819 self.stacks[player] -= (C + Rb)
820 act = (player, action, Rb, Rt, C, self.stacks[player]==0)
821 self.actions[street].append(act)
822 self.lastBet[street] = Rt # TODO check this is correct
823 self.pot.addMoney(player, C+Rb)
825 def addBet(self, street, player, amount):
826 log.debug(("%s %s bets %s"), street, player, amount)
827 amount = amount.replace(u',', u'') #some sites have commas
828 amount = Decimal(amount)
829 self.checkPlayerExists(player, 'addBet')
830 self.bets[street][player].append(amount)
831 self.stacks[player] -= amount
832 act = (player, 'bets', amount, self.stacks[player]==0)
833 self.actions[street].append(act)
834 self.lastBet[street] = amount
835 self.pot.addMoney(player, amount)
837 def addStandsPat(self, street, player, cards=None):
838 self.checkPlayerExists(player, 'addStandsPat')
839 act = (player, 'stands pat')
840 self.actions[street].append(act)
841 if cards:
842 cards = cards.split(' ')
843 self.addHoleCards(street, player, open=[], closed=cards)
845 def addFold(self, street, player):
846 log.debug(("%s %s folds"), street, player)
847 self.checkPlayerExists(player, 'addFold')
848 if player in self.folded:
849 return
850 self.folded.add(player)
851 self.pot.addFold(player)
852 self.actions[street].append((player, 'folds'))
854 def addCheck(self, street, player):
855 logging.debug(("%s %s checks"), street, player)
856 self.checkPlayerExists(player, 'addCheck')
857 self.actions[street].append((player, 'checks'))
859 def addCashout(self, street, player):
860 logging.debug(("%s %s cashout"), street, player)
861 self.checkPlayerExists(player, 'addCashout')
862 self.actions[street].append((player, 'cashout'))
864 def discardDrawHoleCards(self, cards, player, street):
865 log.debug("discardDrawHoleCards '%s' '%s' '%s'", cards, player, street)
866 self.discards[street][player] = set([cards])
868 def addDiscard(self, street, player, num, cards=None):
869 self.checkPlayerExists(player, 'addDiscard')
870 if cards:
871 act = (player, 'discards', Decimal(num), cards)
872 self.discardDrawHoleCards(cards, player, street)
873 else:
874 act = (player, 'discards', Decimal(num))
875 self.actions[street].append(act)
877 def addCollectPot(self,player, pot):
878 log.debug("%s collected %s", player, pot)
879 self.checkPlayerExists(player, 'addCollectPot')
880 self.collected = self.collected + [[player, pot]]
881 if player not in self.collectees:
882 self.collectees[player] = Decimal(pot)
883 else:
884 self.collectees[player] += Decimal(pot)
886 def addUncalled(self, street, player, amount):
887 log.debug(("%s %s uncalled %s"), street, player, amount)
888 amount = amount.replace(u',', u'') #some sites have commas
889 amount = Decimal(amount)
890 self.checkPlayerExists(player, 'addUncalled')
891 self.stacks[player] += amount
892 self.pot.removeMoney(player, amount)
894 def sittingOut(self):
895 dealtIn = set()
896 for street in self.actionStreets:
897 for act in self.actions[street]:
898 dealtIn.add(act[0])
899 for player in list(self.collectees.keys()):
900 dealtIn.add(player)
901 for player in self.dealt:
902 dealtIn.add(player)
903 for p in list(self.players):
904 if p[1] not in dealtIn:
905 if self.gametype['type']=='tour':
906 self.sitout.add(p[1])
907 else:
908 self.removePlayer(p[1])
909 if len(self.players)<2:
910 raise FpdbHandPartial(("Less than 2 players - Assuming hand '%s' was cancelled.") % (self.handid))
912 def setUncalledBets(self, value):
913 self.uncalledbets = value
915 def totalPot(self):
916 """ If all bets and blinds have been added, totals up the total pot size"""
917 if self.totalpot is None:
918 try:
919 self.pot.end()
920 self.totalpot = self.pot.total
921 except FpdbParseError as e:
922 log.error("Error in totalPot calculation: %s", e)
923 self.totalpot = 0
925 if self.adjustCollected:
926 self.stats.awardPots(self)
928 def gettempcontainers(collected, collectees):
929 collectedCopy, collecteesCopy, totalcollected = [], {}, 0
930 for v in sorted(collected, key=lambda collectee: collectee[1], reverse=True):
931 if Decimal(v[1]) != 0:
932 totalcollected += Decimal(v[1])
933 collectedCopy.append([v[0], Decimal(v[1])])
934 for k, j in collectees.items():
935 if j != 0:
936 collecteesCopy[k] = j
937 return collectedCopy, collecteesCopy, totalcollected
939 collected, collectees, totalcollected = gettempcontainers(self.collected, self.collectees)
940 if (self.uncalledbets or ((self.totalpot - totalcollected < 0) and self.checkForUncalled)):
941 for i, v in enumerate(sorted(self.collected, key=lambda collectee: collectee[1], reverse=True)):
942 if v[0] in self.pot.returned:
943 collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
944 collectees[v[0]] -= self.pot.returned[v[0]]
945 self.pot.returned[v[0]] = 0
946 self.collected, self.collectees, self.totalcollected = gettempcontainers(collected, collectees)
948 if self.totalcollected is None:
949 self.totalcollected = 0
950 for entry in self.collected:
951 self.totalcollected += Decimal(entry[1])
953 def getGameTypeAsString(self):
954 """ Map the tuple self.gametype onto the pokerstars string describing it """
955 # currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
956 gs = {"holdem" : "Hold'em",
957 "omahahi" : "Omaha",
958 "fusion" : "Fusion",
959 "omahahilo" : "Omaha Hi/Lo",
960 "razz" : "Razz",
961 "studhi" : "7 Card Stud",
962 "studhilo" : "7 Card Stud Hi/Lo",
963 "fivedraw" : "5 Card Draw",
964 "27_1draw" : "Single Draw 2-7 Lowball",
965 "27_3draw" : "Triple Draw 2-7 Lowball",
966 "5_studhi" : "5 Card Stud",
967 "badugi" : "Badugi"
968 }
969 ls = {"nl" : "No Limit",
970 "pl" : "Pot Limit",
971 "fl" : "Limit",
972 "cn" : "Cap No Limit",
973 "cp" : "Cap Pot Limit"
974 }
976 log.debug("gametype: %s", self.gametype)
977 retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
978 return retstring
980 def printHand(self):
981 self.writeHand(sys.stdout)
983 def actionString(self, act, street=None):
984 log.debug("Hand.actionString(act=%s, street=%s)", act, street)
986 if act[1] == 'folds':
987 return ("%s: folds " %(act[0]))
988 elif act[1] == 'checks':
989 return ("%s: checks " %(act[0]))
990 elif act[1] == 'cashout':
991 return ("%s: cashout " %(act[0]))
992 elif act[1] == 'calls':
993 return ("%s: calls %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
994 elif act[1] == 'bets':
995 return ("%s: bets %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
996 elif act[1] == 'raises':
997 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 ''))
998 elif act[1] == 'completes':
999 return ("%s: completes to %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1000 elif(act[1] == "small blind"):
1001 return ("%s: posts small blind %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1002 elif(act[1] == "big blind"):
1003 return ("%s: posts big blind %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1004 elif(act[1] == "straddle"):
1005 return ("%s: straddles %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1006 elif(act[1] == "button blind"):
1007 return ("%s: posts button blind %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1008 elif(act[1] == "both"):
1009 return ("%s: posts small & big blinds %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1010 elif(act[1] == "ante"):
1011 return ("%s: posts the ante %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1012 elif act[1] == 'bringin':
1013 return ("%s: brings in for %s%s%s" %(act[0], self.sym, act[2], ' and is all-in' if act[3] else ''))
1014 elif act[1] == 'discards':
1015 return ("%s: discards %s %s%s" %(act[0], act[2],
1016 'card' if act[2] == 1 else 'cards',
1017 " [" + " ".join(self.discards[street][act[0]]) + "]" if self.hero == act[0] else ''))
1018 elif act[1] == 'stands pat':
1019 return ("%s: stands pat" %(act[0]))
1021 def get_actions_short(self, player, street):
1022 """ Returns a string with shortcuts for the actions of the given player and the given street
1023 F ... fold, X ... Check, B ...Bet, C ... Call, R ... Raise, CO ... CashOut
1024 """
1025 actions = self.actions[street]
1026 result = []
1027 for action in actions:
1028 if player in action:
1029 if action[1] == 'folds':
1030 result.append('F')
1031 elif action[1] == 'checks':
1032 result.append('X')
1033 elif action[1] == 'bets':
1034 result.append('B')
1035 elif action[1] == 'calls':
1036 result.append('C')
1037 elif action[1] == 'raises':
1038 result.append('R')
1039 elif action[1] == 'cashout':
1040 result.append('CO')
1041 return ''.join(result)
1043 def get_actions_short_streets(self, player, *streets):
1044 """ Returns a string with shortcuts for the actions of the given player on all given streets seperated by ',' """
1045 result = []
1046 for street in streets:
1047 action = self.get_actions_short(player, street)
1048 if len(action) > 0: # if there is no action on later streets, nothing is added.
1049 result.append(action)
1050 return ','.join(result)
1052 def get_player_position(self, player):
1053 """ Returns the given players postion (S, B, 0-7) """
1054 # position has been added to the players list. It could be calculated from buttonpos and player seatnums,
1055 # but whats the point in calculating a value that has been there anyway?
1056 for p in self.players:
1057 if p[1] == player:
1058 return p[3]
1060 def getStakesAsString(self):
1061 """Return a string of the stakes of the current hand."""
1062 return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb)
1064 def getStreetTotals(self):
1065 tmp, i = [0, 0, 0, 0, 0, 0], 0
1066 for street in self.allStreets:
1067 if street != 'BLINDSANTES':
1068 tmp[i] = self.pot.getTotalAtStreet(street)
1069 i+=1
1070 tmp[5] = sum(self.pot.committed.values()) + sum(self.pot.common.values())
1071 return tmp
1073 def writeGameLine(self):
1074 """Return the first HH line for the current hand."""
1075 gs = "PokerStars Game #%s: " % self.handid
1077 if self.tourNo is not None and self.mixed is not None: # mixed tournament
1078 gs = gs + "Tournament #%s, %s %s (%s) - Level %s (%s) - " % (self.tourNo,
1079 self.buyin, self.MS[self.mixed], self.getGameTypeAsString(), self.level, self.getStakesAsString())
1080 elif self.tourNo is not None: # all other tournaments
1081 gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % (self.tourNo,
1082 self.buyin, self.getGameTypeAsString(), self.level, self.getStakesAsString())
1083 elif self.mixed is not None: # all other mixed games
1084 gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed],
1085 self.getGameTypeAsString(), self.getStakesAsString())
1086 else: # non-mixed cash games
1087 gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
1089 try:
1090 timestr = datetime.datetime.strftime(self.startTime, '%Y/%m/%d %H:%M:%S ET')
1091 except TypeError:
1092 print(("*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:"), self.startTime)
1093 print(("*** Make sure your HandHistoryConverter is setting hand.startTime properly!"))
1094 print(("*** Game String:"), gs)
1095 return gs
1096 else:
1097 return gs + timestr
1099 def writeTableLine(self):
1100 table_string = "Table "
1101 if self.gametype['type'] == 'tour':
1102 table_string = table_string + "\'%s %s\' %s-max" % (self.tourNo, self.tablename, self.maxseats)
1103 else:
1104 table_string = table_string + "\'%s\' %s-max" % (self.tablename, self.maxseats)
1105 if self.gametype['currency'] == 'play':
1106 table_string = table_string + " (Play Money)"
1107 if self.buttonpos is not None and self.buttonpos != 0:
1108 table_string = table_string + " Seat #%s is the button" % self.buttonpos
1109 return table_string
1112 def writeHand(self, fh=sys.__stdout__):
1113 # PokerStars format.
1114 print(self.writeGameLine(), file=fh)
1115 print(self.writeTableLine(), file=fh)
1118class HoldemOmahaHand(Hand):
1119 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
1120 self.config = config
1121 if gametype['base'] != 'hold':
1122 pass # or indeed don't pass and complain instead
1123 log.debug("HoldemOmahaHand")
1124 self.allStreets = ['BLINDSANTES', 'PREFLOP','FLOP','TURN','RIVER']
1125 if gametype['category']=='fusion':
1126 self.holeStreets = ['PREFLOP','FLOP','TURN']
1127 else:
1128 self.holeStreets = ['PREFLOP']
1129 if gametype['category']=='irish':
1130 self.discardStreets = ['TURN']
1131 else:
1132 self.discardStreets = ['PREFLOP']
1133 self.communityStreets = ['FLOP', 'TURN', 'RIVER']
1134 self.actionStreets = ['BLINDSANTES','PREFLOP','FLOP','TURN','RIVER']
1135 if gametype['category']=='aof_omaha':
1136 self.allStreets = ['BLINDSANTES','FLOP','TURN','RIVER']
1137 self.holeStreets = ['FLOP']
1138 self.communityStreets = ['FLOP', 'TURN', 'RIVER']
1139 self.actionStreets = ['BLINDSANTES','FLOP','TURN','RIVER']
1140 Hand.__init__(self, self.config, sitename, gametype, handText, builtFrom = "HHC")
1141 self.sb = gametype['sb']
1142 self.bb = gametype['bb']
1143 if hasattr(hhc, "in_path"):
1144 self.in_path = hhc.in_path
1145 else:
1146 self.in_path = "database"
1148 # Populate a HoldemOmahaHand
1149 # Generally, we call 'read' methods here, which get the info according to the particular filter (hhc)
1150 # which then invokes a 'addXXX' callback
1151 if builtFrom == "HHC":
1152 hhc.readHandInfo(self)
1153 if self.gametype['type'] == 'tour':
1154 self.tablename = "%s %s" % (self.tourNo, self.tablename)
1155 hhc.readPlayerStacks(self)
1156 hhc.compilePlayerRegexs(self)
1157 hhc.markStreets(self)
1159 if self.cancelled:
1160 return
1162 hhc.readBlinds(self)
1164 hhc.readSTP(self)
1165 hhc.readAntes(self)
1166 hhc.readButton(self)
1167 hhc.readHoleCards(self)
1168 hhc.readShowdownActions(self)
1169 # Read actions in street order
1170 for street, text in list(self.streets.items()):
1171 if text and (street != "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
1172 hhc.readCommunityCards(self, street)
1173 for street in self.actionStreets:
1174 if self.streets[street] or gametype['split']:
1175 hhc.readAction(self, street)
1176 self.pot.markTotal(street)
1177 hhc.readCollectPot(self)
1178 hhc.readShownCards(self)
1179 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot
1180 self.totalPot() # finalise it (total the pot)
1181 print("self.totalpot",self.totalpot)
1182 hhc.getRake(self)
1183 if self.maxseats is None:
1184 self.maxseats = hhc.guessMaxSeats(self)
1185 self.sittingOut()
1186 hhc.readTourneyResults(self)
1187 hhc.readOther(self)
1188 elif builtFrom == "DB":
1189 # Creator expected to call hhc.select(hid) to fill out object
1190 log.debug("HoldemOmahaHand.__init__: " + ("DEBUG:") + " " +("HoldemOmaha hand initialised for %s"), "select()")
1191 self.maxseats = 10
1192 else:
1193 log.warning("HoldemOmahaHand.__init__: " + ("Neither HHC nor DB+handID provided"))
1195 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
1196 if player == self.hero: # we have hero's cards just update shown/mucked
1197 if shown: self.shown.add(player)
1198 if mucked: self.mucked.add(player)
1199 else:
1200 if self.gametype['category'] == 'aof_omaha':
1201 self.addHoleCards('FLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
1202 elif len(cards) in (2, 3, 4, 6) or self.gametype['category'] in ('5_omahahi', '5_omaha8', 'cour_hi', 'cour_hilo', 'fusion'): # avoid adding board by mistake (Everleaf problem)
1203 self.addHoleCards('PREFLOP', player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
1205 elif len(cards) == 5: # cards holds a winning hand, not hole cards
1206 # filter( lambda x: x not in b, a ) # calcs a - b where a and b are lists
1207 # so diff is set to the winning hand minus the board cards, if we're lucky that leaves the hole cards
1208 diff = [x for x in cards if x not in self.board['FLOP']+self.board['TURN']+self.board['RIVER']]
1209 if len(diff) == 2 and self.gametype['category'] in ('holdem'):
1210 self.addHoleCards('PREFLOP', player, open=[], closed=diff, shown=shown, mucked=mucked, dealt=dealt)
1211 if string is not None:
1212 self.showdownStrings[player] = string
1214 def join_holecards(self, player, asList=False):
1215 holeNo = Card.games[self.gametype['category']][5][0][1]
1216 hcs = [u'0x'] * holeNo
1217 if self.gametype['category'] == 'fusion':
1218 for street in self.holeStreets:
1219 if player in list(self.holecards[street].keys()):
1220 if street == 'PREFLOP':
1221 if len(self.holecards[street][player][1])==1: continue
1222 for i in 0,1:
1223 hcs[i] = self.holecards[street][player][1][i]
1224 hcs[i] = hcs[i][0:1].upper()+hcs[i][1:2]
1225 try:
1226 for i in range(2, holeNo):
1227 hcs[i] = self.holecards[street][player][1][i]
1228 hcs[i] = hcs[i][0:1].upper()+hcs[i][1:2]
1229 except IndexError:
1230 log.debug("Why did we get an indexerror?")
1231 elif street == 'FLOP':
1232 if len(self.holecards[street][player][1])==1: continue
1233 hcs[2] = self.holecards[street][player][0][0]
1234 hcs[2] = hcs[2][0:1].upper()+hcs[2][1:2]
1235 elif street == 'TURN':
1236 if len(self.holecards[street][player][1]) == 1: continue
1237 hcs[3] = self.holecards[street][player][0][0]
1238 hcs[3] = hcs[3][0:1].upper() + hcs[3][1:2]
1240 else:
1241 for street in self.holeStreets:
1242 if player in list(self.holecards[street].keys()):
1243 if len(self.holecards[street][player][1])==1: continue
1244 for i in 0,1:
1245 hcs[i] = self.holecards[street][player][1][i]
1246 hcs[i] = hcs[i][0:1].upper()+hcs[i][1:2]
1247 try:
1248 for i in range(2, holeNo):
1249 hcs[i] = self.holecards[street][player][1][i]
1250 hcs[i] = hcs[i][0:1].upper()+hcs[i][1:2]
1251 except IndexError:
1252 log.debug("Why did we get an indexerror?")
1254 if asList:
1255 return hcs
1256 else:
1257 return " ".join(hcs)
1259 def writeHand(self, fh=sys.__stdout__):
1260 # PokerStars format.
1261 super(HoldemOmahaHand, self).writeHand(fh)
1263 players_who_act_preflop = set(([x[0] for x in self.actions['PREFLOP']]+[x[0] for x in self.actions['BLINDSANTES']]))
1264 log.debug(self.actions['PREFLOP'])
1265 for player in [x for x in self.players if x[1] in players_who_act_preflop]:
1266 # Only print stacks of players who do something preflop
1267 print(("Seat %s: %s ($%.2f in chips) " %(player[0], player[1], float(player[2]))), file=fh)
1269 if self.actions['BLINDSANTES']:
1270 log.debug(self.actions['BLINDSANTES'])
1271 for act in self.actions['BLINDSANTES']:
1272 print(self.actionString(act), file=fh)
1274 print(("*** HOLE CARDS ***"), file=fh)
1275 for player in self.dealt:
1276 print(("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1]))), file=fh)
1277 if self.hero == "":
1278 for player in self.shown.difference(self.dealt):
1279 print(("Dealt to %s [%s]" %(player, " ".join(self.holecards['PREFLOP'][player][1]))), file=fh)
1281 if self.actions['PREFLOP']:
1282 for act in self.actions['PREFLOP']:
1283 print(self.actionString(act), file=fh)
1285 if self.board['FLOP']:
1286 print(("*** FLOP *** [%s]" %( " ".join(self.board['FLOP']))), file=fh)
1287 if self.actions['FLOP']:
1288 for act in self.actions['FLOP']:
1289 print(("*** FLOP *** [%s]" %( " ".join(self.board['FLOP']))), file=fh)
1291 if self.board['TURN']:
1292 print(("*** TURN *** [%s] [%s]" %( " ".join(self.board['FLOP']), " ".join(self.board['TURN']))), file=fh)
1293 if self.actions['TURN']:
1294 for act in self.actions['TURN']:
1295 print(self.actionString(act), file=fh)
1297 if self.board['RIVER']:
1298 print(("*** RIVER *** [%s] [%s]" %(" ".join(self.board['FLOP']+self.board['TURN']), " ".join(self.board['RIVER']) )), file=fh)
1299 if self.actions['RIVER']:
1300 for act in self.actions['RIVER']:
1301 print(self.actionString(act), file=fh)
1305 # Some sites don't have a showdown section so we have to figure out if there should be one
1306 # The logic for a showdown is: at the end of river action there are at least two players in the hand
1307 # we probably don't need a showdown section in pseudo stars format for our filtering purposes
1308 if self.shown:
1309 print(("*** SHOW DOWN ***"), file=fh)
1310 for name in self.shown:
1311 # TODO: legacy importer can't handle only one holecard here, make sure there are 2 for holdem, 4 for omaha
1312 # TOOD: If HoldHand subclass supports more than omahahi, omahahilo, holdem, add them here
1313 numOfHoleCardsNeeded = None
1314 if self.gametype['category'] in ('omahahi','omahahilo'):
1315 numOfHoleCardsNeeded = 4
1316 elif self.gametype['category'] in ('holdem'):
1317 numOfHoleCardsNeeded = 2
1318 if len(self.holecards['PREFLOP'][name]) == numOfHoleCardsNeeded:
1319 print(("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards['PREFLOP'][name][1]))), file=fh)
1321 # Current PS format has the lines:
1322 # Uncalled bet ($111.25) returned to s0rrow
1323 # s0rrow collected $5.15 from side pot
1324 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1325 # stervels collected $45.35 from main pot
1326 # Immediately before the summary.
1327 # The current importer uses those lines for importing winning rather than the summary
1328 for name in self.pot.returned:
1329 print(("Uncalled bet (%s%s) returned to %s" %(self.sym, self.pot.returned[name],name)), file=fh)
1330 for entry in self.collected:
1331 print(("%s collected %s%s from pot" %(entry[0], self.sym, entry[1])), file=fh)
1333 print(("*** SUMMARY ***"), file=fh)
1334 print("%s | Rake %s%.2f" % (self.pot, self.sym, self.rake), file=fh)
1336 board = []
1337 for street in ["FLOP", "TURN", "RIVER"]:
1338 board += self.board[street]
1339 if board: # sometimes hand ends preflop without a board
1340 print(("Board [%s]" % (" ".join(board))), file=fh)
1342 for player in [x for x in self.players if x[1] in players_who_act_preflop]:
1343 seatnum = player[0]
1344 name = player[1]
1345 if name in self.collectees and name in self.shown:
1346 print(("Seat %d: %s showed [%s] and won (%s%s)"
1347 % (seatnum, name,
1348 " ".join(self.holecards['PREFLOP'][name][1]),
1349 self.sym, self.collectees[name])), file=fh)
1350 elif name in self.collectees:
1351 print(("Seat %d: %s collected (%s%s)"
1352 % (seatnum, name, self.sym, self.collectees[name])), file=fh)
1353 elif name in self.folded:
1354 print(("Seat %d: %s folded" % (seatnum, name)), file=fh)
1355 else:
1356 if name in self.shown:
1357 print(("Seat %d: %s showed [%s] and lost with..." % (seatnum, name, " ".join(self.holecards['PREFLOP'][name][1]))), file=fh)
1358 elif name in self.mucked:
1359 print(("Seat %d: %s mucked [%s] " % (seatnum, name, " ".join(self.holecards['PREFLOP'][name][1]))), file=fh)
1360 else:
1361 print(("Seat %d: %s mucked" % (seatnum, name)), file=fh)
1363 print("\n\n", file=fh)
1365class DrawHand(Hand):
1366 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
1367 self.config = config
1368 if gametype['base'] != 'draw':
1369 pass # or indeed don't pass and complain instead
1370 self.streetList = ['BLINDSANTES', 'DEAL', 'DRAWONE']
1371 self.allStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE']
1372 self.holeStreets = ['DEAL', 'DRAWONE']
1373 self.actionStreets = ['BLINDSANTES', 'DEAL', 'DRAWONE']
1374 if gametype['category'] in ["27_3draw","badugi", "a5_3draw", "badacey", "badeucey", "drawmaha"]:
1375 self.streetList += ['DRAWTWO', 'DRAWTHREE']
1376 self.allStreets += ['DRAWTWO', 'DRAWTHREE']
1377 self.holeStreets += ['DRAWTWO', 'DRAWTHREE']
1378 self.actionStreets += ['DRAWTWO', 'DRAWTHREE']
1379 self.discardStreets = self.holeStreets
1380 self.communityStreets = []
1381 Hand.__init__(self, self.config, sitename, gametype, handText)
1382 self.sb = gametype['sb']
1383 self.bb = gametype['bb']
1384 if hasattr(hhc, "in_path"):
1385 self.in_path = hhc.in_path
1386 else:
1387 self.in_path = "database"
1388 # Populate the draw hand.
1389 if builtFrom == "HHC":
1390 hhc.readHandInfo(self)
1391 if self.gametype['type'] == 'tour':
1392 self.tablename = "%s %s" % (self.tourNo, self.tablename)
1393 hhc.readPlayerStacks(self)
1394 hhc.compilePlayerRegexs(self)
1395 hhc.markStreets(self)
1396 # markStreets in Draw may match without dealing cards
1397 if self.streets['DEAL'] is None:
1398 log.error("DrawHand.__init__: " + ("Street 'DEAL' is empty. Was hand '%s' cancelled?") % self.handid)
1399 raise FpdbParseError
1400 hhc.readBlinds(self)
1401 hhc.readSTP(self)
1402 hhc.readAntes(self)
1403 hhc.readButton(self)
1404 hhc.readHoleCards(self)
1405 hhc.readShowdownActions(self)
1406 # Read actions in street order
1407 for street in self.streetList:
1408 if self.streets[street]:
1409 hhc.readAction(self, street)
1410 self.pot.markTotal(street)
1411 hhc.readCollectPot(self)
1412 hhc.readShownCards(self)
1413 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot
1414 self.totalPot() # finalise it (total the pot)
1415 hhc.getRake(self)
1416 if self.maxseats is None:
1417 self.maxseats = hhc.guessMaxSeats(self)
1418 self.sittingOut()
1419 hhc.readTourneyResults(self)
1420 hhc.readOther(self)
1422 elif builtFrom == "DB":
1423 # Creator expected to call hhc.select(hid) to fill out object
1424 self.maxseats = 10
1426 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
1427 #if player == self.hero: # we have hero's cards just update shown/mucked
1428 # if shown: self.shown.add(player)
1429 # if mucked: self.mucked.add(player)
1430 #else:
1431 # pass
1432 # TODO: Probably better to find the last street with action and add the hole cards to that street
1433 self.addHoleCards(self.actionStreets[-1], player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt)
1434 if string is not None:
1435 self.showdownStrings[player] = string
1437 def holecardsAsSet(self, street, player):
1438 """Return holdcards: (oc, nc) as set()"""
1439 (nc,oc) = self.holecards[street][player]
1440 nc = set(nc)
1441 oc = set(oc)
1442 return (nc, oc)
1444 def join_holecards(self, player, asList=False, street=False):
1445 """With asList = True it returns the set cards for a player including down cards if they aren't know"""
1446 handsize = Card.games[self.gametype['category']][5][0][1]
1447 holecards = [u'0x']*20
1449 for i, _street in enumerate(self.holeStreets):
1450 if player in list(self.holecards[_street].keys()):
1451 allhole = self.holecards[_street][player][1] + self.holecards[_street][player][0]
1452 allhole = allhole[:handsize]
1453 for c in range(len(allhole)):
1454 idx = c + i * 5
1455 holecards[idx] = allhole[c]
1457 result = []
1458 if street is False:
1459 result = holecards
1460 elif street in self.holeStreets:
1461 if street == 'DEAL':
1462 result = holecards[0:5]
1463 elif street == 'DRAWONE':
1464 result = holecards[5:10]
1465 elif street == 'DRAWTWO':
1466 result = holecards[10:15]
1467 elif street == 'DRAWTHREE':
1468 result = holecards[15:20]
1469 return result if asList else " ".join(result)
1471 def writeHand(self, fh=sys.__stdout__):
1472 # PokerStars format.
1473 # HH output should not be translated
1474 super(DrawHand, self).writeHand(fh)
1476 players_who_act_ondeal = set(([x[0] for x in self.actions['DEAL']]+[x[0] for x in self.actions['BLINDSANTES']]))
1478 for player in [x for x in self.players if x[1] in players_who_act_ondeal]:
1479 # Only print stacks of players who do something on deal
1480 print((("Seat %s: %s (%s%s in chips) ") % (player[0], player[1], self.sym, player[2])), file=fh)
1482 if 'BLINDSANTES' in self.actions:
1483 for act in self.actions['BLINDSANTES']:
1484 print(("%s: %s %s %s%s" % (act[0], act[1], act[2], self.sym, act[3])), file=fh)
1486 if 'DEAL' in self.actions:
1487 print(("*** DEALING HANDS ***"), file=fh)
1488 for player in [x[1] for x in self.players if x[1] in players_who_act_ondeal]:
1489 if 'DEAL' in self.holecards:
1490 if player in self.holecards['DEAL']:
1491 (nc,oc) = self.holecards['DEAL'][player]
1492 print(("Dealt to %s: [%s]") % (player, " ".join(nc)), file=fh)
1493 for act in self.actions['DEAL']:
1494 print(self.actionString(act, 'DEAL'), file=fh)
1496 if 'DRAWONE' in self.actions:
1497 print(("*** FIRST DRAW ***"), file=fh)
1498 for act in self.actions['DRAWONE']:
1499 print(self.actionString(act, 'DRAWONE'), file=fh)
1500 if act[0] == self.hero and act[1] == 'discards':
1501 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1502 dc = self.discards['DRAWONE'][act[0]]
1503 kc = oc - dc
1504 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh)
1506 if 'DRAWTWO' in self.actions:
1507 print(("*** SECOND DRAW ***"), file=fh)
1508 for act in self.actions['DRAWTWO']:
1509 print(self.actionString(act, 'DRAWTWO'), file=fh)
1510 if act[0] == self.hero and act[1] == 'discards':
1511 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1512 dc = self.discards['DRAWTWO'][act[0]]
1513 kc = oc - dc
1514 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh)
1516 if 'DRAWTHREE' in self.actions:
1517 print(("*** THIRD DRAW ***"), file=fh)
1518 for act in self.actions['DRAWTHREE']:
1519 print(self.actionString(act, 'DRAWTHREE'), file=fh)
1520 if act[0] == self.hero and act[1] == 'discards':
1521 (nc,oc) = self.holecardsAsSet('DRAWONE', act[0])
1522 dc = self.discards['DRAWTHREE'][act[0]]
1523 kc = oc - dc
1524 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh)
1526 if 'SHOWDOWN' in self.actions:
1527 print(("*** SHOW DOWN ***"), file=fh)
1528 #TODO: Complete SHOWDOWN
1530 # Current PS format has the lines:
1531 # Uncalled bet ($111.25) returned to s0rrow
1532 # s0rrow collected $5.15 from side pot
1533 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1534 # stervels collected $45.35 from main pot
1535 # Immediately before the summary.
1536 # The current importer uses those lines for importing winning rather than the summary
1537 for name in self.pot.returned:
1538 print(("Uncalled bet (%s%s) returned to %s" % (self.sym, self.pot.returned[name],name)), file=fh)
1539 for entry in self.collected:
1540 print(("%s collected %s%s from pot" % (entry[0], self.sym, entry[1])), file=fh)
1542 print(("*** SUMMARY ***"), file=fh)
1543 print("%s | Rake %s%.2f" % (self.pot, self.sym, self.rake), file=fh)
1544 print("\n\n", file=fh)
1548class StudHand(Hand):
1549 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom = "HHC", handid=None):
1550 self.config = config
1551 if gametype['base'] != 'stud':
1552 pass # or indeed don't pass and complain instead
1554 self.communityStreets = []
1555 if gametype['category'] == '5_studhi':
1556 self.allStreets = ['BLINDSANTES','SECOND', 'THIRD','FOURTH','FIFTH']
1557 self.actionStreets = ['BLINDSANTES','SECOND','THIRD','FOURTH','FIFTH']
1558 self.streetList = ['BLINDSANTES','SECOND','THIRD','FOURTH','FIFTH'] # a list of the observed street names in order
1559 self.holeStreets = ['SECOND','THIRD','FOURTH','FIFTH']
1560 else:
1561 self.allStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1562 self.actionStreets = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1563 self.streetList = ['BLINDSANTES','THIRD','FOURTH','FIFTH','SIXTH','SEVENTH'] # a list of the observed street names in order
1564 self.holeStreets = ['THIRD','FOURTH','FIFTH','SIXTH','SEVENTH']
1565 self.discardStreets = self.holeStreets
1566 Hand.__init__(self, self.config, sitename, gametype, handText)
1567 self.sb = gametype['sb']
1568 self.bb = gametype['bb']
1569 if hasattr(hhc, "in_path"):
1570 self.in_path = hhc.in_path
1571 else:
1572 self.in_path = "database"
1573 # Populate the StudHand
1574 # Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc)
1575 # which then invokes a 'addXXX' callback
1576 if builtFrom == "HHC":
1577 hhc.readHandInfo(self)
1578 if self.gametype['type'] == 'tour':
1579 self.tablename = "%s %s" % (self.tourNo, self.tablename)
1580 hhc.readPlayerStacks(self)
1581 hhc.compilePlayerRegexs(self)
1582 hhc.markStreets(self)
1583 hhc.readSTP(self)
1584 hhc.readAntes(self)
1585 hhc.readBringIn(self)
1586 hhc.readHoleCards(self)
1587 hhc.readShowdownActions(self)
1588 # Read actions in street order
1589 for street in self.actionStreets:
1590 if street == 'BLINDSANTES': continue # OMG--sometime someone folds in the ante round
1591 if self.streets[street]:
1592 log.debug(street + self.streets[street])
1593 hhc.readAction(self, street)
1594 self.pot.markTotal(street)
1595 hhc.readCollectPot(self)
1596 hhc.readShownCards(self) # not done yet
1597 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot
1598 self.totalPot() # finalise it (total the pot)
1599 hhc.getRake(self)
1600 if self.maxseats is None:
1601 self.maxseats = hhc.guessMaxSeats(self)
1602 self.sittingOut()
1603 hhc.readTourneyResults(self)
1604 hhc.readOther(self)
1606 elif builtFrom == "DB":
1607 # Creator expected to call hhc.select(hid) to fill out object
1608 self.maxseats = 10
1610 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None):
1611 if player == self.hero: # we have hero's cards just update shown/mucked
1612 if shown: self.shown.add(player)
1613 if mucked: self.mucked.add(player)
1614 else:
1615 if self.gametype['category'] == '5_studhi' and len(cards)>4:
1616 self.addHoleCards('SECOND', player, open=[cards[1]], closed=[cards[0]], shown=shown, mucked=mucked)
1617 self.addHoleCards('THIRD', player, open=[cards[2]], closed=[cards[1]], shown=shown, mucked=mucked)
1618 self.addHoleCards('FOURTH', player, open=[cards[3]], closed=cards[1:2], shown=shown, mucked=mucked)
1619 self.addHoleCards('FIFTH', player, open=[cards[4]], closed=cards[1:3], shown=shown, mucked=mucked)
1620 if len(cards) > 6:
1621 self.addHoleCards('THIRD', player, open=[cards[2]], closed=cards[0:2], shown=shown, mucked=mucked)
1622 self.addHoleCards('FOURTH', player, open=[cards[3]], closed=[cards[2]], shown=shown, mucked=mucked)
1623 self.addHoleCards('FIFTH', player, open=[cards[4]], closed=cards[2:4], shown=shown, mucked=mucked)
1624 self.addHoleCards('SIXTH', player, open=[cards[5]], closed=cards[2:5], shown=shown, mucked=mucked)
1625 self.addHoleCards('SEVENTH', player, open=[], closed=[cards[6]], shown=shown, mucked=mucked)
1626 if string is not None:
1627 self.showdownStrings[player] = string
1630 def addPlayerCards(self, player, street, open=[], closed=[]):
1631 """
1632 Assigns observed cards to a player.
1633 player (string) name of player
1634 street (string) the street name (in streetList)
1635 open list of card bigrams e.g. ['2h','Jc'], dealt face up
1636 closed likewise, but known only to player
1637 """
1638 log.debug("addPlayerCards %s, o%s x%s", player, open, closed)
1639 self.checkPlayerExists(player, 'addPlayerCards')
1640 self.holecards[street][player] = (open, closed)
1642 # TODO: def addComplete(self, player, amount):
1643 def addComplete(self, street, player, amountTo):
1644 # assert street=='THIRD'
1645 # This needs to be called instead of addRaiseTo, and it needs to take account of self.lastBet['THIRD'] to determine the raise-by size
1646 """\
1647 Add a complete on [street] by [player] to [amountTo]
1648 """
1649 log.debug(("%s %s completes %s"), street, player, amountTo)
1650 amountTo = amountTo.replace(u',', u'') #some sites have commas
1651 self.checkPlayerExists(player, 'addComplete')
1652 Bp = self.lastBet[street]
1653 Bc = sum(self.bets[street][player])
1654 Rt = Decimal(amountTo)
1655 C = Bp - Bc
1656 Rb = Rt - C
1657 self._addRaise(street, player, C, Rb, Rt, 'completes')
1659 def addBringIn(self, player, bringin):
1660 if player is not None:
1661 if self.gametype['category']=='5_studhi':
1662 street = 'SECOND'
1663 else:
1664 street = 'THIRD'
1665 log.debug(("Bringin: %s, %s"), player, bringin)
1666 bringin = bringin.replace(u',', u'') #some sites have commas
1667 self.checkPlayerExists(player, 'addBringIn')
1668 bringin = Decimal(bringin)
1669 self.bets[street][player].append(bringin)
1670 self.stacks[player] -= bringin
1671 act = (player, 'bringin', bringin, self.stacks[player]==0)
1672 self.actions[street].append(act)
1673 self.lastBet[street] = bringin
1674 self.pot.addMoney(player, bringin)
1676 def writeHand(self, fh=sys.__stdout__):
1677 # PokerStars format.
1678 # HH output should not be translated
1679 super(StudHand, self).writeHand(fh)
1681 players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']])
1683 for player in [x for x in self.players if x[1] in players_who_post_antes]:
1684 # Only print stacks of players who do something preflop
1685 print(("Seat %s: %s (%s%s in chips)" %(player[0], player[1], self.sym, player[2])), file=fh)
1687 if 'BLINDSANTES' in self.actions:
1688 for act in self.actions['BLINDSANTES']:
1689 print(("%s: posts the ante %s%s" %(act[0], self.sym, act[3])), file=fh)
1691 if 'THIRD' in self.actions:
1692 dealt = 0
1693 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1694 if player in self.holecards['THIRD']:
1695 dealt += 1
1696 if dealt == 1:
1697 print(("*** 3RD STREET ***"), file=fh)
1698 print(self.writeHoleCards('THIRD', player), file=fh)
1699 for act in self.actions['THIRD']:
1700 # FIXME: Need some logic here for bringin vs completes
1701 print(self.actionString(act), file=fh)
1703 if 'FOURTH' in self.actions:
1704 dealt = 0
1705 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1706 if player in self.holecards['FOURTH']:
1707 dealt += 1
1708 if dealt == 1:
1709 print(("*** 4TH STREET ***"), file=fh)
1710 print(self.writeHoleCards('FOURTH', player), file=fh)
1711 for act in self.actions['FOURTH']:
1712 print(self.actionString(act), file=fh)
1714 if 'FIFTH' in self.actions:
1715 dealt = 0
1716 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1717 if player in self.holecards['FIFTH']:
1718 dealt += 1
1719 if dealt == 1:
1720 print(("*** 5TH STREET ***"), file=fh)
1721 print(self.writeHoleCards('FIFTH', player), file=fh)
1722 for act in self.actions['FIFTH']:
1723 print(self.actionString(act), file=fh)
1725 if 'SIXTH' in self.actions:
1726 dealt = 0
1727 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1728 if player in self.holecards['SIXTH']:
1729 dealt += 1
1730 if dealt == 1:
1731 print(("*** 6TH STREET ***"), file=fh)
1732 print(self.writeHoleCards('SIXTH', player), file=fh)
1733 for act in self.actions['SIXTH']:
1734 print(self.actionString(act), file=fh)
1736 if 'SEVENTH' in self.actions:
1737 # OK. It's possible that they're all in at an earlier street, but only closed cards are dealt.
1738 # Then we have no 'dealt to' lines, no action lines, but still 7th street should appear.
1739 # The only way I can see to know whether to print this line is by knowing the state of the hand
1740 # i.e. are all but one players folded; is there an allin showdown; and all that.
1741 print(("*** RIVER ***"), file=fh)
1742 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]:
1743 if player in self.holecards['SEVENTH']:
1744 if self.writeHoleCards('SEVENTH', player):
1745 print(self.writeHoleCards('SEVENTH', player), file=fh)
1746 for act in self.actions['SEVENTH']:
1747 print(self.actionString(act), file=fh)
1749 # Some sites don't have a showdown section so we have to figure out if there should be one
1750 # The logic for a showdown is: at the end of river action there are at least two players in the hand
1751 # we probably don't need a showdown section in pseudo stars format for our filtering purposes
1752 if 'SHOWDOWN' in self.actions:
1753 print(("*** SHOW DOWN ***"), file=fh)
1754 # TODO: print showdown lines.
1756 # Current PS format has the lines:
1757 # Uncalled bet ($111.25) returned to s0rrow
1758 # s0rrow collected $5.15 from side pot
1759 # stervels: shows [Ks Qs] (two pair, Kings and Queens)
1760 # stervels collected $45.35 from main pot
1761 # Immediately before the summary.
1762 # The current importer uses those lines for importing winning rather than the summary
1763 for name in self.pot.returned:
1764 print(("Uncalled bet (%s%s) returned to %s" %(self.sym, self.pot.returned[name],name)), file=fh)
1765 for entry in self.collected:
1766 print(("%s collected %s%s from pot" %(entry[0], self.sym, entry[1])), file=fh)
1768 print(("*** SUMMARY ***"), file=fh)
1769 print("%s | Rake %s%.2f" % (self.pot, self.sym, self.rake), file=fh)
1770# TODO: side pots
1772 board = []
1773 for s in list(self.board.values()):
1774 board += s
1775 if board: # sometimes hand ends preflop without a board
1776 print(("Board [%s]" % (" ".join(board))), file=fh)
1778 for player in [x for x in self.players if x[1] in players_who_post_antes]:
1779 seatnum = player[0]
1780 name = player[1]
1781 if name in self.collectees and name in self.shown:
1782 print(("Seat %d: %s showed [%s] and won (%s%s)" % (seatnum, name, self.join_holecards(name), self.sym, self.collectees[name])), file=fh)
1783 elif name in self.collectees:
1784 print(("Seat %d: %s collected (%s%s)" % (seatnum, name, self.sym, self.collectees[name])), file=fh)
1785 elif name in self.shown:
1786 print(("Seat %d: %s showed [%s]" % (seatnum, name, self.join_holecards(name))), file=fh)
1787 elif name in self.mucked:
1788 print(("Seat %d: %s mucked [%s]" % (seatnum, name, self.join_holecards(name))), file=fh)
1789 elif name in self.folded:
1790 print(("Seat %d: %s folded" % (seatnum, name)), file=fh)
1791 else:
1792 print(("Seat %d: %s mucked" % (seatnum, name)), file=fh)
1794 print("\n\n", file=fh)
1797 def writeHoleCards(self, street, player):
1798 hc = "Dealt to %s [" % player
1799 if street == 'THIRD':
1800 if player == self.hero:
1801 return hc + " ".join(self.holecards[street][player][1]) + " " + " ".join(self.holecards[street][player][0]) + ']'
1802 else:
1803 return hc + " ".join(self.holecards[street][player][0]) + ']'
1805 if street == 'SEVENTH' and player != self.hero: return # only write 7th st line for hero, LDO
1806 return hc + " ".join(self.holecards[street][player][1]) + "] [" + " ".join(self.holecards[street][player][0]) + "]"
1808 def join_holecards(self, player, asList=False):
1809 """Function returns a string for the stud writeHand method by default
1810 With asList = True it returns the set cards for a player including down cards if they aren't know"""
1811 holecards = []
1812 for street in self.holeStreets:
1813 if player in self.holecards[street]:
1814 if ((self.gametype['category'] == '5_studhi' and street == 'SECOND') or
1815 (self.gametype['category'] != '5_studhi' and street == 'THIRD')):
1816 holecards = holecards + self.holecards[street][player][1] + self.holecards[street][player][0]
1817 elif street == 'SEVENTH':
1818 if player == self.hero:
1819 holecards = holecards + self.holecards[street][player][0]
1820 else:
1821 holecards = holecards + self.holecards[street][player][1]
1822 else:
1823 holecards = holecards + self.holecards[street][player][0]
1825 if asList:
1826 if self.gametype['category']=='5_studhi':
1827 if len(holecards) < 2:
1828 holecards = [u'0x'] + holecards
1829 return holecards
1830 else:
1831 if player == self.hero:
1832 if len(holecards) < 3:
1833 holecards = [u'0x', u'0x'] + holecards
1834 else:
1835 return holecards
1836 elif len(holecards) == 7:
1837 return holecards
1838 elif len(holecards) <= 4:
1839 # Non hero folded before showdown, add first two downcards
1840 holecards = [u'0x', u'0x'] + holecards
1841 else:
1842 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"))
1843 log.warning("join_holecards: holecards(%s): %s", player, holecards)
1844 if holecards == [u'0x', u'0x']:
1845 log.warning(("join_holecards: Player '%s' appears not to have been dealt a card"), player)
1846 # If a player is listed but not dealt a card in a cash game this can occur
1847 # Noticed in FTP Razz hand. Return 3 empty cards in this case
1848 holecards = [u'0x', u'0x', u'0x']
1849 return holecards
1850 else:
1851 return " ".join(holecards)
1854class Pot(object):
1857 def __init__(self):
1858 self.contenders = set()
1859 self.committed = {}
1860 self.streettotals = {}
1861 self.common = {}
1862 self.antes = {}
1863 self.total = None
1864 self.returned = {}
1865 self.sym = u'$' # this is the default currency symbol
1866 self.pots = []
1867 self.handid = 0
1868 self.stp = 0
1870 def setSym(self, sym):
1871 self.sym = sym
1873 def addPlayer(self,player):
1874 self.committed[player] = Decimal(0)
1875 self.common[player] = Decimal(0)
1876 self.antes[player] = Decimal(0)
1878 def removePlayer(self,player):
1879 del self.committed[player]
1880 del self.common[player]
1881 del self.antes[player]
1883 def addFold(self, player):
1884 # addFold must be called when a player folds
1885 self.contenders.discard(player)
1887 def addCommonMoney(self, player, amount):
1888 self.common[player] += amount
1890 def addAntes(self, player, amount):
1891 self.antes[player] += amount
1893 def addMoney(self, player, amount):
1894 # addMoney must be called for any actions that put money in the pot, in the order they occur
1895 self.contenders.add(player)
1896 self.committed[player] += amount
1898 def removeMoney(self, player, amount):
1899 self.committed[player] -= amount
1900 self.returned[player] = amount
1902 def setSTP(self, amount):
1903 self.stp = amount
1905 def markTotal(self, street):
1906 self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values()) + self.stp
1908 def getTotalAtStreet(self, street):
1909 if street in self.streettotals:
1910 return self.streettotals[street]
1911 return 0
1913 def end(self):
1914 # Initialize
1915 print("Starting pot calculation...")
1917 self.total = sum(self.committed.values()) + sum(self.common.values()) + self.stp
1919 # Calculating secondary pots
1920 commitsall = sorted([(v, k) for (k, v) in self.committed.items() if v > 0])
1921 try:
1922 while len(commitsall) > 0:
1923 # Filter players still in the running
1924 commitslive = [(v, k) for (v, k) in commitsall if k in self.contenders]
1925 v1 = commitslive[0][0]
1926 # Create a new secondary pot
1927 self.pots += [(sum([min(v, v1) for (v, k) in commitsall]),
1928 set(k for (v, k) in commitsall if k in self.contenders))]
1929 # Updates the remaining bets
1930 commitsall = [((v - v1), k) for (v, k) in commitsall if v - v1 > 0]
1931 except IndexError as e:
1932 log.error("Pot.end(): Major failure while calculating pot: %s", e)
1933 raise FpdbParseError("Error in pot calculation during side pots handling")
1935 # TODO: Gestion du rake (commission)
1936 # Explication de la gestion du rake
1937 # total pot x. main pot y, side pot z. | rake r
1938 # et y+z+r = x
1939 # Exemple:
1940 # Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2
1942 def __str__(self):
1943 if self.sym is None:
1944 self.sym = "C"
1945 if self.total is None:
1946 # NB if I'm sure end() is idempotent, call it here.
1947 log.error(("Error in printing Hand object"))
1948 raise FpdbParseError
1950 ret = "Total pot %s%.2f" % (self.sym, self.total)
1951 if len(self.pots) < 2:
1952 return ret;
1953 ret += " Main pot %s%.2f" % (self.sym, self.pots[0][0])
1954 return ret + ''.join([ (" Side pot %s%.2f." % (self.sym, self.pots[x][0]) ) for x in range(1, len(self.pots)) ])
1956def hand_factory(hand_id, config, db_connection):
1957 # a factory function to discover the base type of the hand
1958 # and to return a populated class instance of the correct hand
1960 log.debug(f"get info from db for hand {hand_id}")
1961 gameinfo = db_connection.get_gameinfo_from_hid(hand_id)
1962 log.debug(f"gameinfo {gameinfo} for hand {hand_id}")
1964 if gameinfo['base'] == 'hold':
1965 hand_instance = HoldemOmahaHand(config=config, hhc=None, sitename=gameinfo['sitename'],
1966 gametype = gameinfo, handText=None, builtFrom = "DB", handid=hand_id)
1967 elif gameinfo['base'] == 'stud':
1968 hand_instance = StudHand(config=config, hhc=None, sitename=gameinfo['sitename'],
1969 gametype = gameinfo, handText=None, builtFrom = "DB", handid=hand_id)
1970 elif gameinfo['base'] == 'draw':
1971 hand_instance = DrawHand(config=config, hhc=None, sitename=gameinfo['sitename'],
1972 gametype = gameinfo, handText=None, builtFrom = "DB", handid=hand_id)
1974 log.debug(f"selecting info from db for hand {hand_id}")
1975 hand_instance.select(db_connection, hand_id)
1976 hand_instance.handid_selected = hand_id #hand_instance does not supply this, create it here
1977 log.debug(f"exiting hand_factory for hand {hand_id}")
1979 return hand_instance