Coverage for Hand.py: 9%

1412 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-07 02:19 +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# import L10n 

21# _ = L10n.get_translation() 

22 

23# TODO: get writehand() encoding correct 

24 

25import sys 

26from decimal import Decimal 

27import datetime 

28 

29import pprint 

30 

31import logging 

32 

33 

34import Configuration 

35from Exceptions import FpdbHandDuplicate, FpdbHandPartial, FpdbParseError 

36import DerivedStats 

37import Card 

38 

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

40 

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

42log = logging.getLogger("hand") 

43 

44 

45class Hand(object): 

46 UPS = {"a": "A", "t": "T", "j": "J", "q": "Q", "k": "K", "S": "s", "C": "c", "H": "h", "D": "d"} 

47 LCS = {"H": "h", "D": "d", "C": "c", "S": "s"} 

48 SYMBOL = { 

49 "USD": "$", 

50 "CAD": "C$", 

51 "EUR": "€", 

52 "GBP": "£", 

53 "SEK": "kr.", 

54 "RSD": "РСД", 

55 "mBTC": "ⓑ", 

56 "INR": "₹", 

57 "CNY": "¥", 

58 "T$": "", 

59 "play": "", 

60 } 

61 MS = {"horse": "HORSE", "8game": "8-Game", "hose": "HOSE", "ha": "HA"} 

62 ACTION = { 

63 "ante": 1, 

64 "small blind": 2, 

65 "secondsb": 3, 

66 "big blind": 4, 

67 "both": 5, 

68 "calls": 6, 

69 "raises": 7, 

70 "bets": 8, 

71 "stands pat": 9, 

72 "folds": 10, 

73 "checks": 11, 

74 "discards": 12, 

75 "bringin": 13, 

76 "completes": 14, 

77 "straddle": 15, 

78 "button blind": 16, 

79 "cashout": 17, 

80 } 

81 

82 def __init__(self, config, sitename, gametype, handText, builtFrom="HHC"): 

83 self.config = config 

84 self.saveActions = self.config.get_import_parameters().get("saveActions") 

85 self.callHud = self.config.get_import_parameters().get("callFpdbHud") 

86 self.cacheSessions = self.config.get_import_parameters().get("cacheSessions") 

87 self.publicDB = self.config.get_import_parameters().get("publicDB") 

88 self.sitename = sitename 

89 self.siteId = self.config.get_site_id(sitename) 

90 self.stats = DerivedStats.DerivedStats() 

91 self.gametype = gametype 

92 self.startTime = 0 

93 self.handText = handText 

94 self.handid = 0 

95 self.in_path = None 

96 self.cancelled = False 

97 self.dbid_hands = 0 

98 self.dbid_pids = None 

99 self.dbid_hpid = None 

100 self.dbid_gt = 0 

101 self.tablename = "" 

102 self.hero = "" 

103 self.maxseats = None 

104 self.counted_seats = 0 

105 self.buttonpos = 0 

106 self.runItTimes = 0 

107 self.uncalledbets = False 

108 self.checkForUncalled = False 

109 self.adjustCollected = False 

110 self.cashedOut = False 

111 

112 # tourney stuff 

113 self.tourNo = None 

114 self.tourneyId = None 

115 self.tourneyName = None 

116 self.tourneyTypeId = None 

117 self.buyin = None 

118 self.buyinCurrency = None 

119 self.buyInChips = None 

120 self.fee = None # the Database code is looking for this one .. ? 

121 self.level = None 

122 self.mixed = None 

123 self.speed = "Normal" 

124 self.isSng = False 

125 self.isRebuy = False 

126 self.rebuyCost = 0 

127 self.isAddOn = False 

128 self.addOnCost = 0 

129 self.isKO = False 

130 self.koBounty = 0 

131 self.isProgressive = False 

132 self.isMatrix = False 

133 self.isShootout = False 

134 self.isFast = False 

135 self.stack = "Regular" 

136 self.isStep = False 

137 self.stepNo = 0 

138 self.isChance = False 

139 self.chanceCount = 0 

140 self.isMultiEntry = False 

141 self.isReEntry = False 

142 self.isNewToGame = False 

143 self.isHomeGame = False 

144 self.isSplit = False 

145 self.isFifty50 = False 

146 self.isTime = False 

147 self.timeAmt = 0 

148 self.isSatellite = False 

149 self.isDoubleOrNothing = False 

150 self.isCashOut = False 

151 self.isOnDemand = False 

152 self.isFlighted = False 

153 self.isGuarantee = False 

154 self.guaranteeAmt = 0 

155 self.added = None 

156 self.addedCurrency = None 

157 self.entryId = 1 

158 

159 self.seating = [] 

160 self.players = [] 

161 # Cache used for checkPlayerExists. 

162 self.player_exists_cache = set() 

163 self.posted = [] 

164 self.tourneysPlayersIds = {} 

165 

166 # Collections indexed by street names 

167 self.bets = {} 

168 self.lastBet = {} 

169 self.streets = {} 

170 self.actions = {} # [['mct','bets','$10'],['mika','folds'],['carlg','raises','$20']] 

171 self.board = {} # dict from street names to community cards 

172 self.holecards = {} 

173 self.discards = {} 

174 self.showdownStrings = {} 

175 for street in self.allStreets: 

176 self.streets[street] = "" # portions of the handText, filled by markStreets() 

177 self.actions[street] = [] 

178 for street in self.actionStreets: 

179 self.bets[street] = {} 

180 self.lastBet[street] = 0 

181 self.board[street] = [] 

182 for street in self.holeStreets: 

183 self.holecards[street] = {} # dict from player names to holecards 

184 for street in self.discardStreets: 

185 self.discards[ 

186 street 

187 ] = {} # dict from player names to dicts by street ... of tuples ... of discarded holecards 

188 # Collections indexed by player names 

189 self.rakes = {} 

190 self.stacks = {} 

191 self.collected = [] # list of ? 

192 self.collectees = {} # dict from player names to amounts collected (?) 

193 self.koCounts = {} 

194 self.endBounty = {} 

195 

196 # Sets of players 

197 self.folded = set() 

198 self.dealt = set() # 'dealt to' line to be printed 

199 self.shown = set() # cards were shown 

200 self.mucked = set() # cards were mucked at showdown 

201 self.sitout = set() # players sitting out or not dealt in (usually tournament) 

202 

203 # Things to do with money 

204 self.pot = Pot() 

205 self.totalpot = None 

206 self.totalcollected = None 

207 self.rake = None 

208 self.roundPenny = False 

209 self.fastFold = False 

210 # currency symbol for this hand 

211 self.sym = self.SYMBOL[self.gametype["currency"]] # save typing! delete this attr when done 

212 self.pot.setSym(self.sym) 

213 self.is_duplicate = False # i.e. don't update hudcache if true 

214 

215 def __str__(self): 

216 vars = ( 

217 (("BB"), self.bb), 

218 (("SB"), self.sb), 

219 (("BUTTON POS"), self.buttonpos), 

220 (("HAND NO."), self.handid), 

221 (("SITE"), self.sitename), 

222 (("TABLE NAME"), self.tablename), 

223 (("HERO"), self.hero), 

224 (("MAX SEATS"), self.maxseats), 

225 (("LEVEL"), self.level), 

226 (("MIXED"), self.mixed), 

227 (("LAST BET"), self.lastBet), 

228 (("ACTION STREETS"), self.actionStreets), 

229 (("STREETS"), self.streets), 

230 (("ALL STREETS"), self.allStreets), 

231 (("COMMUNITY STREETS"), self.communityStreets), 

232 (("HOLE STREETS"), self.holeStreets), 

233 (("COUNTED SEATS"), self.counted_seats), 

234 (("DEALT"), self.dealt), 

235 (("SHOWN"), self.shown), 

236 (("MUCKED"), self.mucked), 

237 (("TOTAL POT"), self.totalpot), 

238 (("TOTAL COLLECTED"), self.totalcollected), 

239 (("RAKE"), self.rake), 

240 (("START TIME"), self.startTime), 

241 (("TOURNAMENT NO"), self.tourNo), 

242 (("TOURNEY ID"), self.tourneyId), 

243 (("TOURNEY TYPE ID"), self.tourneyTypeId), 

244 (("BUYIN"), self.buyin), 

245 (("BUYIN CURRENCY"), self.buyinCurrency), 

246 (("BUYIN CHIPS"), self.buyInChips), 

247 (("FEE"), self.fee), 

248 (("IS REBUY"), self.isRebuy), 

249 (("IS ADDON"), self.isAddOn), 

250 (("IS KO"), self.isKO), 

251 (("KO BOUNTY"), self.koBounty), 

252 (("IS MATRIX"), self.isMatrix), 

253 (("IS SHOOTOUT"), self.isShootout), 

254 ) 

255 

256 structs = ( 

257 (("PLAYERS"), self.players), 

258 (("STACKS"), self.stacks), 

259 (("POSTED"), self.posted), 

260 (("POT"), self.pot), 

261 (("SEATING"), self.seating), 

262 (("GAMETYPE"), self.gametype), 

263 (("ACTION"), self.actions), 

264 (("COLLECTEES"), self.collectees), 

265 (("BETS"), self.bets), 

266 (("BOARD"), self.board), 

267 (("DISCARDS"), self.discards), 

268 (("HOLECARDS"), self.holecards), 

269 (("TOURNEYS PLAYER IDS"), self.tourneysPlayersIds), 

270 ) 

271 result = "" 

272 for name, var in vars: 

273 result = result + "\n%s = " % name + pprint.pformat(var) 

274 

275 for name, struct in structs: 

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

277 return result 

278 

279 def addHoleCards(self, street, player, open=[], closed=[], shown=False, mucked=False, dealt=False): 

280 """Assigns observed holecards to a player. 

281 cards list of card bigrams e.g. ['2h','Jc'] 

282 player (string) name of player 

283 shown whether they were revealed at showdown 

284 mucked whether they were mucked at showdown 

285 dealt whether they were seen in a 'dealt to' line""" 

286 log.debug( 

287 "Hand.addHoleCards open+closed: %s, player: %s, shown: %s, mucked: %s, dealt: %s", 

288 open + closed, 

289 player, 

290 shown, 

291 mucked, 

292 dealt, 

293 ) 

294 self.checkPlayerExists(player, "addHoleCards") 

295 

296 if dealt: 

297 self.dealt.add(player) 

298 if shown: 

299 self.shown.add(player) 

300 if mucked: 

301 self.mucked.add(player) 

302 

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

304 if closed[i] in ("", "Xx", "Null", "null", "X"): 

305 closed[i] = "0x" 

306 

307 try: 

308 self.holecards[street][player] = [open, closed] 

309 except KeyError as e: 

310 log.error(("Hand.addHoleCards: '%s': Major failure while adding holecards: '%s'"), self.handid, e) 

311 raise FpdbParseError 

312 

313 def prepInsert(self, db, printtest=False): 

314 ##### 

315 # Players, Gametypes, TourneyTypes are all shared functions that are needed for additional tables 

316 # These functions are intended for prep insert eventually 

317 ##### 

318 if self.gametype.get("maxSeats") is None: 

319 self.gametype["maxSeats"] = self.maxseats # TODO: move up to individual parsers 

320 else: 

321 self.maxseats = self.gametype["maxSeats"] 

322 self.dbid_pids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId, self.hero) 

323 self.dbid_gt = db.getSqlGameTypeId(self.siteId, self.gametype, printdata=printtest) 

324 

325 # Gametypes 

326 hilo = Card.games[self.gametype["category"]][2] 

327 

328 self.gametyperow = ( 

329 self.siteId, 

330 self.gametype["currency"], 

331 self.gametype["type"], 

332 self.gametype["base"], 

333 self.gametype["category"], 

334 self.gametype["limitType"], 

335 hilo, 

336 self.gametype["mix"], 

337 int(Decimal(self.gametype["sb"]) * 100), 

338 int(Decimal(self.gametype["bb"]) * 100), 

339 int(Decimal(self.gametype["bb"]) * 100), 

340 int(Decimal(self.gametype["bb"]) * 200), 

341 int(self.gametype["maxSeats"]), 

342 int(self.gametype["ante"] * 100), 

343 self.gametype["buyinType"], 

344 self.gametype["fast"], 

345 self.gametype["newToGame"], 

346 self.gametype["homeGame"], 

347 self.gametype["split"], 

348 ) 

