Coverage for BovadaToFpdb.py: 0%

461 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-14 11:07 +0000

1#!/usr/bin/env python 

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

3# 

4# Copyright 2008-2012, Chaz Littlejohn 

5# 

6# This program is free software; you can redistribute it and/or modify 

7# it under the terms of the GNU General Public License as published by 

8# the Free Software Foundation; either version 2 of the License, or 

9# (at your option) any later version. 

10# 

11# This program is distributed in the hope that it will be useful, 

12# but WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14# GNU General Public License for more details. 

15# 

16# You should have received a copy of the GNU General Public License 

17# along with this program; if not, write to the Free Software 

18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

19######################################################################## 

20 

21# import L10n 

22# _ = L10n.get_translation() 

23 

24# TODO: straighten out discards for draw games 

25 

26from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial 

27from decimal import Decimal 

28import re 

29import logging 

30import datetime 

31 

32# Bovada HH Format 

33log = logging.getLogger("parser") 

34 

35 

36class Bovada(HandHistoryConverter): 

37 # Class Variables 

38 

39 sitename = "Bovada" 

40 filetype = "text" 

41 codepage = ("utf8", "cp1252") 

42 siteId = 21 # Needs to match id entry in Sites database 

43 summaryInFile = True 

44 copyGameHeader = True 

45 sym = {"USD": "\$", "T$": "", "play": ""} # ADD Euro, Sterling, etc HERE 

46 substitutions = { 

47 "LEGAL_ISO": "USD", # legal ISO currency codes 

48 "LS": "\$|", # legal currency symbols - Euro(cp1252, utf-8) 

49 "PLYR": r"(?P<PNAME>.+?)", 

50 "CUR": "(\$|)", 

51 "NUM": ".,\d", 

52 } 

53 

54 # translations from captured groups to fpdb info strings 

55 Lim_Blinds = { 

56 "0.04": ("0.01", "0.02"), 

57 "0.08": ("0.02", "0.04"), 

58 "0.10": ("0.02", "0.05"), 

59 "0.20": ("0.05", "0.10"), 

60 "0.25": ("0.05", "0.10"), 

61 "0.40": ("0.10", "0.20"), 

62 "0.50": ("0.10", "0.25"), 

63 "1.00": ("0.25", "0.50"), 

64 "1": ("0.25", "0.50"), 

65 "2.00": ("0.50", "1.00"), 

66 "2": ("0.50", "1.00"), 

67 "4.00": ("1.00", "2.00"), 

68 "4": ("1.00", "2.00"), 

69 "6.00": ("1.50", "3.00"), 

70 "6": ("1.50", "3.00"), 

71 "8.00": ("2.00", "4.00"), 

72 "8": ("2.00", "4.00"), 

73 "10.00": ("2.50", "5.00"), 

74 "10": ("2.50", "5.00"), 

75 "16.00": ("4.00", "8.00"), 

76 "16": ("4.00", "8.00"), 

77 "20.00": ("5.00", "10.00"), 

78 "20": ("5.00", "10.00"), 

79 "30.00": ("7.50", "15.00"), 

80 "30": ("7.50", "15.00"), 

81 "40.00": ("10.00", "20.00"), 

82 "40": ("10.00", "20.00"), 

83 "60.00": ("15.00", "30.00"), 

84 "60": ("15.00", "30.00"), 

85 "80.00": ("20.00", "40.00"), 

86 "80": ("20.00", "40.00"), 

87 "100.00": ("25.00", "50.00"), 

88 "100": ("25.00", "50.00"), 

89 } 

90 

91 limits = {"No Limit": "nl", "Pot Limit": "pl", "Fixed Limit": "fl", "Turbo": "nl"} 

92 games = { # base, category 

93 "HOLDEM": ("hold", "holdem"), 

94 "OMAHA": ("hold", "omahahi"), 

95 "OMAHA HiLo": ("hold", "omahahilo"), 

96 "OMAHA_HL": ("hold", "omahahilo"), 

97 "7CARD": ("stud", "studhi"), 

98 "7CARD HiLo": ("stud", "studhilo"), 

99 "7CARD_HL": ("hold", "studhilo"), 

100 "HOLDEMZonePoker": ("hold", "holdem"), 

101 "OMAHAZonePoker": ("hold", "omahahi"), 

102 "OMAHA HiLoZonePoker": ("hold", "omahahilo"), 

103 } 

104 currencies = {"$": "USD", "": "T$"} 

105 

106 # Static regexes 

