Coverage for Hand.py: 10%

1394 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-28 16:41 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

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 

18 

19 

20 

21 

22 

23#import L10n 

24#_ = L10n.get_translation() 

25 

26# TODO: get writehand() encoding correct 

27 

28import sys 

29from decimal_wrapper import Decimal 

30import datetime 

31 

32import pprint 

33 

34import logging 

35# logging has been set up in fpdb.py or HUD_main.py, use their settings: 

36log = logging.getLogger("parser") 

37 

38import Configuration 

39from Exceptions import FpdbHandDuplicate, FpdbHandPartial, FpdbParseError 

40import DerivedStats 

41import Card 

42Configuration.set_logfile("fpdb-log.txt") 

43 

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} 

52 

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 

82 

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 

129 

130 self.seating = [] 

131 self.players = [] 

132 # Cache used for checkPlayerExists. 

133 self.player_exists_cache = set() 

134 self.posted = [] 

135 self.tourneysPlayersIds = {} 

136 

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 = {} 

164 

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) 

171 

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 

183 

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 ) 

223 

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) 

241 

242 for (name, struct) in structs: 

243 result = result + "\n%s =\n" % name + pprint.pformat(struct, 4) 

244 return result 

245 

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') 

256 

257 if dealt: self.dealt.add(player) 

258 if shown: self.shown.add(player) 

259 if mucked: self.mucked.add(player) 

260 

261 for i in range(len(closed)): 

262 if closed[i] in ('', 'Xx', 'Null', 'null', 'X'): 

263 closed[i] = '0x' 

264 

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 

270 

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) 

282 

283 # Gametypes 

284 hilo = Card.games[self.gametype['category']][2] 

285 

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) 

300 

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() 

308 

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 

323 

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) 

333 

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) 

342 

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) 

347 

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) 

353 

354 def updateTourneyResults(self, db): 

355 """ Function to update Tourney Bounties if any""" 

356 db.updateTourneyPlayerBounties(self) 

357 

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) 

362 

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) 

370 

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) 

376 

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) 

382 

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']) 

388 

389 c.execute("select heroseat from Hands where id = {}".format(handId)) 

390 heroSeat = c.fetchone()[0] 

391 

392 # PlayerStacks 

393 c.execute(q, (handId,)) 

394 # See NOTE: below on what this does. 

395 

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'] 

449 

450 

451 # HandInfo 

452 q = db.sql.query['singleHand'] 

453 q = q.replace('%s', db.sql.query['placeholder']) 

454 c.execute(q, (handId,)) 

455 

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() 

460 

461 # Using row_factory is global, and affects the rest of fpdb. The following 2 line achieves 

462 # a similar result 

463 

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] 

467 

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 

472 

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") 

481 

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]]) 

489 

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] = [] 

519 

520 # playersVpi | playersAtStreet1 | playersAtStreet2 | playersAtStreet3 | 

521 # playersAtStreet4 | playersAtShowdown | street0Raises | street1Raises | 

522 # street2Raises | street3Raises | street4Raises | street1Pot | street2Pot | 

523 # street3Pot | street4Pot | showdownPot | comment | commentTs | texture 

524 

525 # Actions 

526 q = db.sql.query['handActions'] 

527 q = q.replace('%s', db.sql.query['placeholder']) 

528 c.execute(q, (handId,)) 

529 

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) 

578 

579 self.totalPot() 

580 self.rake = self.totalpot - self.totalcollected 