349 # Note: the above data is calculated in db.getGameTypeId 

350 # Only being calculated above so we can grab the testdata 

351 if self.tourNo is not None: 

352 self.tourneyTypeId = db.getSqlTourneyTypeIDs(self) 

353 self.tourneyId = db.getSqlTourneyIDs(self) 

354 self.tourneysPlayersIds = db.getSqlTourneysPlayersIDs(self) 

355 

356 def assembleHand(self): 

357 self.stats.getStats(self) 

358 self.hands = self.stats.getHands() 

359 self.handsplayers = self.stats.getHandsPlayers() 

360 self.handsactions = self.stats.getHandsActions() 

361 self.handsstove = self.stats.getHandsStove() 

362 self.handspots = self.stats.getHandsPots() 

363 

364 def getHandId(self, db, id): 

365 if db.isDuplicate(self.siteId, self.hands["siteHandNo"], self.hands["heroSeat"], self.publicDB): 

366 # log.debug(("Hand.insert(): hid #: %s is a duplicate") % self.hands['siteHandNo']) 

367 self.is_duplicate = True # i.e. don't update hudcache 

368 next = id 

369 if self.publicDB: 

370 raise FpdbHandDuplicate( 

371 "%s-%s-%s" % (str(self.siteId), str(self.hands["siteHandNo"]), self.hands["heroSeat"]) 

372 ) 

373 else: 

374 raise FpdbHandDuplicate("%s-%s" % (str(self.siteId), str(self.hands["siteHandNo"]))) 

375 else: 

376 self.dbid_hands = id 

377 self.hands["id"] = self.dbid_hands 

378 next = id + db.hand_inc 

379 return next 

380 

381 def insertHands(self, db, fileId, doinsert=False, printtest=False): 

382 """Function to insert Hand into database 

383 Should not commit, and do minimal selects. Callers may want to cache commits 

384 db: a connected Database object""" 

385 self.hands["gametypeId"] = self.dbid_gt 

386 self.hands["seats"] = len(self.dbid_pids) 

387 self.hands["fileId"] = fileId 

388 db.storeHand(self.hands, doinsert, printtest) 

389 db.storeBoards(self.dbid_hands, self.hands["boards"], doinsert) 

390 

391 def insertHandsPlayers(self, db, doinsert=False, printtest=False): 

392 """Function to inserts HandsPlayers into database""" 

393 db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, self.handsplayers, doinsert, printtest) 

394 if self.handspots: 

395 self.handspots.sort(key=lambda x: x[1]) 

396 for ht in self.handspots: 

397 ht[0] = self.dbid_hands 

398 db.storeHandsPots(self.handspots, doinsert) 

399 

400 def insertHandsActions(self, db, doinsert=False, printtest=False): 

401 """Function to inserts HandsActions into database""" 

402 if self.saveActions: 

403 db.storeHandsActions(self.dbid_hands, self.dbid_pids, self.handsactions, doinsert, printtest) 

404 

405 def insertHandsStove(self, db, doinsert=False): 

406 """Function to inserts HandsStove into database""" 

407 if self.handsstove: 

408 for hs in self.handsstove: 

409 hs[0] = self.dbid_hands 

410 db.storeHandsStove(self.handsstove, doinsert) 

411 

412 def updateTourneyResults(self, db): 

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

414 db.updateTourneyPlayerBounties(self) 

415 

416 def updateHudCache(self, db, doinsert=False): 

417 """Function to update the HudCache""" 

418 if self.callHud: 

419 db.storeHudCache(self.dbid_gt, self.gametype, self.dbid_pids, self.startTime, self.handsplayers, doinsert) 

420 

421 def updateSessionsCache(self, db, tz, doinsert=False): 

422 """Function to update the Sessions""" 

423 if self.cacheSessions: 

424 heroes = [list(self.dbid_pids.values())[0]] 

425 db.storeSessions(self.dbid_hands, self.dbid_pids, self.startTime, self.tourneyId, heroes, tz, doinsert) 

426 db.storeSessionsCache( 

427 self.dbid_hands, 

428 self.dbid_pids, 

429 self.startTime, 

430 self.dbid_gt, 

431 self.gametype, 

432 self.handsplayers, 

433 heroes, 

434 doinsert, 

435 ) 

436 db.storeTourneysCache( 

437 self.dbid_hands, 

438 self.dbid_pids, 

439 self.startTime, 

440 self.tourneyId, 

441 self.gametype, 

442 self.handsplayers, 

443 heroes, 

444 doinsert, 

445 ) 

446 

447 def updateCardsCache(self, db, tz, doinsert=False): 

448 """Function to update the CardsCache""" 

449 if self.cacheSessions: # and self.hero in self.dbid_pids: 

450 heroes = [list(self.dbid_pids.values())[0]] 

451 db.storeCardsCache( 

452 self.dbid_hands, 

453 self.dbid_pids, 

454 self.startTime, 

455 self.dbid_gt, 

456 self.tourneyTypeId, 

457 self.handsplayers, 

458 heroes, 

459 tz, 

460 doinsert, 

461 ) 

462 

463 def updatePositionsCache(self, db, tz, doinsert=False): 

464 """Function to update the PositionsCache""" 

465 if self.cacheSessions: # and self.hero in self.dbid_pids: 

466 heroes = [list(self.dbid_pids.values())[0]] 

467 db.storePositionsCache( 

468 self.dbid_hands, 

469 self.dbid_pids, 

470 self.startTime, 

471 self.dbid_gt, 

472 self.tourneyTypeId, 

473 self.handsplayers, 

474 self.hands, 

475 heroes, 

476 tz, 

477 doinsert, 

478 ) 

479 

480 def select(self, db, handId): 

481 """Function to create Hand object from database""" 

482 c = db.get_cursor() 

483 q = db.sql.query["playerHand"] 

484 q = q.replace("%s", db.sql.query["placeholder"]) 

485 

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

487 heroSeat = c.fetchone()[0] 

488 

489 # PlayerStacks 

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

491 # See NOTE: below on what this does. 

492 

493 # Discripter must be set to lowercase as postgres returns all descriptors lower case and SQLight returns them as they are 

494 res = [ 

495 dict(line) 

496 for line in [list(zip([column[0].lower() for column in c.description], row)) for row in c.fetchall()] 

497 ] 

498 for row in res: 

499 self.addPlayer( 

500 row["seatno"], row["name"], str(row["chips"]), str(row["position"]), row["sitout"], str(row["bounty"]) 

501 ) 

502 cardlist = [] 

503 cardlist.append(Card.valueSuitFromCard(row["card1"])) 

504 cardlist.append(Card.valueSuitFromCard(row["card2"])) 

505 cardlist.append(Card.valueSuitFromCard(row["card3"])) 

506 cardlist.append(Card.valueSuitFromCard(row["card4"])) 

507 cardlist.append(Card.valueSuitFromCard(row["card5"])) 

508 cardlist.append(Card.valueSuitFromCard(row["card6"])) 

509 cardlist.append(Card.valueSuitFromCard(row["card7"])) 

510 cardlist.append(Card.valueSuitFromCard(row["card8"])) 

511 cardlist.append(Card.valueSuitFromCard(row["card9"])) 

512 cardlist.append(Card.valueSuitFromCard(row["card10"])) 

513 cardlist.append(Card.valueSuitFromCard(row["card11"])) 

514 cardlist.append(Card.valueSuitFromCard(row["card12"])) 

515 cardlist.append(Card.valueSuitFromCard(row["card13"])) 

516 cardlist.append(Card.valueSuitFromCard(row["card14"])) 

517 cardlist.append(Card.valueSuitFromCard(row["card15"])) 

518 cardlist.append(Card.valueSuitFromCard(row["card16"])) 

519 cardlist.append(Card.valueSuitFromCard(row["card17"])) 

520 cardlist.append(Card.valueSuitFromCard(row["card18"])) 

521 cardlist.append(Card.valueSuitFromCard(row["card19"])) 

522 cardlist.append(Card.valueSuitFromCard(row["card20"])) 

523 # mucked/shown/dealt is not in the database, use mucked for villain and dealt for hero 

524 if row["seatno"] == heroSeat: 

525 dealt = True 

526 mucked = False 

527 else: 

528 dealt = False 

529 mucked = True 

530 game = Card.games[self.gametype["category"]] 

531 if game[0] == "hold" and cardlist[0] != "": 

532 self.addHoleCards( 

533 "PREFLOP", row["name"], closed=cardlist[0 : game[5][0][1]], shown=False, mucked=mucked, dealt=dealt 

534 ) 

535 elif game[0] == "stud" and cardlist[2] != "": 

536 streets = dict((v, k) for (k, v) in list(game[3].items())) 

537 for streetidx, hrange in enumerate(game[5]): 

538 # FIXME shown/dealt/mucked might need some tweaking 

539 self.addHoleCards( 

540 streets[streetidx], 

541 row["name"], 

542 open=[cardlist[hrange[1] - 1]], 

543 closed=cardlist[0 : hrange[1] - 1], 

544 shown=False, 

545 mucked=False, 

546 ) 

547 elif game[0] == "draw": 

548 streets = dict((v, k) for (k, v) in list(game[3].items())) 

549 for streetidx, hrange in enumerate(game[5]): 

550 self.addHoleCards( 

551 streets[streetidx], 

552 row["name"], 

553 closed=cardlist[hrange[0] : hrange[1]], 

554 shown=False, 

555 mucked=mucked, 

556 dealt=dealt, 

557 ) 

558 if row["winnings"] > 0: 

559 self.addCollectPot(row["name"], str(row["winnings"])) 

560 if row["position"] == "0": 

561 # position 0 is the button, heads-up there is no position 0 

562 self.buttonpos = row["seatno"] 

563 elif row["position"] == "B": 

564 # Headsup the BB is the button, only set the button position if it's not set before 

565 if self.buttonpos is None or self.buttonpos == 0: 

566 self.buttonpos = row["seatno"] 

567 

568 # HandInfo 

569 q = db.sql.query["singleHand"] 

570 q = q.replace("%s", db.sql.query["placeholder"]) 

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

572 

573 # NOTE: This relies on row_factory = sqlite3.Row (set in connect() params) 

574 # Need to find MySQL and Postgres equivalents 

575 # MySQL maybe: cursorclass=MySQLdb.cursors.DictCursor 

576 # res = c.fetchone() 

577 

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

579 # a similar result 

580 

581 # Discripter must be set to lowercase as supported dbs differ on what is returned. 

582 res = [ 

583 dict(line) 

584 for line in [list(zip([column[0].lower() for column in c.description], row)) for row in c.fetchall()] 

585 ] 

586 res = res[0] 

587 

588 self.tablename = res["tablename"] 

589 self.handid = res["sitehandno"] 

590 # FIXME: Need to figure out why some times come out of the DB as %Y-%m-%d %H:%M:%S+00:00, 

591 # and others as %Y-%m-%d %H:%M:%S 

592 

593 # self.startTime currently unused in the replayer and commented out. 

594 # Can't be done like this because not all dbs return the same type for starttime 

595 # try: 

596 # self.startTime = datetime.datetime.strptime(res['starttime'], "%Y-%m-%d %H:%M:%S+00:00") 

597 # except ValueError: 

598 # self.startTime = datetime.datetime.strptime(res['starttime'], "%Y-%m-%d %H:%M:%S") 

599 # However a startTime is needed for a valid output by writeHand: 

600 self.startTime = datetime.datetime.strptime("1970-01-01 12:00:00", "%Y-%m-%d %H:%M:%S") 

601 

602 cards = list( 

603 map( 

604 Card.valueSuitFromCard, 

605 [res["boardcard1"], res["boardcard2"], res["boardcard3"], res["boardcard4"], res["boardcard5"]], 

606 ) 

607 ) 

608 if cards[0]: 

609 self.setCommunityCards("FLOP", cards[0:3]) 