107 re_GameInfo = re.compile( 

108 """ 

109 (Ignition|Bovada|Bodog(\.com|\.eu|\sUK|\sCanada|88)?)\sHand\s\#C?(?P<HID>[0-9]+):?\s+ 

110 ((?P<ZONE>Zone\sPoker\sID|TBL)\#(?P<TABLE>.+?)\s)? 

111 (?P<GAME>HOLDEM|OMAHA|OMAHA_HL|7CARD|7CARD\sHiLo|OMAHA\sHiLo|7CARD_HL|HOLDEMZonePoker|OMAHAZonePoker|OMAHA\sHiLoZonePoker)\s+ 

112 (Tournament\s\#(M\-)? # open paren of tournament info Tournament #2194767 TBL#1,  

113 (?P<TOURNO>\d+)\sTBL\#(?P<TABLENO>\d+), 

114 \s)? 

115 (?P<HU>1\son\s1\s)?  

116 (?P<LIMIT>No\sLimit|Fixed\sLimit|Pot\sLimit|Turbo)? 

117 (\s?Normal\s?)? 

118 (-\sLevel\s\d+?\s 

119 \(? # open paren of the stakes 

120 (?P<CURRENCY>%(LS)s|)? 

121 (?P<SB>[,.0-9]+)/(%(LS)s)? 

122 (?P<BB>[,.0-9]+) 

123 \s?(?P<ISO>%(LEGAL_ISO)s)? 

124 \))? 

125 (\s\[(?P<VERSION>MVS)\])? 

126 \s-\s 

127 (?P<DATETIME>.*$) 

128 """ 

129 % substitutions, 

130 re.MULTILINE | re.VERBOSE, 

131 ) 

132 

133 re_PlayerInfo = re.compile( 

134 """ 

135 ^Seat\s(?P<SEAT>[0-9]+):\s 

136 %(PLYR)s\s(?P<HERO>\[ME\]\s)? 

137 \((%(LS)s)?(?P<CASH>[%(NUM)s]+)\sin\schips\)""" 

138 % substitutions, 

139 re.MULTILINE | re.VERBOSE, 

140 ) 

141 

142 re_PlayerInfoStud = re.compile( 

143 """ 

144 ^(?P<PNAME>Seat\+(?P<SEAT>[0-9]+)) 

145 (?P<HERO>\s\[ME\])?:\s 

146 (%(LS)s)?(?P<CASH>[%(NUM)s]+)\sin\schips""" 

147 % substitutions, 

148 re.MULTILINE | re.VERBOSE, 

149 ) 

150 

151 re_PlayerSeat = re.compile("^Seat\+(?P<SEAT>[0-9]+)", re.MULTILINE | re.VERBOSE) 

152 re_Identify = re.compile("(Ignition|Bovada|Bodog(\.com|\.eu|\sUK|\sCanada|88)?)\sHand") 

153 re_SplitHands = re.compile("\n\n+") 

154 re_TailSplitHands = re.compile("(\n\n\n+)") 

155 re_Button = re.compile("Dealer : Set dealer\/Bring in spot \[(?P<BUTTON>\d+)\]", re.MULTILINE) 

156 re_Board1 = re.compile(r"Board \[(?P<FLOP>\S\S\S? \S\S\S? \S\S\S?)?\s+?(?P<TURN>\S\S\S?)?\s+?(?P<RIVER>\S\S\S?)?\]") 

157 re_Board2 = { 

158 "FLOP": re.compile(r"\*\*\* FLOP \*\*\* \[(?P<CARDS>\S\S \S\S \S\S)\]"), 

159 "TURN": re.compile(r"\*\*\* TURN \*\*\* \[\S\S \S\S \S\S\] \[(?P<CARDS>\S\S)\]"), 

160 "RIVER": re.compile(r"\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S\] \[(?P<CARDS>\S\S)\]"), 

161 } 

162 re_DateTime = re.compile( 

163 """(?P<Y>[0-9]{4})\-(?P<M>[0-9]{2})\-(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)""", 

164 re.MULTILINE, 

165 ) 

166 # These used to be compiled per player, but regression tests say 

167 # we don't have to, and it makes life faster. 

168 re_PostSB = re.compile( 

169 r"^%(PLYR)s (\s?\[ME\]\s)?: (Ante\/Small (B|b)lind|Posts chip|Small (B|b)lind) (?P<CURRENCY>%(CUR)s)(?P<SB>[%(NUM)s]+)" 

170 % substitutions, 

171 re.MULTILINE, 

172 ) 

173 re_PostBB = re.compile( 

174 r"^%(PLYR)s (\s?\[ME\]\s)?: (Big (B|b)lind\/Bring in|Big (B|b)lind) (?P<CURRENCY>%(CUR)s)(?P<BB>[%(NUM)s]+)" 

175 % substitutions, 

176 re.MULTILINE, 

177 ) 

178 re_Antes = re.compile( 

179 r"^%(PLYR)s (\s?\[ME\]\s)?: Ante chip %(CUR)s(?P<ANTE>[%(NUM)s]+)" % substitutions, re.MULTILINE 

180 ) 

181 re_BringIn = re.compile( 

182 r"^%(PLYR)s (\s?\[ME\]\s)?: (Bring_in chip|Big (B|b)lind\/Bring in|Bring in)\s?(\(timeout\) )?%(CUR)s(?P<BRINGIN>[%(NUM)s]+)" 

183 % substitutions, 

184 re.MULTILINE, 

185 ) 

186 re_PostBoth = re.compile( 

187 r"^%(PLYR)s (\s?\[ME\]\s)?: Posts dead chip %(CUR)s(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE 

188 ) 