581 

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.""" 

589 

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) 

592 

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) 

603 

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) 

612 

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) 

626 

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 

631 

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) 

638 

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] 

642 

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 

648 

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) 

666 

667 def addSTP(self, amount): 

668 amount = amount.replace(u',', u'') #some sites have commas 

669 amount = Decimal(amount) 

670 self.pot.setSTP(amount) 

671 

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 

687 

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) 

704 

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) 

712 

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) 

718 

719 street = 'BLAH' 

720 

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' 

727 

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]] 

733 

734 

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) 

751 

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) 

768 

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 

789 

790 self._addRaise(street, player, C, Rb, Rt) 

791 

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 

802 

803 self._addRaise(street, player, C, Rb, Rt) 

804 

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) 

815 

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) 

824 

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) 

836 

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) 

844 

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')) 

853 

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')) 

858 

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')) 

863 

864 def discardDrawHoleCards(self, cards, player, street): 

865 log.debug("discardDrawHoleCards '%s' '%s' '%s'", cards, player, street) 

866 self.discards[street][player] = set([cards]) 

867 

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) 

876 

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) 

885 

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) 

893 

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)) 

911 

912 def setUncalledBets(self, value): 

913 self.uncalledbets = value 

914 

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 

924 

925 if self.adjustCollected: 

926 self.stats.awardPots(self) 

927 

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 

938 

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) 

947 

948 if self.totalcollected is None: 

949 self.totalcollected = 0 

950 for entry in self.collected: 

951 self.totalcollected += Decimal(entry[1]) 

952 

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 } 

975 

976 log.debug("gametype: %s", self.gametype) 

977 retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']]) 

978 return retstring 

979 

980 def printHand(self): 

981 self.writeHand(sys.stdout) 

982 

983 def actionString(self, act, street=None): 

984 log.debug("Hand.actionString(act=%s, street=%s)", act, street) 

985 

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])) 

1020 

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) 

1042 

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) 

1051 

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] 

1059 

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) 

1063 

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 

1072 

1073 def writeGameLine(self): 

1074 """Return the first HH line for the current hand.""" 

1075 gs = "PokerStars Game #%s: " % self.handid 

1076 

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()) 

1088 

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 

1098 

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 

1110 

1111 

1112 def writeHand(self, fh=sys.__stdout__): 

1113 # PokerStars format. 

1114 print(self.writeGameLine(), file=fh) 

1115 print(self.writeTableLine(), file=fh) 

1116 

1117 

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" 

1147 

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) 

1158 

1159 if self.cancelled: 

1160 return 

1161 

1162 hhc.readBlinds(self) 

1163 

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")) 

1194 

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) 

1204 

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 

1213 

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] 

1239 

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?") 

1253 

1254 if asList: 

1255 return hcs 

1256 else: 

1257 return " ".join(hcs) 

1258 

1259 def writeHand(self, fh=sys.__stdout__): 

1260 # PokerStars format. 

1261 super(HoldemOmahaHand, self).writeHand(fh) 

1262 

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) 

1268 

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) 

1273 

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) 

1280 

1281 if self.actions['PREFLOP']: 

1282 for act in self.actions['PREFLOP']: 

1283 print(self.actionString(act), file=fh) 

1284 

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) 

1290 

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) 

1296 

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) 

1302 

1303 

1304 

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) 

1320 

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) 

1332 

1333 print(("*** SUMMARY ***"), file=fh) 

1334 print("%s | Rake %s%.2f" % (self.pot, self.sym, self.rake), file=fh) 

1335 

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) 

1341 

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) 

1362 

1363 print("\n\n", file=fh) 

1364 

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) 

1421 

1422 elif builtFrom == "DB": 

1423 # Creator expected to call hhc.select(hid) to fill out object 

1424 self.maxseats = 10 

1425 

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 

1436 

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) 

1443 

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 

1448 

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] 

1456 

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) 

1470 

1471 def writeHand(self, fh=sys.__stdout__): 

1472 # PokerStars format. 

1473 # HH output should not be translated 

1474 super(DrawHand, self).writeHand(fh) 

1475 

1476 players_who_act_ondeal = set(([x[0] for x in self.actions['DEAL']]+[x[0] for x in self.actions['BLINDSANTES']])) 

1477 

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) 

1481 

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) 

1485 

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) 

1495 

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) 

1505 

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) 

1515 

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) 

1525 

1526 if 'SHOWDOWN' in self.actions: 

1527 print(("*** SHOW DOWN ***"), file=fh) 

1528 #TODO: Complete SHOWDOWN 

1529 

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) 

1541 

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) 

1545 

1546 

1547 

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 

1553 

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) 

1605 

1606 elif builtFrom == "DB": 

1607 # Creator expected to call hhc.select(hid) to fill out object 

1608 self.maxseats = 10 

1609 

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 

1628 

1629 

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) 

1641 

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') 

1658 

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) 

1675 

1676 def writeHand(self, fh=sys.__stdout__): 

1677 # PokerStars format. 

1678 # HH output should not be translated 

1679 super(StudHand, self).writeHand(fh) 

1680 

1681 players_who_post_antes = set([x[0] for x in self.actions['BLINDSANTES']]) 

1682 

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) 

1686 

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) 

1690 

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) 

1702 

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) 

1713 

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) 

1724 

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) 

1735 

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) 

1748 

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. 

1755 

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) 

1767 

1768 print(("*** SUMMARY ***"), file=fh) 

1769 print("%s | Rake %s%.2f" % (self.pot, self.sym, self.rake), file=fh) 

1770# TODO: side pots 

1771 

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) 

1777 

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) 

1793 

1794 print("\n\n", file=fh) 

1795 

1796 

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]) + ']' 

1804 

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]) + "]" 

1807 

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] 

1824 

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) 

1852 

1853 

1854class Pot(object): 

1855 

1856 

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 

1869 

1870 def setSym(self, sym): 

1871 self.sym = sym 

1872 

1873 def addPlayer(self,player): 

1874 self.committed[player] = Decimal(0) 

1875 self.common[player] = Decimal(0) 

1876 self.antes[player] = Decimal(0) 

1877 

1878 def removePlayer(self,player): 

1879 del self.committed[player] 

1880 del self.common[player] 

1881 del self.antes[player] 

1882 

1883 def addFold(self, player): 

1884 # addFold must be called when a player folds 

1885 self.contenders.discard(player) 

1886 

1887 def addCommonMoney(self, player, amount): 

1888 self.common[player] += amount 

1889 

1890 def addAntes(self, player, amount): 

1891 self.antes[player] += amount 

1892 

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 

1897 

1898 def removeMoney(self, player, amount): 

1899 self.committed[player] -= amount 

1900 self.returned[player] = amount 

1901 

1902 def setSTP(self, amount): 

1903 self.stp = amount 

1904 

1905 def markTotal(self, street): 

1906 self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values()) + self.stp 

1907 

1908 def getTotalAtStreet(self, street): 

1909 if street in self.streettotals: 

1910 return self.streettotals[street] 

1911 return 0 

1912 

1913 def end(self): 

1914 # Initialize 

1915 print("Starting pot calculation...") 

1916 

1917 self.total = sum(self.committed.values()) + sum(self.common.values()) + self.stp 

1918 

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") 

1934 

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 

1941 

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 

1949 

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)) ]) 

1955 

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 

1959 

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}") 

1963 

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) 

1973 

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}") 

1978 

1979 return hand_instance