610 if cards[3]: 

611 self.setCommunityCards("TURN", [cards[3]]) 

612 if cards[4]: 

613 self.setCommunityCards("RIVER", [cards[4]]) 

614 

615 if res["runittwice"] or self.gametype["split"]: 

616 # Get runItTwice boards 

617 q = db.sql.query["singleHandBoards"] 

618 q = q.replace("%s", db.sql.query["placeholder"]) 

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

620 boards = [ 

621 dict(line) 

622 for line in [list(zip([column[0].lower() for column in c.description], row)) for row in c.fetchall()] 

623 ] 

624 for b in boards: 

625 cards = list( 

626 map( 

627 Card.valueSuitFromCard, 

628 [b["boardcard1"], b["boardcard2"], b["boardcard3"], b["boardcard4"], b["boardcard5"]], 

629 ) 

630 ) 

631 if cards[0]: 

632 street = "FLOP" + str(b["boardid"]) 

633 self.setCommunityCards(street, cards[0:3]) 

634 if "FLOP" in self.allStreets: 

635 self.allStreets.remove("FLOP") 

636 self.allStreets.append(street) 

637 self.actions[street] = [] 

638 if cards[3]: 

639 street = "TURN" + str(b["boardid"]) 

640 self.setCommunityCards(street, [cards[3]]) 

641 if "TURN" in self.allStreets: 

642 self.allStreets.remove("TURN") 

643 self.allStreets.append(street) 

644 self.actions[street] = [] 

645 if cards[4]: 

646 street = "RIVER" + str(b["boardid"]) 

647 self.setCommunityCards(street, [cards[4]]) 

648 if "RIVER" in self.allStreets: 

649 self.allStreets.remove("RIVER") 

650 self.allStreets.append(street) 

651 self.actions[street] = [] 

652 

653 # playersVpi | playersAtStreet1 | playersAtStreet2 | playersAtStreet3 | 

654 # playersAtStreet4 | playersAtShowdown | street0Raises | street1Raises | 

655 # street2Raises | street3Raises | street4Raises | street1Pot | street2Pot | 

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

657 

658 # Actions 

659 q = db.sql.query["handActions"] 

660 q = q.replace("%s", db.sql.query["placeholder"]) 

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

662 

663 # Discripter must be set to lowercase as supported dbs differ on what is returned. 

664 res = [ 

665 dict(line) 

666 for line in [list(zip([column[0].lower() for column in c.description], row)) for row in c.fetchall()] 

667 ] 

668 for row in res: 

669 name = row["name"] 

670 street = row["street"] 

671 act = row["actionid"] 

672 # allin True/False if row['allIn'] == 0 

673 bet = str(row["bet"]) 

674 street = self.allStreets[int(street) + 1] 

675 discards = row["cardsdiscarded"] 

676 log.debug("Hand.select():: name: '%s' street: '%s' act: '%s' bet: '%s'", name, street, act, bet) 

677 if act == 1: # Ante 

678 self.addAnte(name, bet) 

679 elif act == 2: # Small Blind 

680 self.addBlind(name, "small blind", bet) 

681 elif act == 3: # Second small blind 

682 self.addBlind(name, "secondsb", bet) 

683 elif act == 4: # Big Blind 

684 self.addBlind(name, "big blind", bet) 

685 elif act == 5: # Post both blinds 

686 self.addBlind(name, "both", bet) 

687 elif act == 6: # Call 

688 self.addCall(street, name, bet) 

689 elif act == 7: # Raise 

690 self.addRaiseBy(street, name, bet) 

691 elif act == 8: # Bet 

692 self.addBet(street, name, bet) 

693 elif act == 9: # Stands pat 

694 self.addStandsPat(street, name, discards) 

695 elif act == 10: # Fold 

696 self.addFold(street, name) 

697 elif act == 11: # Check 

698 self.addCheck(street, name) 

699 elif act == 12: # Discard 

700 self.addDiscard(street, name, row["numdiscarded"], discards) 

701 elif act == 13: # Bringin 

702 self.addBringIn(name, bet) 

703 elif act == 14: # Complete 

704 self.addComplete(street, name, bet) 

705 elif act == 15: 

706 self.addBlind(name, "straddle", bet) 

707 elif act == 16: 

708 self.addBlind(name, "button blind", bet) 

709 elif act == 17: # Cashout 

710 self.addCashout(street, name) 

711 else: 

712 print("DEBUG: unknown action: '%s'" % act) 

713 

714 self.totalPot() 

715 self.rake = self.totalpot - self.totalcollected 

716 

717 def addPlayer(self, seat, name, chips, position=None, sitout=False, bounty=None): 

718 """Adds a player to the hand, and initialises data structures indexed by player. 

719 seat (int) indicating the seat 

720 name (string) player name 

721 chips (string) the chips the player has at the start of the hand (can be None) 

722 position (string) indicating the position of the player (S,B, 0-7) (optional, not needed on Hand import from Handhistory). 

723 If a player has None chips he won't be added.""" 

724 

725 if len(self.players) > 0 and seat in [p[0] for p in self.players]: 

726 raise FpdbHandPartial("addPlayer: " + ("Can't have 2 players in the same seat!") + ": '%s'" % self.handid) 

727 

728 log.debug("addPlayer: %s %s (%s)", seat, name, chips) 

729 if chips is not None: 

730 chips = chips.replace(",", "") # some sites have commas 

731 self.players.append([seat, name, chips, position, bounty]) 

732 self.stacks[name] = Decimal(chips) 

733 self.pot.addPlayer(name) 

734 for street in self.actionStreets: 

735 self.bets[street][name] = [] 

736 if sitout: 

737 self.sitout.add(name) 

738 

739 def removePlayer(self, name): 

740 if self.stacks.get(name): 

741 self.players = [p for p in self.players if p[1] != name] 

742 del self.stacks[name] 

743 self.pot.removePlayer(name) 

744 for street in self.actionStreets: 

745 del self.bets[street][name] 

746 self.sitout.discard(name) 

747 

748 def addStreets(self, match): 

749 # go through m and initialise actions to empty list for each street. 

750 if match: 

751 # print('if match', match) 

752 # print("if match.gr:",match.groupdict()) 

753 print("type self.streets", type(self.streets)) 

754 self.streets.update(match.groupdict()) 

755 print("streets:", str(self.streets)) 

756 log.debug("markStreets:\n" + str(self.streets)) 

757 else: 

758 tmp = self.handText[0:100] 

759 self.cancelled = True 

760 raise FpdbHandPartial( 

761 ("Streets didn't match - Assuming hand '%s' was cancelled.") % (self.handid) 

762 + " " 

763 + ("First 100 characters: %s") % tmp 

764 ) 

765 

766 def checkPlayerExists(self, player, source=None): 

767 # Fast path, because this function is called ALL THE TIME. 

768 if player in self.player_exists_cache: 

769 return 

770 

771 if player not in (p[1] for p in self.players): 

772 if source is not None: 

773 log.error(("Hand.%s: '%s' unknown player: '%s'"), source, self.handid, player) 

774 raise FpdbParseError 

775 else: 

776 self.player_exists_cache.add(player) 

777 

778 def setCommunityCards(self, street, cards): 

779 log.debug("setCommunityCards %s %s", street, cards) 

780 self.board[street] = [self.card(c) for c in cards] 

781 

782 def card(self, c): 

783 """upper case the ranks but not suits, 'atjqk' => 'ATJQK'""" 

784 for k, v in list(self.UPS.items()): 

785 c = c.replace(k, v) 

786 return c 

787 

788 def addAllIn(self, street, player, amount): 

789 """For sites (currently only Merge & Microgaming) which record "all in" as a special action, 

790 which can mean either "calls and is all in" or "raises all in".""" 

791 self.checkPlayerExists(player, "addAllIn") 

792 amount = amount.replace(",", "") # some sites have commas 

793 Ai = Decimal(amount) 

794 Bp = self.lastBet[street] 

795 Bc = sum(self.bets[street][player]) 

796 C = Bp - Bc 

797 if Ai <= C: 

798 self.addCall(street, player, amount) 

799 elif Bp == 0: 

800 self.addBet(street, player, amount) 

801 else: 

802 Rb = Ai - C 

803 Rt = Bp + Rb 

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

805 

806 def addSTP(self, amount): 

807 amount = amount.replace(",", "") # some sites have commas 

808 amount = Decimal(amount) 

809 self.pot.setSTP(amount) 

810 

811 def addAnte(self, player, ante): 

812 log.debug("%s %s antes %s", "BLINDSANTES", player, ante) 

813 if player is not None: 

814 ante = ante.replace(",", "") # some sites have commas 

815 self.checkPlayerExists(player, "addAnte") 

816 ante = Decimal(ante) 

817 self.bets["BLINDSANTES"][player].append(ante) 

818 self.stacks[player] -= ante 

819 act = (player, "ante", ante, self.stacks[player] == 0) 

820 self.actions["BLINDSANTES"].append(act) 

821 self.pot.addCommonMoney(player, ante) 

822 self.pot.addAntes(player, ante) 

823 if "ante" not in list(self.gametype.keys()) or self.gametype["ante"] < ante: 

824 self.gametype["ante"] = ante 

825 

826 # I think the antes should be common money, don't have enough hand history to check 

827 

828 def addBlind(self, player, blindtype, amount): 

829 # if player is None, it's a missing small blind. 

830 # The situation we need to cover are: 

831 # Player in small blind posts 

832 # - this is a bet of 1 sb, as yet uncalled. 

833 # Player in the big blind posts 

834 # - this is a call of 1 sb and a raise to 1 bb 

835 # 

836 log.debug("addBlind: %s posts %s, %s", player, blindtype, amount) 

837 if player is not None: 

838 self.checkPlayerExists(player, "addBlind") 

839 amount = amount.replace(",", "") # some sites have commas 

840 amount = Decimal(amount) 

841 self.stacks[player] -= amount 

842 act = (player, blindtype, amount, self.stacks[player] == 0) 

843 self.actions["BLINDSANTES"].append(act) 

844 

845 if blindtype == "both": 

846 # work with the real amount. limit games are listed as $1, $2, where 

847 # the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2.... 

848 amount = Decimal(str(self.bb)) 

849 sb = Decimal(str(self.sb)) 

850 self.bets["BLINDSANTES"][player].append(sb) 

851 self.pot.addCommonMoney(player, sb) 

852 

853 if blindtype == "secondsb": 

854 amount = Decimal(0) 

855 sb = Decimal(str(self.sb)) 

856 self.bets["BLINDSANTES"][player].append(sb) 

857 self.pot.addCommonMoney(player, sb) 

858 

859 street = "BLAH" 

860 

861 if self.gametype["category"] == "aof_omaha": 

862 street = "FLOP" 

863 elif self.gametype["base"] == "hold": 

864 street = "PREFLOP" 

865 elif self.gametype["base"] == "draw": 

866 street = "DEAL" 

867 

868 self.bets[street][player].append(amount) 

869 self.pot.addMoney(player, amount) 

870 if amount > self.lastBet.get(street): 

871 self.lastBet[street] = amount 

872 self.posted = self.posted + [[player, blindtype]] 

873 

874 def addCall(self, street, player=None, amount=None): 

875 if amount is not None: 

876 amount = amount.replace(",", "") # some sites have commas 

877 log.debug(("%s %s calls %s"), street, player, amount) 

878 # Potentially calculate the amount of the call if not supplied 

879 # corner cases include if player would be all in 

880 if amount is not None: 

881 self.checkPlayerExists(player, "addCall") 

882 amount = Decimal(amount) 

883 self.bets[street][player].append(amount) 

884 if street in ("PREFLOP", "DEAL", "THIRD") and self.lastBet.get(street) < amount: 

885 self.lastBet[street] = amount 

886 self.stacks[player] -= amount 

887 act = (player, "calls", amount, self.stacks[player] == 0) 

888 self.actions[street].append(act) 

889 self.pot.addMoney(player, amount) 

890 

891 def addCallTo(self, street, player=None, amountTo=None): 

892 if amountTo: 

893 amountTo = amountTo.replace(",", "") # some sites have commas 