189 re_HeroCards = re.compile( 

190 r"^%(PLYR)s ?\[ME\] : Card dealt to a spot \[(?P<NEWCARDS>.+?)\]" % substitutions, re.MULTILINE 

191 ) 

192 re_Action = re.compile( 

193 r"""(?P<ACTION> 

194 ^%(PLYR)s\s(\s?\[ME\]\s)?:(\sD)?(?P<ATYPE>\s(B|b)ets|\sDouble\sbets|\sChecks|\s(R|r)aises|\sCalls?|\sFold|\sBring_in\schip|\sBig\sblind\/Bring\sin|\sAll\-in(\((raise|raise\-timeout)\))?|\sCard\sdealt\sto\sa\sspot) 

195 (\schip\sinfo)?(\(timeout\))?(\s%(CUR)s(?P<BET>[%(NUM)s]+)(\sto\s%(CUR)s(?P<BETTO>[%(NUM)s]+))?|\s\[(?P<NEWCARDS>.+?)\])?)""" 

196 % substitutions, 

197 re.MULTILINE | re.VERBOSE, 

198 ) 

199 re_ShowdownAction = re.compile( 

200 r"^%(PLYR)s (?P<HERO>\s?\[ME\]\s)?: Card dealt to a spot \[(?P<CARDS>.*)\]" % substitutions, re.MULTILINE 

201 ) 

202 # re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and won \([.\d]+\) with (?P<STRING>.*))?" % substitutions, re.MULTILINE) 

203 re_CollectPot1 = re.compile( 

204 r"^%(PLYR)s (\s?\[ME\]\s)?: Hand (R|r)esult(\-Side (P|p)ot)? %(CUR)s(?P<POT1>[%(NUM)s]+)" % substitutions, 

205 re.MULTILINE, 

206 ) 

207 re_Bounty = re.compile( 

208 r"^%(PLYR)s (\s?\[ME\]\s)?: BOUNTY PRIZE \[%(CUR)s(?P<BOUNTY>[%(NUM)s]+)\]" % substitutions, re.MULTILINE 

209 ) 

210 re_Dealt = re.compile(r"^%(PLYR)s (\s?\[ME\]\s)?: Card dealt to a spot" % substitutions, re.MULTILINE) 

211 re_Buyin = re.compile( 

212 r"(\s-\s\d+\s-\s(?P<TOURNAME>.+?))?\s-\s(?P<BUYIN>(?P<TICKET>TT)?(?P<BIAMT>[%(LS)s\d\.,]+)-(?P<BIRAKE>[%(LS)s\d\.,]+)?)\s-\s" 

213 % substitutions 

214 ) 

215 re_Knockout = re.compile(r"\s\((?P<BOUNTY>[%(LS)s\d\.,]+)\sKnockout" % substitutions) 

216 re_Stakes = re.compile( 

217 r"(RING|ZONE)\s-\s(?P<CURRENCY>%(LS)s|)?(?P<SB>[%(NUM)s]+)-(%(LS)s)?(?P<BB>[%(NUM)s]+)" % substitutions 

218 ) 

219 re_Summary = re.compile(r"\*\*\*\sSUMMARY\s\*\*\*") 

220 re_Hole_Third = re.compile(r"\*\*\*\s(3RD\sSTREET|HOLE\sCARDS)\s\*\*\*") 

221 re_ReturnBet = re.compile(r"Return\suncalled\sportion", re.MULTILINE) 

222 # Small Blind : Hand result $19 

223 

224 def compilePlayerRegexs(self, hand): 

225 subst = self.substitutions 

226 players = set(self.playersMap.keys()) 

227 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' 

228 self.compiledPlayers = players 

229 subst["PLYR"] = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" 

230 self.re_CollectPot2 = re.compile( 

231 """ 

232 Seat[\+ ](?P<SEAT>[0-9]+):\s?%(PLYR)s 

233 (\sHI)?\s(%(LS)s)?(?P<POT1>[%(NUM)s]+)? 

234 (?P<STRING>[a-zA-Z ]+) 

235 (?P<CARDS1>\[[-a-zA-Z0-9 ]+\]) 

236 (\sLOW?\s(%(LS)s)?(?P<POT2>[%(NUM)s]+))? 

237 """ 

238 % subst, 

239 re.MULTILINE | re.VERBOSE, 

240 ) 

241 

242 def readSupportedGames(self): 

243 return [ 

244 ["ring", "hold", "nl"], 

245 ["ring", "hold", "pl"], 

246 ["ring", "hold", "fl"], 

247 ["ring", "stud", "fl"], 

248 ["tour", "hold", "nl"], 

249 ["tour", "hold", "pl"], 

250 ["tour", "hold", "fl"], 

251 ["tour", "stud", "fl"], 

252 ] 

253 

254 def parseHeader(self, handText, whole_file): 

255 gametype = self.determineGameType(handText) 

256 if gametype["type"] == "tour": 

257 handlist = re.split(self.re_SplitHands, whole_file) 

258 result = re.findall(self.re_PlayerSeat, handlist[0]) 

259 gametype["maxSeats"] = len(result) 

260 return gametype 

261 

262 def determineGameType(self, handText): 

263 info = {} 

264 m = self.re_GameInfo.search(handText) 