894 # Potentially calculate the amount of the callTo if not supplied 

895 # corner cases include if player would be all in 

896 if amountTo is not None: 

897 self.checkPlayerExists(player, "addCallTo") 

898 Bc = sum(self.bets[street][player]) 

899 Ct = Decimal(amountTo) 

900 C = Ct - Bc 

901 amount = C 

902 self.bets[street][player].append(amount) 

903 self.stacks[player] -= amount 

904 act = (player, "calls", amount, self.stacks[player] == 0) 

905 self.actions[street].append(act) 

906 self.pot.addMoney(player, amount) 

907 

908 def addRaiseBy(self, street, player, amountBy): 

909 """Add a raise by amountBy on [street] by [player]""" 

910 # Given only the amount raised by, the amount of the raise can be calculated by 

911 # working out how much this player has already in the pot 

912 # (which is the sum of self.bets[street][player]) 

913 # and how much he needs to call to match the previous player 

914 # (which is tracked by self.lastBet) 

915 # let Bp = previous bet 

916 # Bc = amount player has committed so far 

917 # Rb = raise by 

918 # then: C = Bp - Bc (amount to call) 

919 # Rt = Bp + Rb (raise to) 

920 # 

921 amountBy = amountBy.replace(",", "") # some sites have commas 

922 self.checkPlayerExists(player, "addRaiseBy") 

923 Rb = Decimal(amountBy) 

924 Bp = self.lastBet[street] 

925 Bc = sum(self.bets[street][player]) 

926 C = Bp - Bc 

927 Rt = Bp + Rb 

928 

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

930 

931 def addCallandRaise(self, street, player, amount): 

932 """For sites which by "raises x" mean "calls and raises putting a total of x in the por".""" 

933 self.checkPlayerExists(player, "addCallandRaise") 

934 amount = amount.replace(",", "") # some sites have commas 

935 CRb = Decimal(amount) 

936 Bp = self.lastBet[street] 

937 Bc = sum(self.bets[street][player]) 

938 C = Bp - Bc 

939 Rb = CRb - C 

940 Rt = Bp + Rb 

941 

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

943 

944 def addRaiseTo(self, street, player, amountTo): 

945 """Add a raise on [street] by [player] to [amountTo]""" 

946 self.checkPlayerExists(player, "addRaiseTo") 

947 amountTo = amountTo.replace(",", "") # some sites have commas 

948 Bp = self.lastBet[street] 

949 Bc = sum(self.bets[street][player]) 

950 Rt = Decimal(amountTo) 

951 C = Bp - Bc 

952 Rb = Rt - C - Bc 

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

954 

955 def _addRaise(self, street, player, C, Rb, Rt, action="raises"): 

956 log.debug(("%s %s raise %s"), street, player, Rt) 

957 self.bets[street][player].append(C + Rb) 

958 self.stacks[player] -= C + Rb 

959 act = (player, action, Rb, Rt, C, self.stacks[player] == 0) 

960 self.actions[street].append(act) 

961 self.lastBet[street] = Rt # TODO check this is correct 

962 self.pot.addMoney(player, C + Rb) 

963 

964 def addBet(self, street, player, amount): 

965 log.debug(("%s %s bets %s"), street, player, amount) 

966 amount = amount.replace(",", "") # some sites have commas 

967 amount = Decimal(amount) 

968 self.checkPlayerExists(player, "addBet") 

969 self.bets[street][player].append(amount) 

970 self.stacks[player] -= amount 

971 act = (player, "bets", amount, self.stacks[player] == 0) 

972 self.actions[street].append(act) 

973 self.lastBet[street] = amount 

974 self.pot.addMoney(player, amount) 

975 

976 def addStandsPat(self, street, player, cards=None): 

977 self.checkPlayerExists(player, "addStandsPat") 

978 act = (player, "stands pat") 

979 self.actions[street].append(act) 

980 if cards: 

981 cards = cards.split(" ") 

982 self.addHoleCards(street, player, open=[], closed=cards) 

983 

984 def addFold(self, street, player): 

985 log.debug(("%s %s folds"), street, player) 

986 self.checkPlayerExists(player, "addFold") 

987 if player in self.folded: 

988 return 

989 self.folded.add(player) 

990 self.pot.addFold(player) 

991 self.actions[street].append((player, "folds")) 

992 

993 def addCheck(self, street, player): 

994 logging.debug(("%s %s checks"), street, player) 

995 self.checkPlayerExists(player, "addCheck") 

996 self.actions[street].append((player, "checks")) 

997 

998 def addCashout(self, street, player): 

999 logging.debug(("%s %s cashout"), street, player) 

1000 self.checkPlayerExists(player, "addCashout") 

1001 self.actions[street].append((player, "cashout")) 

1002 

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

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

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

1006 

1007 def addDiscard(self, street, player, num, cards=None): 

1008 self.checkPlayerExists(player, "addDiscard") 

1009 if cards: 

1010 act = (player, "discards", Decimal(num), cards) 

1011 self.discardDrawHoleCards(cards, player, street) 

1012 else: 

1013 act = (player, "discards", Decimal(num)) 

1014 self.actions[street].append(act) 

1015 

1016 def addCollectPot(self, player, pot): 

1017 log.debug("%s collected %s", player, pot) 

1018 self.checkPlayerExists(player, "addCollectPot") 

1019 self.collected = self.collected + [[player, pot]] 

1020 if player not in self.collectees: 

1021 self.collectees[player] = Decimal(pot) 

1022 else: 

1023 self.collectees[player] += Decimal(pot) 

1024 

1025 def addUncalled(self, street, player, amount): 

1026 log.debug(("%s %s uncalled %s"), street, player, amount) 

1027 amount = amount.replace(",", "") # some sites have commas 

1028 amount = Decimal(amount) 

1029 self.checkPlayerExists(player, "addUncalled") 

1030 self.stacks[player] += amount 

1031 self.pot.removeMoney(player, amount) 

1032 

1033 def sittingOut(self): 

1034 dealtIn = set() 

1035 for street in self.actionStreets: 

1036 for act in self.actions[street]: 

1037 dealtIn.add(act[0]) 

1038 for player in list(self.collectees.keys()): 

1039 dealtIn.add(player) 

1040 for player in self.dealt: 

1041 dealtIn.add(player) 

1042 for p in list(self.players): 

1043 if p[1] not in dealtIn: 

1044 if self.gametype["type"] == "tour": 

1045 self.sitout.add(p[1]) 

1046 else: 

1047 self.removePlayer(p[1]) 

1048 if len(self.players) < 2: 

1049 raise FpdbHandPartial(("Less than 2 players - Assuming hand '%s' was cancelled.") % (self.handid)) 

1050 

1051 def setUncalledBets(self, value): 

1052 self.uncalledbets = value 

1053 

1054 def totalPot(self): 

1055 """If all bets and blinds have been added, totals up the total pot size""" 

1056 if self.totalpot is None: 

1057 try: 

1058 self.pot.end() 

1059 self.totalpot = self.pot.total 

1060 except FpdbParseError as e: 

1061 log.error("Error in totalPot calculation: %s", e) 

1062 self.totalpot = 0 

1063 

1064 if self.adjustCollected: 

1065 self.stats.awardPots(self) 

1066 

1067 def gettempcontainers(collected, collectees): 

1068 collectedCopy, collecteesCopy, totalcollected = [], {}, 0 

1069 for v in sorted(collected, key=lambda collectee: collectee[1], reverse=True): 

1070 if Decimal(v[1]) != 0: 

1071 totalcollected += Decimal(v[1]) 

1072 collectedCopy.append([v[0], Decimal(v[1])]) 

1073 for k, j in collectees.items(): 

1074 if j != 0: 

1075 collecteesCopy[k] = j 

1076 return collectedCopy, collecteesCopy, totalcollected 

1077 

1078 collected, collectees, totalcollected = gettempcontainers(self.collected, self.collectees) 

1079 if self.uncalledbets or ((self.totalpot - totalcollected < 0) and self.checkForUncalled): 

1080 for i, v in enumerate(sorted(self.collected, key=lambda collectee: collectee[1], reverse=True)): 

1081 if v[0] in self.pot.returned: 

1082 collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]] 

1083 collectees[v[0]] -= self.pot.returned[v[0]] 

1084 self.pot.returned[v[0]] = 0 

1085 self.collected, self.collectees, self.totalcollected = gettempcontainers(collected, collectees) 

1086 

1087 if self.totalcollected is None: 

1088 self.totalcollected = 0 

1089 for entry in self.collected: 

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

1091 

1092 def getGameTypeAsString(self): 

1093 """Map the tuple self.gametype onto the pokerstars string describing it""" 

1094 # currently it appears to be something like ["ring", "hold", "nl", sb, bb]: 

1095 gs = { 

1096 "holdem": "Hold'em", 

1097 "omahahi": "Omaha", 

1098 "fusion": "Fusion", 

1099 "omahahilo": "Omaha Hi/Lo", 

1100 "razz": "Razz", 

1101 "studhi": "7 Card Stud", 

1102 "studhilo": "7 Card Stud Hi/Lo", 

1103 "fivedraw": "5 Card Draw", 

1104 "27_1draw": "Single Draw 2-7 Lowball", 

1105 "27_3draw": "Triple Draw 2-7 Lowball", 

1106 "5_studhi": "5 Card Stud", 

1107 "badugi": "Badugi", 

1108 } 

1109 ls = {"nl": "No Limit", "pl": "Pot Limit", "fl": "Limit", "cn": "Cap No Limit", "cp": "Cap Pot Limit"} 

1110 

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

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

1113 return retstring 

1114 

1115 def printHand(self): 

1116 self.writeHand(sys.stdout) 

1117 

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

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

1120 

1121 if act[1] == "folds": 

1122 return "%s: folds " % (act[0]) 

1123 elif act[1] == "checks": 

1124 return "%s: checks " % (act[0]) 

1125 elif act[1] == "cashout": 

1126 return "%s: cashout " % (act[0]) 

1127 elif act[1] == "calls": 

1128 return "%s: calls %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1129 elif act[1] == "bets": 

1130 return "%s: bets %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1131 elif act[1] == "raises": 

1132 return "%s: raises %s%s to %s%s%s" % ( 

1133 act[0], 

1134 self.sym, 

1135 act[2], 

1136 self.sym, 

1137 act[3], 

1138 " and is all-in" if act[5] else "", 

1139 ) 

1140 elif act[1] == "completes": 

1141 return "%s: completes to %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1142 elif act[1] == "small blind": 

1143 return "%s: posts small blind %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1144 elif act[1] == "big blind": 

1145 return "%s: posts big blind %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1146 elif act[1] == "straddle": 

1147 return "%s: straddles %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1148 elif act[1] == "button blind": 

1149 return "%s: posts button blind %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1150 elif act[1] == "both": 

1151 return "%s: posts small & big blinds %s%s%s" % ( 

1152 act[0], 

1153 self.sym, 

1154 act[2], 

1155 " and is all-in" if act[3] else "", 

1156 ) 

1157 elif act[1] == "ante": 

1158 return "%s: posts the ante %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1159 elif act[1] == "bringin": 

1160 return "%s: brings in for %s%s%s" % (act[0], self.sym, act[2], " and is all-in" if act[3] else "") 

1161 elif act[1] == "discards": 

1162 return "%s: discards %s %s%s" % ( 

1163 act[0], 

1164 act[2], 

1165 "card" if act[2] == 1 else "cards", 

1166 " [" + " ".join(self.discards[street][act[0]]) + "]" if self.hero == act[0] else "", 

1167 ) 

1168 elif act[1] == "stands pat": 

1169 return "%s: stands pat" % (act[0]) 

1170 

1171 def get_actions_short(self, player, street): 

1172 """Returns a string with shortcuts for the actions of the given player and the given street 

1173 F ... fold, X ... Check, B ...Bet, C ... Call, R ... Raise, CO ... CashOut 

1174 """ 

1175 actions = self.actions[street] 

1176 result = [] 

1177 for action in actions: 

1178 if player in action: 

1179 if action[1] == "folds": 

1180 result.append("F") 

1181 elif action[1] == "checks": 

1182 result.append("X") 

1183 elif action[1] == "bets": 

1184 result.append("B") 

1185 elif action[1] == "calls": 

1186 result.append("C") 

1187 elif action[1] == "raises": 

1188 result.append("R") 

1189 elif action[1] == "cashout": 

1190 result.append("CO") 

1191 return "".join(result) 

1192 

1193 def get_actions_short_streets(self, player, *streets): 

1194 """Returns a string with shortcuts for the actions of the given player on all given streets seperated by ','""" 

1195 result = [] 

1196 for street in streets: 

1197 action = self.get_actions_short(player, street) 

1198 if len(action) > 0: # if there is no action on later streets, nothing is added. 

1199 result.append(action) 

1200 return ",".join(result) 

1201 

1202 def get_player_position(self, player): 

1203 """Returns the given players postion (S, B, 0-7)""" 

1204 # position has been added to the players list. It could be calculated from buttonpos and player seatnums, 

1205 # but whats the point in calculating a value that has been there anyway? 

1206 for p in self.players: 

1207 if p[1] == player: 

1208 return p[3] 

1209 

1210 def getStakesAsString(self): 

1211 """Return a string of the stakes of the current hand.""" 

1212 return "%s%s/%s%s" % (self.sym, self.sb, self.sym, self.bb) 

1213 

1214 def getStreetTotals(self): 

1215 tmp, i = [0, 0, 0, 0, 0, 0], 0 

1216 for street in self.allStreets: 

1217 if street != "BLINDSANTES": 

1218 tmp[i] = self.pot.getTotalAtStreet(street) 

1219 i += 1 

1220 tmp[5] = sum(self.pot.committed.values()) + sum(self.pot.common.values()) 

1221 return tmp 

1222 

1223 def writeGameLine(self): 

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

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

1226 

1227 if self.tourNo is not None and self.mixed is not None: # mixed tournament 

1228 gs = gs + "Tournament #%s, %s %s (%s) - Level %s (%s) - " % ( 

1229 self.tourNo, 

1230 self.buyin, 

1231 self.MS[self.mixed], 

1232 self.getGameTypeAsString(), 

1233 self.level, 

1234 self.getStakesAsString(), 

1235 ) 

1236 elif self.tourNo is not None: # all other tournaments 

1237 gs = gs + "Tournament #%s, %s %s - Level %s (%s) - " % ( 

1238 self.tourNo, 

1239 self.buyin, 

1240 self.getGameTypeAsString(), 

1241 self.level, 

1242 self.getStakesAsString(), 

1243 ) 

1244 elif self.mixed is not None: # all other mixed games 

1245 gs = gs + " %s (%s, %s) - " % (self.MS[self.mixed], self.getGameTypeAsString(), self.getStakesAsString()) 

1246 else: # non-mixed cash games 

1247 gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString()) 

1248 

1249 try: 

1250 timestr = datetime.datetime.strftime(self.startTime, "%Y/%m/%d %H:%M:%S ET") 

1251 except TypeError: 

1252 print( 

1253 ( 

1254 "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:" 

1255 ), 

1256 self.startTime, 

1257 ) 

1258 print(("*** Make sure your HandHistoryConverter is setting hand.startTime properly!")) 

1259 print(("*** Game String:"), gs) 

1260 return gs 

1261 else: 

1262 return gs + timestr 

1263 

1264 def writeTableLine(self): 

1265 table_string = "Table " 

1266 if self.gametype["type"] == "tour": 

1267 table_string = table_string + "'%s %s' %s-max" % (self.tourNo, self.tablename, self.maxseats) 

1268 else: 

1269 table_string = table_string + "'%s' %s-max" % (self.tablename, self.maxseats) 

1270 if self.gametype["currency"] == "play": 

1271 table_string = table_string + " (Play Money)" 

1272 if self.buttonpos is not None and self.buttonpos != 0: 

1273 table_string = table_string + " Seat #%s is the button" % self.buttonpos 

1274 return table_string 

1275 

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

1277 # PokerStars format. 

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

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

1280 

1281 

1282class HoldemOmahaHand(Hand): 

1283 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom="HHC", handid=None): 

1284 self.config = config 

1285 if gametype["base"] != "hold": 

1286 pass # or indeed don't pass and complain instead 

1287 log.debug("HoldemOmahaHand") 

1288 self.allStreets = ["BLINDSANTES", "PREFLOP", "FLOP", "TURN", "RIVER"] 

1289 if gametype["category"] == "fusion": 

1290 self.holeStreets = ["PREFLOP", "FLOP", "TURN"] 

1291 else: 

1292 self.holeStreets = ["PREFLOP"] 

1293 if gametype["category"] == "irish": 

1294 self.discardStreets = ["TURN"] 

1295 else: 

1296 self.discardStreets = ["PREFLOP"] 

1297 self.communityStreets = ["FLOP", "TURN", "RIVER"] 

1298 self.actionStreets = ["BLINDSANTES", "PREFLOP", "FLOP", "TURN", "RIVER"] 

1299 if gametype["category"] == "aof_omaha": 

1300 self.allStreets = ["BLINDSANTES", "FLOP", "TURN", "RIVER"] 

1301 self.holeStreets = ["FLOP"] 

1302 self.communityStreets = ["FLOP", "TURN", "RIVER"] 

1303 self.actionStreets = ["BLINDSANTES", "FLOP", "TURN", "RIVER"] 

1304 Hand.__init__(self, self.config, sitename, gametype, handText, builtFrom="HHC") 

1305 self.sb = gametype["sb"] 

1306 self.bb = gametype["bb"] 

1307 if hasattr(hhc, "in_path"): 

1308 self.in_path = hhc.in_path 

1309 else: 

1310 self.in_path = "database" 

1311 

1312 # Populate a HoldemOmahaHand 

1313 # Generally, we call 'read' methods here, which get the info according to the particular filter (hhc) 

1314 # which then invokes a 'addXXX' callback 

1315 if builtFrom == "HHC": 

1316 hhc.readHandInfo(self) 

1317 if self.gametype["type"] == "tour": 

1318 self.tablename = "%s %s" % (self.tourNo, self.tablename) 

1319 hhc.readPlayerStacks(self) 

1320 hhc.compilePlayerRegexs(self) 

1321 hhc.markStreets(self) 

1322 

1323 if self.cancelled: 

1324 return 

1325 

1326 hhc.readBlinds(self) 

1327 

1328 hhc.readSTP(self) 

1329 hhc.readAntes(self) 

1330 hhc.readButton(self) 

1331 hhc.readHoleCards(self) 

1332 hhc.readShowdownActions(self) 

1333 # Read actions in street order 

1334 for street, text in list(self.streets.items()): 

1335 if ( 

1336 text and (street != "PREFLOP") 

1337 ): # 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 

1338 hhc.readCommunityCards(self, street) 

1339 for street in self.actionStreets: 

1340 if self.streets[street] or gametype["split"]: 

1341 hhc.readAction(self, street) 

1342 self.pot.markTotal(street) 

1343 hhc.readCollectPot(self) 

1344 hhc.readShownCards(self) 

1345 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot 

1346 self.totalPot() # finalise it (total the pot) 

1347 print("self.totalpot", self.totalpot) 

1348 hhc.getRake(self) 

1349 if self.maxseats is None: 

1350 self.maxseats = hhc.guessMaxSeats(self) 

1351 self.sittingOut() 

1352 hhc.readTourneyResults(self) 

1353 # readOther is deprecated 

1354 # hhc.readOther(self) 

1355 elif builtFrom == "DB": 

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

1357 log.debug( 

1358 "HoldemOmahaHand.__init__: " + ("DEBUG:") + " " + ("HoldemOmaha hand initialised for %s"), "select()" 

1359 ) 

1360 self.maxseats = 10 

1361 else: 

1362 log.warning("HoldemOmahaHand.__init__: " + ("Neither HHC nor DB+handID provided")) 

1363 

1364 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None): 

1365 if player == self.hero: # we have hero's cards just update shown/mucked 

1366 if shown: 

1367 self.shown.add(player) 

1368 if mucked: 

1369 self.mucked.add(player) 

1370 else: 

1371 if self.gametype["category"] == "aof_omaha": 

1372 self.addHoleCards("FLOP", player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt) 

1373 elif len(cards) in (2, 3, 4, 6) or self.gametype["category"] in ( 

1374 "5_omahahi", 

1375 "5_omaha8", 

1376 "cour_hi", 

1377 "cour_hilo", 

1378 "fusion", 

1379 ): # avoid adding board by mistake (Everleaf problem) 

1380 self.addHoleCards("PREFLOP", player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt) 

1381 

1382 elif len(cards) == 5: # cards holds a winning hand, not hole cards 

1383 # filter( lambda x: x not in b, a ) # calcs a - b where a and b are lists 

1384 # so diff is set to the winning hand minus the board cards, if we're lucky that leaves the hole cards 

1385 diff = [x for x in cards if x not in self.board["FLOP"] + self.board["TURN"] + self.board["RIVER"]] 

1386 if len(diff) == 2 and self.gametype["category"] in ("holdem"): 

1387 self.addHoleCards("PREFLOP", player, open=[], closed=diff, shown=shown, mucked=mucked, dealt=dealt) 

1388 if string is not None: 

1389 self.showdownStrings[player] = string 

1390 

1391 def join_holecards(self, player, asList=False): 

1392 holeNo = Card.games[self.gametype["category"]][5][0][1] 

1393 hcs = ["0x"] * holeNo 

1394 if self.gametype["category"] == "fusion": 

1395 for street in self.holeStreets: 

1396 if player in list(self.holecards[street].keys()): 

1397 if street == "PREFLOP": 

1398 if len(self.holecards[street][player][1]) == 1: 

1399 continue 

1400 for i in 0, 1: 

1401 hcs[i] = self.holecards[street][player][1][i] 

1402 hcs[i] = hcs[i][0:1].upper() + hcs[i][1:2] 

1403 try: 

1404 for i in range(2, holeNo): 

1405 hcs[i] = self.holecards[street][player][1][i] 

1406 hcs[i] = hcs[i][0:1].upper() + hcs[i][1:2] 

1407 except IndexError: 

1408 log.debug("Why did we get an indexerror?") 

1409 elif street == "FLOP": 

1410 if len(self.holecards[street][player][1]) == 1: 

1411 continue 

1412 hcs[2] = self.holecards[street][player][0][0] 

1413 hcs[2] = hcs[2][0:1].upper() + hcs[2][1:2] 

1414 elif street == "TURN": 

1415 if len(self.holecards[street][player][1]) == 1: 

1416 continue 

1417 hcs[3] = self.holecards[street][player][0][0] 

1418 hcs[3] = hcs[3][0:1].upper() + hcs[3][1:2] 

1419 

1420 else: 

1421 for street in self.holeStreets: 

1422 if player in list(self.holecards[street].keys()): 

1423 if len(self.holecards[street][player][1]) == 1: 

1424 continue 

1425 for i in 0, 1: 

1426 hcs[i] = self.holecards[street][player][1][i] 

1427 hcs[i] = hcs[i][0:1].upper() + hcs[i][1:2] 

1428 try: 

1429 for i in range(2, holeNo): 

1430 hcs[i] = self.holecards[street][player][1][i] 

1431 hcs[i] = hcs[i][0:1].upper() + hcs[i][1:2] 

1432 except IndexError: 

1433 log.debug("Why did we get an indexerror?") 

1434 

1435 if asList: 

1436 return hcs 

1437 else: 

1438 return " ".join(hcs) 

1439 

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

1441 # PokerStars format. 

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

1443 

1444 players_who_act_preflop = set( 

1445 ([x[0] for x in self.actions["PREFLOP"]] + [x[0] for x in self.actions["BLINDSANTES"]]) 

1446 ) 

1447 log.debug(self.actions["PREFLOP"]) 

1448 for player in [x for x in self.players if x[1] in players_who_act_preflop]: 

1449 # Only print stacks of players who do something preflop 

1450 print(("Seat %s: %s ($%.2f in chips) " % (player[0], player[1], float(player[2]))), file=fh) 