265 if not m: 

266 tmp = handText[0:200] 

267 log.error(("BovadaToFpdb.determineGameType: '%s'") % tmp) 

268 raise FpdbParseError 

269 

270 m1 = self.re_Dealt.search(handText) 

271 m2 = self.re_Summary.split(handText) 

272 m3 = self.re_Hole_Third.split(handText) 

273 if not m1 or len(m2) != 2 or len(m3) > 3: 

274 raise FpdbHandPartial("BovadaToFpdb.determineGameType: " + ("Partial hand history")) 

275 

276 mg = m.groupdict() 

277 m = self.re_Stakes.search(self.in_path) 

278 if m: 

279 mg.update(m.groupdict()) 

280 

281 if "LIMIT" in mg: 

282 if not mg["LIMIT"]: 

283 info["limitType"] = "nl" 

284 else: 

285 info["limitType"] = self.limits[mg["LIMIT"]] 

286 if "GAME" in mg: 

287 (info["base"], info["category"]) = self.games[mg["GAME"]] 

288 

289 if "SB" in mg: 

290 info["sb"] = self.clearMoneyString(mg["SB"]) 

291 if "BB" in mg: 

292 info["bb"] = self.clearMoneyString(mg["BB"]) 

293 

294 if "TOURNO" in mg and mg["TOURNO"] is not None: 

295 info["type"] = "tour" 

296 info["currency"] = "T$" 

297 else: 

298 info["type"] = "ring" 

299 info["currency"] = "USD" 

300 

301 if "CURRENCY" in mg and mg["CURRENCY"] is not None: 

302 info["currency"] = self.currencies[mg["CURRENCY"]] 

303 

304 if "Zone" in mg["GAME"]: 

305 info["fast"] = True 

306 else: 

307 info["fast"] = False 

308 

309 if info["limitType"] == "fl" and info["bb"] is not None: 

310 if info["type"] == "ring": 

311 try: 

312 info["sb"] = self.Lim_Blinds[info["bb"]][0] 

313 info["bb"] = self.Lim_Blinds[info["bb"]][1] 

314 except KeyError: 

315 tmp = handText[0:200] 

316 log.error( 

317 ("BovadaToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") % (mg["BB"], tmp) 

318 ) 

319 raise FpdbParseError 

320 else: 

321 info["sb"] = str((Decimal(info["sb"]) / 2).quantize(Decimal("0.01"))) 

322 info["bb"] = str(Decimal(info["sb"]).quantize(Decimal("0.01"))) 

323 

324 return info 

325 

326 def readHandInfo(self, hand): 

327 info = {} 

328 m = self.re_GameInfo.search(hand.handText) 

329 if m is None: 

330 tmp = hand.handText[0:200] 

331 log.error(("BovadaToFpdb.readHandInfo: '%s'") % tmp) 

332 raise FpdbParseError 

333 

334 info.update(m.groupdict()) 

335 m = self.re_Buyin.search(self.in_path) 

336 if m: 

337 info.update(m.groupdict()) 

338 hand.allInBlind = False 

339 m2 = self.re_Knockout.search(self.in_path) 

340 if m2: 

341 info.update(m2.groupdict()) 

342 

343 if "Hyper Turbo" in self.in_path: 

344 hand.speed = "Hyper" 

345 elif "Turbo" in self.in_path: 

346 hand.speed = "Turbo" 

347 

348 for key in info: 

349 if key == "DATETIME": 

350 m1 = self.re_DateTime.finditer(info[key]) 

351 datetimestr = "2000/01/01 00:00:00" # default used if time not found 

352 for a in m1: 

353 datetimestr = "%s/%s/%s %s:%s:%s" % ( 

354 a.group("Y"), 

355 a.group("M"), 

356 a.group("D"), 

357 a.group("H"), 

358 a.group("MIN"), 

359 a.group("S"), 

360 ) 

361 # tz = a.group('TZ') # just assume ET?? 

362 # print " tz = ", tz, " datetime =", datetimestr 

363 hand.startTime = datetime.datetime.strptime( 

364 datetimestr, "%Y/%m/%d %H:%M:%S" 

365 ) # also timezone at end, e.g. " ET" 

366 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC") 

367 if key == "HID": 

368 hand.handid = info[key] 

369 if key == "TOURNO": 

370 hand.tourNo = info[key] 

371 if key == "BUYIN": 

372 if info["TOURNO"] is not None: 

373 if info[key] == "Freeroll": 

374 hand.buyin = 0 

375 hand.fee = 0 

376 hand.buyinCurrency = "FREE" 

377 else: 

378 if info[key].find("$") != -1: 

379 hand.buyinCurrency = "USD" 

380 elif re.match("^[0-9+]*$", info[key]): 

381 hand.buyinCurrency = "play" 

382 else: 

383 # FIXME: handle other currencies, play money 

384 log.error( 

385 ("BovadaToFpdb.readHandInfo: Failed to detect currency.") 

386 + " Hand ID: %s: '%s'" % (hand.handid, info[key]) 

387 ) 

388 raise FpdbParseError 

389 