1451 

1452 if self.actions["BLINDSANTES"]: 

1453 log.debug(self.actions["BLINDSANTES"]) 

1454 for act in self.actions["BLINDSANTES"]: 

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

1456 

1457 print(("*** HOLE CARDS ***"), file=fh) 

1458 for player in self.dealt: 

1459 print(("Dealt to %s [%s]" % (player, " ".join(self.holecards["PREFLOP"][player][1]))), file=fh) 

1460 if self.hero == "": 

1461 for player in self.shown.difference(self.dealt): 

1462 print(("Dealt to %s [%s]" % (player, " ".join(self.holecards["PREFLOP"][player][1]))), file=fh) 

1463 

1464 if self.actions["PREFLOP"]: 

1465 for act in self.actions["PREFLOP"]: 

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

1467 

1468 if self.board["FLOP"]: 

1469 print(("*** FLOP *** [%s]" % (" ".join(self.board["FLOP"]))), file=fh) 

1470 if self.actions["FLOP"]: 

1471 for act in self.actions["FLOP"]: 

1472 print(("*** FLOP *** [%s]" % (" ".join(self.board["FLOP"]))), file=fh) 

1473 

1474 if self.board["TURN"]: 

1475 print(("*** TURN *** [%s] [%s]" % (" ".join(self.board["FLOP"]), " ".join(self.board["TURN"]))), file=fh) 

1476 if self.actions["TURN"]: 

1477 for act in self.actions["TURN"]: 

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

1479 

1480 if self.board["RIVER"]: 

1481 print( 

1482 ( 

1483 "*** RIVER *** [%s] [%s]" 

1484 % (" ".join(self.board["FLOP"] + self.board["TURN"]), " ".join(self.board["RIVER"])) 

1485 ), 

1486 file=fh, 

1487 ) 

1488 if self.actions["RIVER"]: 

1489 for act in self.actions["RIVER"]: 

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

1491 

1492 # Some sites don't have a showdown section so we have to figure out if there should be one 

1493 # The logic for a showdown is: at the end of river action there are at least two players in the hand 

1494 # we probably don't need a showdown section in pseudo stars format for our filtering purposes 

1495 if self.shown: 

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

1497 for name in self.shown: 

1498 # TODO: legacy importer can't handle only one holecard here, make sure there are 2 for holdem, 4 for omaha 

1499 # TOOD: If HoldHand subclass supports more than omahahi, omahahilo, holdem, add them here 

1500 numOfHoleCardsNeeded = None 

1501 if self.gametype["category"] in ("omahahi", "omahahilo"): 

1502 numOfHoleCardsNeeded = 4 

1503 elif self.gametype["category"] in ("holdem"): 

1504 numOfHoleCardsNeeded = 2 

1505 if len(self.holecards["PREFLOP"][name]) == numOfHoleCardsNeeded: 

1506 print(("%s shows [%s] (a hand...)" % (name, " ".join(self.holecards["PREFLOP"][name][1]))), file=fh) 

1507 

1508 # Current PS format has the lines: 

1509 # Uncalled bet ($111.25) returned to s0rrow 

1510 # s0rrow collected $5.15 from side pot 

1511 # stervels: shows [Ks Qs] (two pair, Kings and Queens) 

1512 # stervels collected $45.35 from main pot 

1513 # Immediately before the summary. 

1514 # The current importer uses those lines for importing winning rather than the summary 

1515 for name in self.pot.returned: 

1516 print(("Uncalled bet (%s%s) returned to %s" % (self.sym, self.pot.returned[name], name)), file=fh) 

1517 for entry in self.collected: 

1518 print(("%s collected %s%s from pot" % (entry[0], self.sym, entry[1])), file=fh) 

1519 

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

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

1522 

1523 board = [] 

1524 for street in ["FLOP", "TURN", "RIVER"]: 

1525 board += self.board[street] 

1526 if board: # sometimes hand ends preflop without a board 

1527 print(("Board [%s]" % (" ".join(board))), file=fh) 

1528 

1529 for player in [x for x in self.players if x[1] in players_who_act_preflop]: 

1530 seatnum = player[0] 

1531 name = player[1] 

1532 if name in self.collectees and name in self.shown: 

1533 print( 

1534 ( 

1535 "Seat %d: %s showed [%s] and won (%s%s)" 

1536 % (seatnum, name, " ".join(self.holecards["PREFLOP"][name][1]), self.sym, self.collectees[name]) 

1537 ), 

1538 file=fh, 

1539 ) 

1540 elif name in self.collectees: 

1541 print(("Seat %d: %s collected (%s%s)" % (seatnum, name, self.sym, self.collectees[name])), file=fh) 

1542 elif name in self.folded: 

1543 print(("Seat %d: %s folded" % (seatnum, name)), file=fh) 

1544 else: 

1545 if name in self.shown: 

1546 print( 

1547 ( 

1548 "Seat %d: %s showed [%s] and lost with..." 

1549 % (seatnum, name, " ".join(self.holecards["PREFLOP"][name][1])) 

1550 ), 

1551 file=fh, 

1552 ) 

1553 elif name in self.mucked: 

1554 print( 

1555 ("Seat %d: %s mucked [%s] " % (seatnum, name, " ".join(self.holecards["PREFLOP"][name][1]))), 

1556 file=fh, 

1557 ) 

1558 else: 

1559 print(("Seat %d: %s mucked" % (seatnum, name)), file=fh) 

1560 

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

1562 

1563 

1564class DrawHand(Hand): 

1565 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom="HHC", handid=None): 

1566 self.config = config 

1567 if gametype["base"] != "draw": 

1568 pass # or indeed don't pass and complain instead 

1569 self.streetList = ["BLINDSANTES", "DEAL", "DRAWONE"] 

1570 self.allStreets = ["BLINDSANTES", "DEAL", "DRAWONE"] 

1571 self.holeStreets = ["DEAL", "DRAWONE"] 

1572 self.actionStreets = ["BLINDSANTES", "DEAL", "DRAWONE"] 

1573 if gametype["category"] in ["27_3draw", "badugi", "a5_3draw", "badacey", "badeucey", "drawmaha"]: 

1574 self.streetList += ["DRAWTWO", "DRAWTHREE"] 

1575 self.allStreets += ["DRAWTWO", "DRAWTHREE"] 

1576 self.holeStreets += ["DRAWTWO", "DRAWTHREE"] 

1577 self.actionStreets += ["DRAWTWO", "DRAWTHREE"] 

1578 self.discardStreets = self.holeStreets 

1579 self.communityStreets = [] 

1580 Hand.__init__(self, self.config, sitename, gametype, handText) 

1581 self.sb = gametype["sb"] 

1582 self.bb = gametype["bb"] 

1583 if hasattr(hhc, "in_path"): 

1584 self.in_path = hhc.in_path 

1585 else: 

1586 self.in_path = "database" 

1587 # Populate the draw hand. 

1588 if builtFrom == "HHC": 

1589 hhc.readHandInfo(self) 

1590 if self.gametype["type"] == "tour": 

1591 self.tablename = "%s %s" % (self.tourNo, self.tablename) 

1592 hhc.readPlayerStacks(self) 

1593 hhc.compilePlayerRegexs(self) 

1594 hhc.markStreets(self) 

1595 # markStreets in Draw may match without dealing cards 

1596 if self.streets["DEAL"] is None: 

1597 log.error("DrawHand.__init__: " + ("Street 'DEAL' is empty. Was hand '%s' cancelled?") % self.handid) 

1598 raise FpdbParseError 

1599 hhc.readBlinds(self) 

1600 hhc.readSTP(self) 

1601 hhc.readAntes(self) 

1602 hhc.readButton(self) 

1603 hhc.readHoleCards(self) 

1604 hhc.readShowdownActions(self) 

1605 # Read actions in street order 

1606 for street in self.streetList: 

1607 if self.streets[street]: 

1608 hhc.readAction(self, street) 

1609 self.pot.markTotal(street) 

1610 hhc.readCollectPot(self) 

1611 hhc.readShownCards(self) 

1612 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot 

1613 self.totalPot() # finalise it (total the pot) 

1614 hhc.getRake(self) 

1615 if self.maxseats is None: 

1616 self.maxseats = hhc.guessMaxSeats(self) 

1617 self.sittingOut() 

1618 hhc.readTourneyResults(self) 

1619 hhc.readOther(self) 

1620 

1621 elif builtFrom == "DB": 

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

1623 self.maxseats = 10 

1624 

1625 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None): 

1626 # if player == self.hero: # we have hero's cards just update shown/mucked 

1627 # if shown: self.shown.add(player) 

1628 # if mucked: self.mucked.add(player) 

1629 # else: 

1630 # pass 

1631 # TODO: Probably better to find the last street with action and add the hole cards to that street 

1632 self.addHoleCards( 

1633 self.actionStreets[-1], player, open=[], closed=cards, shown=shown, mucked=mucked, dealt=dealt 

1634 ) 

1635 if string is not None: 

1636 self.showdownStrings[player] = string 

1637 

1638 def holecardsAsSet(self, street, player): 

1639 """Return holdcards: (oc, nc) as set()""" 

1640 (nc, oc) = self.holecards[street][player] 

1641 nc = set(nc) 

1642 oc = set(oc) 

1643 return (nc, oc) 

1644 

1645 def join_holecards(self, player, asList=False, street=False): 

1646 """With asList = True it returns the set cards for a player including down cards if they aren't know""" 

1647 handsize = Card.games[self.gametype["category"]][5][0][1] 

1648 holecards = ["0x"] * 20 

1649 

1650 for i, _street in enumerate(self.holeStreets): 

1651 if player in list(self.holecards[_street].keys()): 

1652 allhole = self.holecards[_street][player][1] + self.holecards[_street][player][0] 

1653 allhole = allhole[:handsize] 

1654 for c in range(len(allhole)): 

1655 idx = c + i * 5 

1656 holecards[idx] = allhole[c] 

1657 

1658 result = [] 

1659 if street is False: 

1660 result = holecards 

1661 elif street in self.holeStreets: 

1662 if street == "DEAL": 

1663 result = holecards[0:5] 

1664 elif street == "DRAWONE": 

1665 result = holecards[5:10] 

1666 elif street == "DRAWTWO": 

1667 result = holecards[10:15] 

1668 elif street == "DRAWTHREE": 

1669 result = holecards[15:20] 

1670 return result if asList else " ".join(result) 

1671 

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

1673 # PokerStars format. 

1674 # HH output should not be translated 

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

1676 

1677 players_who_act_ondeal = set( 

1678 ([x[0] for x in self.actions["DEAL"]] + [x[0] for x in self.actions["BLINDSANTES"]]) 

1679 ) 

1680 

1681 for player in [x for x in self.players if x[1] in players_who_act_ondeal]: 

1682 # Only print stacks of players who do something on deal 

1683 print((("Seat %s: %s (%s%s in chips) ") % (player[0], player[1], self.sym, player[2])), file=fh) 

1684 

1685 if "BLINDSANTES" in self.actions: 

1686 for act in self.actions["BLINDSANTES"]: 

1687 print(("%s: %s %s %s%s" % (act[0], act[1], act[2], self.sym, act[3])), file=fh) 

1688 

1689 if "DEAL" in self.actions: 

1690 print(("*** DEALING HANDS ***"), file=fh) 

1691 for player in [x[1] for x in self.players if x[1] in players_who_act_ondeal]: 

1692 if "DEAL" in self.holecards: 

1693 if player in self.holecards["DEAL"]: 

1694 (nc, oc) = self.holecards["DEAL"][player] 

1695 print(("Dealt to %s: [%s]") % (player, " ".join(nc)), file=fh) 

1696 for act in self.actions["DEAL"]: 

1697 print(self.actionString(act, "DEAL"), file=fh) 

1698 

1699 if "DRAWONE" in self.actions: 

1700 print(("*** FIRST DRAW ***"), file=fh) 

1701 for act in self.actions["DRAWONE"]: 

1702 print(self.actionString(act, "DRAWONE"), file=fh) 

1703 if act[0] == self.hero and act[1] == "discards": 

1704 (nc, oc) = self.holecardsAsSet("DRAWONE", act[0]) 

1705 dc = self.discards["DRAWONE"][act[0]] 

1706 kc = oc - dc 

1707 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh) 

1708 

1709 if "DRAWTWO" in self.actions: 

1710 print(("*** SECOND DRAW ***"), file=fh) 

1711 for act in self.actions["DRAWTWO"]: 

1712 print(self.actionString(act, "DRAWTWO"), file=fh) 

1713 if act[0] == self.hero and act[1] == "discards": 

1714 (nc, oc) = self.holecardsAsSet("DRAWONE", act[0]) 

1715 dc = self.discards["DRAWTWO"][act[0]] 

1716 kc = oc - dc 

1717 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh) 

1718 

1719 if "DRAWTHREE" in self.actions: 

1720 print(("*** THIRD DRAW ***"), file=fh) 

1721 for act in self.actions["DRAWTHREE"]: 

1722 print(self.actionString(act, "DRAWTHREE"), file=fh) 

1723 if act[0] == self.hero and act[1] == "discards": 

1724 (nc, oc) = self.holecardsAsSet("DRAWONE", act[0]) 

1725 dc = self.discards["DRAWTHREE"][act[0]] 

1726 kc = oc - dc 

1727 print((("Dealt to %s [%s] [%s]") % (act[0], " ".join(kc), " ".join(nc))), file=fh) 

1728 

1729 if "SHOWDOWN" in self.actions: 

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

1731 # TODO: Complete SHOWDOWN 

1732 

1733 # Current PS format has the lines: 

1734 # Uncalled bet ($111.25) returned to s0rrow 

1735 # s0rrow collected $5.15 from side pot 

1736 # stervels: shows [Ks Qs] (two pair, Kings and Queens) 

1737 # stervels collected $45.35 from main pot 

1738 # Immediately before the summary. 

1739 # The current importer uses those lines for importing winning rather than the summary 

1740 for name in self.pot.returned: 

1741 print(("Uncalled bet (%s%s) returned to %s" % (self.sym, self.pot.returned[name], name)), file=fh) 

1742 for entry in self.collected: 

1743 print(("%s collected %s%s from pot" % (entry[0], self.sym, entry[1])), file=fh) 

1744 

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

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

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

1748 

1749 

1750class StudHand(Hand): 

1751 def __init__(self, config, hhc, sitename, gametype, handText, builtFrom="HHC", handid=None): 

1752 self.config = config 

1753 if gametype["base"] != "stud": 

1754 pass # or indeed don't pass and complain instead 

1755 

1756 self.communityStreets = [] 

1757 if gametype["category"] == "5_studhi": 

1758 self.allStreets = ["BLINDSANTES", "SECOND", "THIRD", "FOURTH", "FIFTH"] 

1759 self.actionStreets = ["BLINDSANTES", "SECOND", "THIRD", "FOURTH", "FIFTH"] 

1760 self.streetList = [ 

1761 "BLINDSANTES", 

1762 "SECOND", 

1763 "THIRD", 

1764 "FOURTH", 

1765 "FIFTH", 

1766 ] # a list of the observed street names in order 

1767 self.holeStreets = ["SECOND", "THIRD", "FOURTH", "FIFTH"] 

1768 else: 

1769 self.allStreets = ["BLINDSANTES", "THIRD", "FOURTH", "FIFTH", "SIXTH", "SEVENTH"] 

1770 self.actionStreets = ["BLINDSANTES", "THIRD", "FOURTH", "FIFTH", "SIXTH", "SEVENTH"] 

1771 self.streetList = [ 

1772 "BLINDSANTES", 

1773 "THIRD", 

1774 "FOURTH", 

1775 "FIFTH", 

1776 "SIXTH", 

1777 "SEVENTH", 

1778 ] # a list of the observed street names in order 

1779 self.holeStreets = ["THIRD", "FOURTH", "FIFTH", "SIXTH", "SEVENTH"] 

1780 self.discardStreets = self.holeStreets 

1781 Hand.__init__(self, self.config, sitename, gametype, handText) 

1782 self.sb = gametype["sb"] 

1783 self.bb = gametype["bb"] 

1784 if hasattr(hhc, "in_path"): 

1785 self.in_path = hhc.in_path 

1786 else: 

1787 self.in_path = "database" 

1788 # Populate the StudHand 

1789 # Generally, we call a 'read' method here, which gets the info according to the particular filter (hhc) 

1790 # which then invokes a 'addXXX' callback 

1791 if builtFrom == "HHC": 

1792 hhc.readHandInfo(self) 

1793 if self.gametype["type"] == "tour": 

1794 self.tablename = "%s %s" % (self.tourNo, self.tablename) 

1795 hhc.readPlayerStacks(self) 

1796 hhc.compilePlayerRegexs(self) 

1797 hhc.markStreets(self) 

1798 hhc.readSTP(self) 

1799 hhc.readAntes(self) 

1800 hhc.readBringIn(self) 

1801 hhc.readHoleCards(self) 

1802 hhc.readShowdownActions(self) 

1803 # Read actions in street order 

1804 for street in self.actionStreets: 

1805 if street == "BLINDSANTES": 

1806 continue # OMG--sometime someone folds in the ante round 

1807 if self.streets[street]: 

1808 log.debug(street + self.streets[street]) 

1809 hhc.readAction(self, street) 

1810 self.pot.markTotal(street) 

1811 hhc.readCollectPot(self) 

1812 hhc.readShownCards(self) # not done yet 

1813 self.pot.handid = self.handid # This is only required so Pot can throw it in totalPot 

1814 self.totalPot() # finalise it (total the pot) 

1815 hhc.getRake(self) 

1816 if self.maxseats is None: 

1817 self.maxseats = hhc.guessMaxSeats(self) 

1818 self.sittingOut() 

1819 hhc.readTourneyResults(self) 

1820 hhc.readOther(self) 

1821 

1822 elif builtFrom == "DB": 

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

1824 self.maxseats = 10 

1825 

1826 def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False, string=None): 

1827 if player == self.hero: # we have hero's cards just update shown/mucked 

1828 if shown: 

1829 self.shown.add(player) 

1830 if mucked: 

1831 self.mucked.add(player) 

1832 else: 

1833 if self.gametype["category"] == "5_studhi" and len(cards) > 4: 

1834 self.addHoleCards("SECOND", player, open=[cards[1]], closed=[cards[0]], shown=shown, mucked=mucked) 

1835 self.addHoleCards("THIRD", player, open=[cards[2]], closed=[cards[1]], shown=shown, mucked=mucked) 

1836 self.addHoleCards("FOURTH", player, open=[cards[3]], closed=cards[1:2], shown=shown, mucked=mucked) 

1837 self.addHoleCards("FIFTH", player, open=[cards[4]], closed=cards[1:3], shown=shown, mucked=mucked) 

1838 if len(cards) > 6: 

1839 self.addHoleCards("THIRD", player, open=[cards[2]], closed=cards[0:2], shown=shown, mucked=mucked) 

1840 self.addHoleCards("FOURTH", player, open=[cards[3]], closed=[cards[2]], shown=shown, mucked=mucked) 

1841 self.addHoleCards("FIFTH", player, open=[cards[4]], closed=cards[2:4], shown=shown, mucked=mucked) 

1842 self.addHoleCards("SIXTH", player, open=[cards[5]], closed=cards[2:5], shown=shown, mucked=mucked) 

1843 self.addHoleCards("SEVENTH", player, open=[], closed=[cards[6]], shown=shown, mucked=mucked) 

1844 if string is not None: 

1845 self.showdownStrings[player] = string 

1846 

1847 def addPlayerCards(self, player, street, open=[], closed=[]): 

1848 """ 

1849 Assigns observed cards to a player. 

1850 player (string) name of player 

1851 street (string) the street name (in streetList) 

1852 open list of card bigrams e.g. ['2h','Jc'], dealt face up 

1853 closed likewise, but known only to player 

1854 """ 

1855 log.debug("addPlayerCards %s, o%s x%s", player, open, closed) 

1856 self.checkPlayerExists(player, "addPlayerCards") 

1857 self.holecards[street][player] = (open, closed) 

1858 

1859 # TODO: def addComplete(self, player, amount): 

1860 def addComplete(self, street, player, amountTo): 

1861 # assert street=='THIRD' 

1862 # This needs to be called instead of addRaiseTo, and it needs to take account of self.lastBet['THIRD'] to determine the raise-by size 

1863 """\ 

1864 Add a complete on [street] by [player] to [amountTo] 

1865 """ 

1866 log.debug(("%s %s completes %s"), street, player, amountTo) 

1867 amountTo = amountTo.replace(",", "") # some sites have commas 

1868 self.checkPlayerExists(player, "addComplete") 

1869 Bp = self.lastBet[street] 

1870 Bc = sum(self.bets[street][player]) 

1871 Rt = Decimal(amountTo) 

1872 C = Bp - Bc 

1873 Rb = Rt - C 

1874 self._addRaise(street, player, C, Rb, Rt, "completes") 

1875 

1876 def addBringIn(self, player, bringin): 

1877 if player is not None: 

1878 if self.gametype["category"] == "5_studhi": 

1879 street = "SECOND" 

1880 else: 

1881 street = "THIRD" 

1882 log.debug(("Bringin: %s, %s"), player, bringin) 

1883 bringin = bringin.replace(",", "") # some sites have commas 

1884 self.checkPlayerExists(player, "addBringIn") 

1885 bringin = Decimal(bringin) 

1886 self.bets[street][player].append(bringin) 

1887 self.stacks[player] -= bringin 

1888 act = (player, "bringin", bringin, self.stacks[player] == 0) 

1889 self.actions[street].append(act) 

1890 self.lastBet[street] = bringin 

1891 self.pot.addMoney(player, bringin) 

1892 

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

1894 # PokerStars format. 

1895 # HH output should not be translated 

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

1897 

1898 players_who_post_antes = set([x[0] for x in self.actions["BLINDSANTES"]]) 

1899 

1900 for player in [x for x in self.players if x[1] in players_who_post_antes]: 

1901 # Only print stacks of players who do something preflop 

1902 print(("Seat %s: %s (%s%s in chips)" % (player[0], player[1], self.sym, player[2])), file=fh) 

1903 

1904 if "BLINDSANTES" in self.actions: 

1905 for act in self.actions["BLINDSANTES"]: 

1906 print(("%s: posts the ante %s%s" % (act[0], self.sym, act[3])), file=fh) 

1907 

1908 if "THIRD" in self.actions: 

1909 dealt = 0 

1910 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]: 

1911 if player in self.holecards["THIRD"]: 

1912 dealt += 1 

1913 if dealt == 1: 

1914 print(("*** 3RD STREET ***"), file=fh) 

1915 print(self.writeHoleCards("THIRD", player), file=fh) 

1916 for act in self.actions["THIRD"]: 

1917 # FIXME: Need some logic here for bringin vs completes 

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

1919 

1920 if "FOURTH" in self.actions: 

1921 dealt = 0 

1922 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]: 

1923 if player in self.holecards["FOURTH"]: 

1924 dealt += 1 

1925 if dealt == 1: 

1926 print(("*** 4TH STREET ***"), file=fh) 

1927 print(self.writeHoleCards("FOURTH", player), file=fh) 

1928 for act in self.actions["FOURTH"]: 

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

1930 

1931 if "FIFTH" in self.actions: 

1932 dealt = 0 

1933 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]: 

1934 if player in self.holecards["FIFTH"]: 

1935 dealt += 1 

1936 if dealt == 1: 

1937 print(("*** 5TH STREET ***"), file=fh) 

1938 print(self.writeHoleCards("FIFTH", player), file=fh) 

1939 for act in self.actions["FIFTH"]: 

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

1941 

1942 if "SIXTH" in self.actions: 

1943 dealt = 0 

1944 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]: 

1945 if player in self.holecards["SIXTH"]: 

1946 dealt += 1 

1947 if dealt == 1: 

1948 print(("*** 6TH STREET ***"), file=fh) 

1949 print(self.writeHoleCards("SIXTH", player), file=fh) 