390 if info.get("BOUNTY") is not None: 

391 info["BOUNTY"] = self.clearMoneyString( 

392 info["BOUNTY"].strip("$") 

393 ) # Strip here where it isn't 'None' 

394 hand.koBounty = int(100 * Decimal(info["BOUNTY"])) 

395 hand.isKO = True 

396 else: 

397 hand.isKO = False 

398 

399 info["BIAMT"] = self.clearMoneyString(info["BIAMT"].strip("$")) 

400 

401 if info["BIRAKE"]: 

402 info["BIRAKE"] = self.clearMoneyString(info["BIRAKE"].strip("$")) 

403 else: 

404 info["BIRAKE"] = "0" 

405 

406 if info["TICKET"] is None: 

407 hand.buyin = int(100 * Decimal(info["BIAMT"])) 

408 hand.fee = int(100 * Decimal(info["BIRAKE"])) 

409 else: 

410 hand.buyin = 0 

411 hand.fee = 0 

412 if key == "TABLE": 

413 if info.get("TABLENO"): 

414 hand.tablename = info.get("TABLENO") 

415 elif info["ZONE"] and "Zone" in info["ZONE"]: 

416 hand.tablename = info["ZONE"] + " " + info[key] 

417 else: 

418 hand.tablename = info[key] 

419 if key == "MAX" and info[key] is not None: 

420 hand.maxseats = int(info[key]) 

421 if key == "HU" and info[key] is not None: 

422 hand.maxseats = 2 

423 if key == "VERSION": 

424 hand.version = info[key] 

425 

426 if not hand.maxseats: 

427 hand.maxseats = 9 

428 

429 if not hand.version: 

430 hand.version = "LEGACY" 

431 

432 def readButton(self, hand): 

433 m = self.re_Button.search(hand.handText) 

434 if m: 

435 hand.buttonpos = int(m.group("BUTTON")) 

436 

437 def readPlayerStacks(self, hand): 

438 self.playersMap, seatNo = {}, 1 

439 if hand.gametype["base"] in ("stud"): 

440 m = self.re_PlayerInfoStud.finditer(hand.handText) 

441 else: 

442 m = self.re_PlayerInfo.finditer(hand.handText) 

443 for a in m: 

444 if ( 

445 re.search(r"%s (\s?\[ME\]\s)?: Card dealt to a spot" % re.escape(a.group("PNAME")), hand.handText) 

446 or hand.version == "MVS" 

447 ): 

448 if not hand.buttonpos and a.group("PNAME") == "Dealer": 

449 hand.buttonpos = int(a.group("SEAT")) 

450 if a.group("HERO"): 

451 self.playersMap[a.group("PNAME")] = "Hero" 

452 else: 

453 self.playersMap[a.group("PNAME")] = "Seat %s" % a.group("SEAT") 

454 hand.addPlayer(seatNo, self.playersMap[a.group("PNAME")], self.clearMoneyString(a.group("CASH"))) 

455 seatNo += 1 

456 if len(hand.players) == 0: 

457 tmp = hand.handText[0:200] 

458 log.error(("BovadaToFpdb.readPlayerStacks: '%s'") % tmp) 

459 raise FpdbParseError 

460 elif len(hand.players) == 10: 

461 hand.maxseats = 10 

462 

463 def playerSeatFromPosition(self, source, handid, position): 

464 player = self.playersMap.get(position) 

465 if player is None: 

466 log.error(("Hand.%s: '%s' unknown player seat from position: '%s'") % (source, handid, position)) 

467 raise FpdbParseError 

468 return player 

469 

470 def markStreets(self, hand): 

471 # PREFLOP = ** Dealing down cards ** 

472 # This re fails if, say, river is missing; then we don't get the ** that starts the river. 

473 if hand.gametype["base"] == "hold": 

474 street, firststreet = "PREFLOP", "PREFLOP" 

475 else: 

476 street, firststreet = "THIRD", "THIRD" 

477 m = self.re_Action.finditer(self.re_Hole_Third.split(hand.handText)[0]) 

478 allinblind = 0 

479 for action in m: 

480 if action.group("ATYPE") == " All-in": 

481 allinblind += 1 

482 m = self.re_Action.finditer(self.re_Hole_Third.split(hand.handText)[-1]) 

483 dealtIn = len(hand.players) - allinblind 

484 streetactions, streetno, players, contenders, bets, acts = 0, 1, dealtIn, 0, dealtIn, 0, None 

485 for action in m: 

486 if action.groupdict() != acts or streetactions == 0: 

487 acts = action.groupdict() 

488 # print "DEBUG: %s, %s, %s" % (street, acts['PNAME'], acts['ATYPE']), action.group('BET'), streetactions, players, contenders 

489 # player = self.playerSeatFromPosition("BovadaToFpdb.markStreets", hand.handid, action.group("PNAME")) 

490 if action.group("ATYPE") == " Fold": 

491 contenders -= 1 

492 elif action.group("ATYPE") in (" Raises", " raises"): 

493 if streetno == 1: 

494 bets = 1 

495 streetactions, players = 0, contenders 

496 elif action.group("ATYPE") in (" Bets", " bets", " Double bets"): 