1950 for act in self.actions["SIXTH"]: 

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

1952 

1953 if "SEVENTH" in self.actions: 

1954 # OK. It's possible that they're all in at an earlier street, but only closed cards are dealt. 

1955 # Then we have no 'dealt to' lines, no action lines, but still 7th street should appear. 

1956 # The only way I can see to know whether to print this line is by knowing the state of the hand 

1957 # i.e. are all but one players folded; is there an allin showdown; and all that. 

1958 print(("*** RIVER ***"), file=fh) 

1959 for player in [x[1] for x in self.players if x[1] in players_who_post_antes]: 

1960 if player in self.holecards["SEVENTH"]: 

1961 if self.writeHoleCards("SEVENTH", player): 

1962 print(self.writeHoleCards("SEVENTH", player), file=fh) 

1963 for act in self.actions["SEVENTH"]: 

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

1965 

1966 # Some sites don't have a showdown section so we have to figure out if there should be one 

1967 # The logic for a showdown is: at the end of river action there are at least two players in the hand 

1968 # we probably don't need a showdown section in pseudo stars format for our filtering purposes 

1969 if "SHOWDOWN" in self.actions: 

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

1971 # TODO: print showdown lines. 

1972 

1973 # Current PS format has the lines: 

1974 # Uncalled bet ($111.25) returned to s0rrow 

1975 # s0rrow collected $5.15 from side pot 

1976 # stervels: shows [Ks Qs] (two pair, Kings and Queens) 

1977 # stervels collected $45.35 from main pot 

1978 # Immediately before the summary. 

1979 # The current importer uses those lines for importing winning rather than the summary 

1980 for name in self.pot.returned: 

1981 print(("Uncalled bet (%s%s) returned to %s" % (self.sym, self.pot.returned[name], name)), file=fh) 

1982 for entry in self.collected: 

1983 print(("%s collected %s%s from pot" % (entry[0], self.sym, entry[1])), file=fh) 

1984 

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

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

1987 # TODO: side pots 

1988 

1989 board = [] 

1990 for s in list(self.board.values()): 

1991 board += s 

1992 if board: # sometimes hand ends preflop without a board 

1993 print(("Board [%s]" % (" ".join(board))), file=fh) 

1994 

1995 for player in [x for x in self.players if x[1] in players_who_post_antes]: 

1996 seatnum = player[0] 

1997 name = player[1] 

1998 if name in self.collectees and name in self.shown: 

1999 print( 

2000 ( 

2001 "Seat %d: %s showed [%s] and won (%s%s)" 

2002 % (seatnum, name, self.join_holecards(name), self.sym, self.collectees[name]) 

2003 ), 

2004 file=fh, 

2005 ) 

2006 elif name in self.collectees: 

2007 print(("Seat %d: %s collected (%s%s)" % (seatnum, name, self.sym, self.collectees[name])), file=fh) 

2008 elif name in self.shown: 

2009 print(("Seat %d: %s showed [%s]" % (seatnum, name, self.join_holecards(name))), file=fh) 

2010 elif name in self.mucked: 

2011 print(("Seat %d: %s mucked [%s]" % (seatnum, name, self.join_holecards(name))), file=fh) 

2012 elif name in self.folded: 

2013 print(("Seat %d: %s folded" % (seatnum, name)), file=fh) 

2014 else: 

2015 print(("Seat %d: %s mucked" % (seatnum, name)), file=fh) 

2016 

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

2018 

2019 def writeHoleCards(self, street, player): 

2020 hc = "Dealt to %s [" % player 

2021 if street == "THIRD": 

2022 if player == self.hero: 

2023 return ( 

2024 hc 

2025 + " ".join(self.holecards[street][player][1]) 

2026 + " " 

2027 + " ".join(self.holecards[street][player][0]) 

2028 + "]" 

2029 ) 

2030 else: 

2031 return hc + " ".join(self.holecards[street][player][0]) + "]" 

2032 

2033 if street == "SEVENTH" and player != self.hero: 

2034 return # only write 7th st line for hero, LDO 

2035 return ( 

2036 hc + " ".join(self.holecards[street][player][1]) + "] [" + " ".join(self.holecards[street][player][0]) + "]" 

2037 ) 

2038 

2039 def join_holecards(self, player, asList=False): 

2040 """Function returns a string for the stud writeHand method by default 

2041 With asList = True it returns the set cards for a player including down cards if they aren't know""" 

2042 holecards = [] 

2043 for street in self.holeStreets: 

2044 if player in self.holecards[street]: 

2045 if (self.gametype["category"] == "5_studhi" and street == "SECOND") or ( 

2046 self.gametype["category"] != "5_studhi" and street == "THIRD" 

2047 ): 

2048 holecards = holecards + self.holecards[street][player][1] + self.holecards[street][player][0] 

2049 elif street == "SEVENTH": 

2050 if player == self.hero: 

2051 holecards = holecards + self.holecards[street][player][0] 

2052 else: 

2053 holecards = holecards + self.holecards[street][player][1] 

2054 else: 

2055 holecards = holecards + self.holecards[street][player][0] 

2056 

2057 if asList: 

2058 if self.gametype["category"] == "5_studhi": 

2059 if len(holecards) < 2: 

2060 holecards = ["0x"] + holecards 

2061 return holecards 

2062 else: 

2063 if player == self.hero: 

2064 if len(holecards) < 3: 

2065 holecards = ["0x", "0x"] + holecards 

2066 else: 

2067 return holecards 

2068 elif len(holecards) == 7: 

2069 return holecards 

2070 elif len(holecards) <= 4: 

2071 # Non hero folded before showdown, add first two downcards 

2072 holecards = ["0x", "0x"] + holecards 

2073 else: 

2074 log.warning( 

2075 ( 

2076 "join_holecards: # of holecards should be either < 4, 4 or 7 - 5 and 6 should be impossible for anyone who is not a hero" 

2077 ) 

2078 ) 

2079 log.warning("join_holecards: holecards(%s): %s", player, holecards) 

2080 if holecards == ["0x", "0x"]: 

2081 log.warning(("join_holecards: Player '%s' appears not to have been dealt a card"), player) 

2082 # If a player is listed but not dealt a card in a cash game this can occur 

2083 # Noticed in FTP Razz hand. Return 3 empty cards in this case 

2084 holecards = ["0x", "0x", "0x"] 

2085 return holecards 

2086 else: 

2087 return " ".join(holecards) 

2088 

2089 

2090class Pot(object): 

2091 def __init__(self): 

2092 self.contenders = set() 

2093 self.committed = {} 

2094 self.streettotals = {} 

2095 self.common = {} 

2096 self.antes = {} 

2097 self.total = None 

2098 self.returned = {} 

2099 self.sym = "$" # this is the default currency symbol 

2100 self.pots = [] 

2101 self.handid = 0 

2102 self.stp = 0 

2103 

2104 def setSym(self, sym): 

2105 self.sym = sym 

2106 

2107 def addPlayer(self, player): 

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

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

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

2111 

2112 def removePlayer(self, player): 

2113 del self.committed[player] 

2114 del self.common[player] 

2115 del self.antes[player] 

2116 

2117 def addFold(self, player): 

2118 # addFold must be called when a player folds 

2119 self.contenders.discard(player) 

2120 

2121 def addCommonMoney(self, player, amount): 

2122 self.common[player] += amount 

2123 

2124 def addAntes(self, player, amount): 

2125 self.antes[player] += amount 

2126 

2127 def addMoney(self, player, amount): 

2128 # addMoney must be called for any actions that put money in the pot, in the order they occur 

2129 self.contenders.add(player) 

2130 self.committed[player] += amount 

2131 

2132 def removeMoney(self, player, amount): 

2133 self.committed[player] -= amount 

2134 self.returned[player] = amount 

2135 

2136 def setSTP(self, amount): 

2137 self.stp = amount 

2138 

2139 def markTotal(self, street): 

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

2141 

2142 def getTotalAtStreet(self, street): 

2143 if street in self.streettotals: 

2144 return self.streettotals[street] 

2145 return 0 

2146 

2147 def end(self): 

2148 # Initialize 

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

2150 

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

2152 

2153 # Calculating secondary pots 

2154 commitsall = sorted([(v, k) for (k, v) in self.committed.items() if v > 0]) 

2155 try: 

2156 while len(commitsall) > 0: 

2157 # Filter players still in the running 

2158 commitslive = [(v, k) for (v, k) in commitsall if k in self.contenders] 

2159 v1 = commitslive[0][0] 

2160 # Create a new secondary pot 

2161 self.pots += [ 

2162 ( 

2163 sum([min(v, v1) for (v, k) in commitsall]), 

2164 set(k for (v, k) in commitsall if k in self.contenders), 

2165 ) 

2166 ] 

2167 # Updates the remaining bets 

2168 commitsall = [((v - v1), k) for (v, k) in commitsall if v - v1 > 0] 

2169 except IndexError as e: 

2170 log.error("Pot.end(): Major failure while calculating pot: %s", e) 

2171 raise FpdbParseError("Error in pot calculation during side pots handling") 

2172 

2173 # TODO: Gestion du rake (commission) 

2174 # Explication de la gestion du rake 

2175 # total pot x. main pot y, side pot z. | rake r 

2176 # et y+z+r = x 

2177 # Exemple: 

2178 # Total pot $124.30 Main pot $98.90. Side pot $23.40. | Rake $2 

2179 

2180 def __str__(self): 

2181 if self.sym is None: 

2182 self.sym = "C" 

2183 if self.total is None: 

2184 # NB if I'm sure end() is idempotent, call it here. 

2185 log.error(("Error in printing Hand object")) 

2186 raise FpdbParseError 

2187 

2188 ret = "Total pot %s%.2f" % (self.sym, self.total) 

2189 if len(self.pots) < 2: 

2190 return ret 

2191 ret += " Main pot %s%.2f" % (self.sym, self.pots[0][0]) 

2192 return ret + "".join([(" Side pot %s%.2f." % (self.sym, self.pots[x][0])) for x in range(1, len(self.pots))]) 

2193 

2194 

2195def hand_factory(hand_id, config, db_connection): 

2196 # a factory function to discover the base type of the hand 

2197 # and to return a populated class instance of the correct hand 

2198 

2199 log.debug(f"get info from db for hand {hand_id}") 

2200 gameinfo = db_connection.get_gameinfo_from_hid(hand_id) 

2201 

2202 if gameinfo is None: 

2203 log.error(f"No game info found for hand ID {hand_id}") 

2204 return None # Return None or handle the error appropriately 

2205 

2206 log.debug(f"gameinfo {gameinfo} for hand {hand_id}") 

2207 

2208 if gameinfo["base"] == "hold": 

2209 hand_instance = HoldemOmahaHand( 

2210 config=config, 

2211 hhc=None, 

2212 sitename=gameinfo["sitename"], 

2213 gametype=gameinfo, 

2214 handText=None, 

2215 builtFrom="DB", 

2216 handid=hand_id, 

2217 ) 

2218 elif gameinfo["base"] == "stud": 

2219 hand_instance = StudHand( 

2220 config=config, 

2221 hhc=None, 

2222 sitename=gameinfo["sitename"], 

2223 gametype=gameinfo, 

2224 handText=None, 

2225 builtFrom="DB", 

2226 handid=hand_id, 

2227 ) 

2228 elif gameinfo["base"] == "draw": 

2229 hand_instance = DrawHand( 

2230 config=config, 

2231 hhc=None, 

2232 sitename=gameinfo["sitename"], 

2233 gametype=gameinfo, 

2234 handText=None, 

2235 builtFrom="DB", 

2236 handid=hand_id, 

2237 ) 

2238 else: 

2239 log.error(f"Unknown game base type: {gameinfo['base']} for hand {hand_id}") 

2240 return None # Handle unexpected game types 

2241 

2242 log.debug(f"selecting info from db for hand {hand_id}") 

2243 hand_instance.select(db_connection, hand_id) 

2244 hand_instance.handid_selected = hand_id # hand_instance does not supply this, create it here 

2245 log.debug(f"exiting hand_factory for hand {hand_id}") 

2246 

2247 return hand_instance