497 streetactions, players, bets = 0, contenders, 1 

498 elif action.group("ATYPE") in (" All-in(raise)", "All-in(raise-timeout)"): 

499 streetactions, players = 0, contenders 

500 contenders -= 1 

501 elif action.group("ATYPE") == " All-in": 

502 if bets == 0 and streetno > 1: 

503 streetactions, players, bets = 0, contenders, 1 

504 contenders -= 1 

505 if action.group("ATYPE") != " Card dealt to a spot": 

506 if action.group("ATYPE") != " Big blind/Bring in" or hand.gametype["base"] == "stud": 

507 streetactions += 1 

508 hand.streets[street] += action.group("ACTION") + "\n" 

509 # print street, action.group('PNAME'), action.group('ATYPE'), streetactions, players, contenders 

510 if streetactions == players: 

511 streetno += 1 

512 if streetno < len(hand.actionStreets): 

513 street = hand.actionStreets[streetno] 

514 streetactions, players, bets = 0, contenders, 0 

515 

516 if not hand.streets.get(firststreet): 

517 hand.streets[firststreet] = hand.handText 

518 if hand.gametype["base"] == "hold": 

519 if hand.gametype["fast"]: 

520 for street in ("FLOP", "TURN", "RIVER"): 

521 m1 = self.re_Board2[street].search(hand.handText) 

522 if m1 and m1.group("CARDS") and not hand.streets.get(street): 

523 hand.streets[street] = m1.group("CARDS") 

524 else: 

525 m1 = self.re_Board1.search(hand.handText) 

526 for street in ("FLOP", "TURN", "RIVER"): 

527 if m1 and m1.group(street) and not hand.streets.get(street): 

528 hand.streets[street] = m1.group(street) 

529 

530 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand 

531 if hand.gametype["fast"]: 

532 m = self.re_Board2[street].search(hand.handText) 

533 if m and m.group("CARDS"): 

534 hand.setCommunityCards(street, m.group("CARDS").split(" ")) 

535 else: 

536 if street in ( 

537 "FLOP", 

538 "TURN", 

539 "RIVER", 

540 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP) 

541 m = self.re_Board1.search(hand.handText) 

542 if m and m.group(street): 

543 cards = m.group(street).split(" ") 

544 hand.setCommunityCards(street, cards) 

545 

546 def readAntes(self, hand): 

547 antes = None 

548 m = self.re_Antes.finditer(hand.handText) 

549 for a in m: 

550 if a.groupdict() != antes: 

551 antes = a.groupdict() 

552 player = self.playerSeatFromPosition("BovadaToFpdb.readAntes", hand.handid, antes["PNAME"]) 

553 hand.addAnte(player, self.clearMoneyString(antes["ANTE"])) 

554 

555 def readBringIn(self, hand): 

556 m = self.re_BringIn.search(hand.handText, re.DOTALL) 

557 if m: 

558 # ~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) 

559 player = self.playerSeatFromPosition("BovadaToFpdb.readBringIn", hand.handid, m.group("PNAME")) 

560 hand.addBringIn(player, self.clearMoneyString(m.group("BRINGIN"))) 

561 

562 if hand.gametype["sb"] is None and hand.gametype["bb"] is None: 

563 hand.gametype["sb"] = "1" 

564 hand.gametype["bb"] = "2" 

565 

566 def readBlinds(self, hand): 

567 # sb, bb, 

568 acts, postsb, postbb, both = None, None, None, None # , None, None 

569 if not self.re_ReturnBet.search(hand.handText): 

570 hand.setUncalledBets(True) 

571 for a in self.re_PostSB.finditer(hand.handText): 

572 if postsb != a.groupdict(): 

573 postsb = a.groupdict() 

574 player = self.playerSeatFromPosition("BovadaToFpdb.readBlinds.postSB", hand.handid, postsb["PNAME"]) 

575 hand.addBlind(player, "small blind", self.clearMoneyString(postsb["SB"])) 

576 if not hand.gametype["sb"]: 

577 hand.gametype["sb"] = self.clearMoneyString(postsb["SB"]) 

578 # sb = self.clearMoneyString(postsb["SB"]) 

579 self.allInBlind(hand, "PREFLOP", a, "small blind") 

580 for a in self.re_PostBB.finditer(hand.handText): 

581 if postbb != a.groupdict(): 

582 postbb = a.groupdict() 

583 player = self.playerSeatFromPosition("BovadaToFpdb.readBlinds.postBB", hand.handid, "Big Blind") 

584 hand.addBlind(player, "big blind", self.clearMoneyString(postbb["BB"])) 

585 self.allInBlind(hand, "PREFLOP", a, "big blind") 

586 if not hand.gametype["bb"]: 

587 hand.gametype["bb"] = self.clearMoneyString(postbb["BB"]) 

588 # bb = self.clearMoneyString(postbb["BB"]) 

589 if not hand.gametype["currency"]: 

590 if postbb["CURRENCY"].find("$") != -1: 

591 hand.gametype["currency"] = "USD" 

592 elif re.match("^[0-9+]*$", postbb["CURRENCY"]): 

593 hand.gametype["currency"] = "play" 

594 for a in self.re_Action.finditer(self.re_Hole_Third.split(hand.handText)[0]): 

595 if acts != a.groupdict(): 

596 acts = a.groupdict() 

597 if acts["ATYPE"] == " All-in": 

598 re_Ante_Plyr = re.compile( 

599 r"^" 

600 + re.escape(acts["PNAME"]) 

601 + " (\s?\[ME\]\s)?: Ante chip %(CUR)s(?P<ANTE>[%(NUM)s]+)" % self.substitutions, 

602 re.MULTILINE, 

603 ) 

604 m = self.re_Antes.search(hand.handText) 

605 m1 = re_Ante_Plyr.search(hand.handText) 

606 if not m or m1: 

607 player = self.playerSeatFromPosition( 

608 "BovadaToFpdb.readBlinds.postBB", hand.handid, acts["PNAME"] 

609 ) 

610 if acts["PNAME"] == "Big Blind": 

611 hand.addBlind(player, "big blind", self.clearMoneyString(acts["BET"])) 

612 self.allInBlind(hand, "PREFLOP", a, "big blind") 

613 elif acts["PNAME"] == "Small Blind" or (acts["PNAME"] == "Dealer" and len(hand.players) == 2): 

614 hand.addBlind(player, "small blind", self.clearMoneyString(acts["BET"])) 

615 self.allInBlind(hand, "PREFLOP", a, "small blind") 

616 elif m: 

617 player = self.playerSeatFromPosition("BovadaToFpdb.readAntes", hand.handid, acts["PNAME"]) 

618 hand.addAnte(player, self.clearMoneyString(acts["BET"])) 

619 self.allInBlind(hand, "PREFLOP", a, "antes") 

620 self.fixBlinds(hand) 

621 for a in self.re_PostBoth.finditer(hand.handText): 

622 if both != a.groupdict(): 

623 both = a.groupdict() 

624 player = self.playerSeatFromPosition("BovadaToFpdb.readBlinds.postBoth", hand.handid, both["PNAME"]) 

625 hand.addBlind(player, "both", self.clearMoneyString(both["SBBB"])) 

626 self.allInBlind(hand, "PREFLOP", a, "both") 

627 

628 def fixBlinds(self, hand): 

629 if hand.gametype["sb"] is None and hand.gametype["bb"] is not None: 

630 BB = str(Decimal(hand.gametype["bb"]) * 2) 

631 if self.Lim_Blinds.get(BB) is not None: 

632 hand.gametype["sb"] = self.Lim_Blinds.get(BB)[0] 

633 elif hand.gametype["bb"] is None and hand.gametype["sb"] is not None: 

634 for k, v in list(self.Lim_Blinds.items()): 

635 if hand.gametype["sb"] == v[0]: 

636 hand.gametype["bb"] = v[1] 

637 if hand.gametype["sb"] is None or hand.gametype["bb"] is None: 

638 log.error(("BovadaToFpdb.fixBlinds: Failed to fix blinds") + " Hand ID: %s" % (hand.handid,)) 

639 raise FpdbParseError 

640 hand.sb = hand.gametype["sb"] 

641 hand.bb = hand.gametype["bb"] 

642 

643 def readHoleCards(self, hand): 

644 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause 

645 # we need to grab hero's cards 

646 for street in ("PREFLOP", "DEAL"): 

647 if street in hand.streets.keys(): 

648 foundDict = None 

649 m = self.re_HeroCards.finditer(hand.handText) 

650 for found in m: 

651 if found.groupdict() != foundDict: 

652 foundDict = found.groupdict() 

653 hand.hero = "Hero" 

654 newcards = found.group("NEWCARDS").split(" ") 

655 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) 

656 

657 for street, text in list(hand.streets.items()): 

658 if not text or street in ("PREFLOP", "DEAL"): 

659 continue # already done these 

660 m = self.re_ShowdownAction.finditer(hand.streets[street]) 

661 foundDict = None 

662 for found in m: 

663 if foundDict != found.groupdict(): 

664 foundDict = found.groupdict() 

665 player = self.playerSeatFromPosition( 

666 "BovadaToFpdb.readHoleCards", hand.handid, found.group("PNAME") 

667 ) 

668 if street != "SEVENTH" or found.group("HERO"): 

669 newcards = found.group("CARDS").split(" ") 

670 oldcards = [] 

671 else: 

672 oldcards = found.group("CARDS").split(" ") 

673 newcards = [] 

674 

675 if street == "THIRD" and found.group("HERO"): # hero in stud game 

676 hand.hero = "Hero" 

677 hand.dealt.add(player) # need this for stud?? 

678 hand.addHoleCards( 

679 street, 

680 player, 

681 closed=newcards[0:2], 

682 open=[newcards[2]], 

683 shown=False, 

684 mucked=False, 

685 dealt=False, 

686 ) 

687 else: 

688 hand.addHoleCards( 

689 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False 

690 ) 

691 

692 def readAction(self, hand, street): 

693 acts = None 

694 m = self.re_Action.finditer(hand.streets[street]) 

695 for action in m: 

696 if acts != action.groupdict(): 

697 acts = action.groupdict() 

698 player = self.playerSeatFromPosition("BovadaToFpdb.readAction", hand.handid, action.group("PNAME")) 

699 # print "DEBUG: %s, %s, %s, %s, %s" % (street, acts['PNAME'], player, acts['ATYPE'], action.group('BET')) 

700 if ( 

701 action.group("ATYPE") not in (" Checks", " Fold", " Card dealt to a spot", " Big blind/Bring in") 

702 and not hand.allInBlind 

703 ): 

704 hand.setUncalledBets(False) 

705 if action.group("ATYPE") == " Fold": 

706 hand.addFold(street, player) 

707 elif action.group("ATYPE") == " Checks": 

708 hand.addCheck(street, player) 

709 elif action.group("ATYPE") == " Calls" or action.group("ATYPE") == " Call": 

710 if not action.group("BET"): 

711 log.error("BovadaToFpdb.readAction: Amount not found in Call %s" % hand.handid) 

712 raise FpdbParseError 

713 hand.addCall(street, player, self.clearMoneyString(action.group("BET"))) 

714 elif action.group("ATYPE") in (" Raises", " raises", " All-in(raise)", " All-in(raise-timeout)"): 

715 if action.group("BETTO"): 

716 bet = self.clearMoneyString(action.group("BETTO")) 

717 elif action.group("BET"): 

718 bet = self.clearMoneyString(action.group("BET")) 

719 else: 

720 log.error("BovadaToFpdb.readAction: Amount not found in Raise %s" % hand.handid) 

721 raise FpdbParseError 

722 hand.addRaiseTo(street, player, bet) 

723 elif action.group("ATYPE") in (" Bets", " bets", " Double bets"): 

724 if not action.group("BET"): 

725 log.error("BovadaToFpdb.readAction: Amount not found in Bet %s" % hand.handid) 

726 raise FpdbParseError 

727 hand.addBet(street, player, self.clearMoneyString(action.group("BET"))) 

728 elif action.group("ATYPE") == " All-in": 

729 if not action.group("BET"): 

730 log.error("BovadaToFpdb.readAction: Amount not found in All-in %s" % hand.handid) 

731 raise FpdbParseError 

732 hand.addAllIn(street, player, self.clearMoneyString(action.group("BET"))) 

733 self.allInBlind(hand, street, action, action.group("ATYPE")) 

734 elif action.group("ATYPE") == " Bring_in chip": 

735 pass 

736 elif action.group("ATYPE") in (" Card dealt to a spot", " Big blind/Bring in"): 

737 pass 

738 else: 

739 log.debug( 

740 ("DEBUG:") 

741 + " " 

742 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE")) 

743 ) 

744 

745 def allInBlind(self, hand, street, action, actiontype): 

746 if street in ("PREFLOP", "DEAL"): 

747 player = self.playerSeatFromPosition("BovadaToFpdb.allInBlind", hand.handid, action.group("PNAME")) 

748 if hand.stacks[player] == 0 and not self.re_ReturnBet.search(hand.handText): 

749 hand.setUncalledBets(True) 

750 hand.allInBlind = True 

751 

752 def readShowdownActions(self, hand): 

753 # TODO: pick up mucks also?? 

754 if hand.gametype["base"] in ("hold"): 

755 for shows in self.re_ShowdownAction.finditer(hand.handText): 

756 cards = shows.group("CARDS").split(" ") 

757 player = self.playerSeatFromPosition( 

758 "BovadaToFpdb.readShowdownActions", hand.handid, shows.group("PNAME") 

759 ) 

760 hand.addShownCards(cards, player) 

761 

762 def readCollectPot(self, hand): 

763 if self.re_CollectPot2.search(hand.handText): 

764 re_CollectPot = self.re_CollectPot2 

765 else: 

766 re_CollectPot = self.re_CollectPot1 

767 for m in re_CollectPot.finditer( 

768 hand.handText.replace(" [ME]", "") if hand.version == "MVS" else hand.handText 

769 ): # [ME] 

770 collect, pot = m.groupdict(), 0 

771 if "POT1" in collect and collect["POT1"] is not None: 

772 pot += Decimal(self.clearMoneyString(collect["POT1"])) 

773 if "POT2" in collect and collect["POT2"] is not None: 

774 pot += Decimal(self.clearMoneyString(collect["POT2"])) 

775 if pot > 0: 

776 player = self.playerSeatFromPosition("BovadaToFpdb.readCollectPot", hand.handid, collect["PNAME"]) 

777 hand.addCollectPot(player=player, pot=str(pot)) 

778 

779 def readShownCards(self, hand): 

780 pass 

781 

782 def readTourneyResults(self, hand): 

783 """Reads knockout bounties and add them to the koCounts dict""" 

784 for a in self.re_Bounty.finditer(hand.handText): 

785 player = self.playerSeatFromPosition("BovadaToFpdb.readCollectPot", hand.handid, a.group("PNAME")) 

786 if player not in hand.koCounts: 

787 hand.koCounts[player] = 0 

788 hand.koCounts[player] += 1