Coverage for BetOnlineToFpdb.py: 0%

375 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-15 19:33 +0000

1#!/usr/bin/env python 

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

3# 

4# Copyright 2008-2011, 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 

21from __future__ import division 

22 

23from past.utils import old_div 

24# import L10n 

25# _ = L10n.get_translation() 

26 

27# TODO: straighten out discards for draw games 

28 

29from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial 

30from decimal import Decimal 

31import re 

32import logging 

33import datetime 

34 

35log = logging.getLogger("parser") 

36# BetOnline HH Format 

37 

38 

39class BetOnline(HandHistoryConverter): 

40 # Class Variables 

41 

42 sitename = "BetOnline" 

43 skin = "BetOnline" 

44 filetype = "text" 

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

46 siteId = 19 # Needs to match id entry in Sites database 

47 sym = {"USD": "\$", "CAD": "\$", "T$": "", "EUR": "€", "GBP": "\xa3", "play": ""} # ADD Euro, Sterling, etc HERE 

48 substitutions = { 

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

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

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.40": ("0.10", "0.20"), 

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

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

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

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

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

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

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

68 "6.00": ("1.00", "3.00"), 

69 "6": ("1.00", "3.00"), 

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

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

72 "10.00": ("2.00", "5.00"), 

73 "10": ("2.00", "5.00"), 

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

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

76 "30.00": ("10.00", "15.00"), 

77 "30": ("10.00", "15.00"), 

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

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

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

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

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

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

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

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

86 "200.00": ("50.00", "100.00"), 

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

88 "400.00": ("100.00", "200.00"), 

89 "400": ("100.00", "200.00"), 

90 "800.00": ("200.00", "400.00"), 

91 "800": ("200.00", "400.00"), 

92 "1000.00": ("250.00", "500.00"), 

93 "1000": ("250.00", "500.00"), 

94 } 

95 

96 limits = {"No Limit": "nl", "Pot Limit": "pl", "Limit": "fl", "LIMIT": "fl"} 

97 games = { # base, category 

98 "Hold'em": ("hold", "holdem"), 

99 "Omaha": ("hold", "omahahi"), 

100 "Omaha Hi/Lo": ("hold", "omahahilo"), 

101 "Razz": ("stud", "razz"), 

102 "7 Card Stud": ("stud", "studhi"), 

103 "7 Card Stud Hi/Lo": ("stud", "studhilo"), 

104 "Badugi": ("draw", "badugi"), 

105 "Triple Draw 2-7 Lowball": ("draw", "27_3draw"), 

106 "Single Draw 2-7 Lowball": ("draw", "27_1draw"), 

107 "5 Card Draw": ("draw", "fivedraw"), 

108 } 

109 mixes = { 

110 "HORSE": "horse", 

111 "8-Game": "8game", 

112 "HOSE": "hose", 

113 "Mixed PLH/PLO": "plh_plo", 

114 "Mixed Omaha H/L": "plo_lo", 

115 "Mixed Hold'em": "mholdem", 

116 "Triple Stud": "3stud", 

117 } # Legal mixed games 

118 currencies = {"€": "EUR", "$": "USD", "": "T$"} 

119 

120 skins = { 

121 "BetOnline Poker": "BetOnline", 

122 "PayNoRake": "PayNoRake", 

123 "ActionPoker.com": "ActionPoker", 

124 "Gear Poker": "GearPoker", 

125 "SportsBetting.ag Poker": "SportsBetting.ag", 

126 "Tiger Gaming": "Tiger Gaming", 

127 } # Legal mixed games 

128 

129 # Static regexes 

130 re_GameInfo = re.compile( 

131 """ 

132 (?P<SKIN>BetOnline\sPoker|PayNoRake|ActionPoker\.com|Gear\sPoker|SportsBetting\.ag\sPoker|Tiger\sGaming)\sGame\s\#(?P<HID>[0-9]+):\s+ 

133 (\{.*\}\s+)?(Tournament\s\# # open paren of tournament info 

134 (?P<TOURNO>\d+):\s? 

135 # here's how I plan to use LS 

136 (?P<BUYIN>(?P<BIAMT>(%(LS)s)[%(NUM)s]+)?\+?(?P<BIRAKE>(%(LS)s)[%(NUM)s]+)?\+?(?P<BOUNTY>(%(LS)s)[%(NUM)s]+)?\s?|Freeroll|)\s+)? 

137 # close paren of tournament info 

138 (?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|Single\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s 

139 (?P<LIMIT>No\sLimit|Limit|LIMIT|Pot\sLimit)?,?\s? 

140 ( 

141 \(? # open paren of the stakes 

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

143 (?P<SB>[%(NUM)s]+)/(%(LS)s)? 

144 (?P<BB>[%(NUM)s]+) 

145 \)? # close paren of the stakes 

146 )? 

147 \s?-\s 

148 (?P<DATETIME>.*$) 

149 """ 

150 % substitutions, 

151 re.MULTILINE | re.VERBOSE, 

152 ) 

153 

154 re_PlayerInfo = re.compile( 

155 """ 

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

157 (?P<PNAME>.*)\s 

158 \((%(LS)s)?(?P<CASH>[%(NUM)s]+)\sin\s[cC]hips\)""" 

159 % substitutions, 

160 re.MULTILINE | re.VERBOSE, 

161 ) 

162 

163 re_HandInfo1 = re.compile( 

164 """ 

165 ^Table\s\'(?P<TABLE>[\/,\.\-\ &%\$\#a-zA-Z\d\'\(\)]+)\'\s 

166 ((?P<MAX>\d+)-max\s)? 

167 (?P<MONEY>\((Play\sMoney|Real\sMoney)\)\s)? 

168 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""", 

169 re.MULTILINE | re.VERBOSE, 

170 ) 

171 

172 re_HandInfo2 = re.compile( 

173 """ 

174 ^Table\s(?P<TABLE>[\/,\.\-\ &%\$\#a-zA-Z\d\']+)\s 

175 (?P<MONEY>\((Play\sMoney|Real\sMoney)\)\s)? 

176 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""", 

177 re.MULTILINE | re.VERBOSE, 

178 ) 

179 

180 re_Identify = re.compile( 

181 "(BetOnline\sPoker|PayNoRake|ActionPoker\.com|Gear\sPoker|SportsBetting\.ag\sPoker|Tiger\sGaming)\sGame\s\#\d+" 

182 ) 

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

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

185 re_Button = re.compile("Seat #(?P<BUTTON>\d+) is the button", re.MULTILINE) 

186 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?)?\]") 

187 re_Board2 = re.compile(r"\[(?P<CARDS>.+)\]") 

188 re_Hole = re.compile(r"\*\*\*\sHOLE\sCARDS\s\*\*\*") 

189 

190 re_DateTime1 = re.compile( 

191 """(?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]+))?\s(?P<TZ>.*$)""", 

192 re.MULTILINE, 

193 ) 

194 re_DateTime2 = re.compile( 

195 """(?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]+)""", 

196 re.MULTILINE, 

197 ) 

198 

199 re_PostSB = re.compile(r"^%(PLYR)s: [Pp]osts small blind (%(LS)s)?(?P<SB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

200 re_PostBB = re.compile( 

201 r"^%(PLYR)s: ([Pp]osts big blind|[Pp]osts? [Nn]ow)( (%(LS)s)?(?P<BB>[%(NUM)s]+))?" % substitutions, re.MULTILINE 

202 ) 

203 re_Antes = re.compile(r"^%(PLYR)s: ante processed (%(LS)s)?(?P<ANTE>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

204 re_BringIn = re.compile( 

205 r"^%(PLYR)s: brings[- ]in( low|) for (%(LS)s)?(?P<BRINGIN>[%(NUM)s]+)" % substitutions, re.MULTILINE 

206 ) 

207 re_PostBoth = re.compile(r"^%(PLYR)s: [Pp]ost dead (%(LS)s)?(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

208 re_HeroCards = re.compile( 

209 r"^Dealt [Tt]o %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE 

210 ) 

211 re_Action = re.compile( 

212 r""" 

213 ^%(PLYR)s:?(?P<ATYPE>\shas\sleft\sthe\stable|\s[Bb]ets|\s[Cc]hecks|\s[Rr]aises|\s[Cc]alls|\s[Ff]olds|\s[Dd]iscards|\s[Ss]tands\spat|\sReraises) 

214 (\s(%(LS)s)?(?P<BET>[%(NUM)s]+))?(\sto\s(%(LS)s)?(?P<BETTO>[%(NUM)s]+))? # the number discarded goes in <BET> 

215 \s*(and\sis\s[Aa]ll.[Ii]n)? 

216 (\son|\scards?)? 

217 (\s\[(?P<CARDS>.+?)\])?\.?\s*$""" 

218 % substitutions, 

219 re.MULTILINE | re.VERBOSE, 

220 ) 

221 re_ShowdownAction = re.compile(r"^%s: shows (?P<CARDS>.*)" % substitutions["PLYR"], re.MULTILINE) 

222 re_sitsOut = re.compile("^%s sits out" % substitutions["PLYR"], re.MULTILINE) 

223 re_JoinsTable = re.compile("^.+ joins the table at seat #\d+", re.MULTILINE) 

224 re_TotalPot = re.compile(r"^Total pot (?P<POT>[%(NUM)s]+)( \| Rake (?P<RAKE>[%(NUM)s]+))?", re.MULTILINE) 

225 re_ShownCards = re.compile( 

226 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(.+?\) )?(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and won \([%(NUM)s]+\))?" 

227 % substitutions, 

228 re.MULTILINE, 

229 ) 

230 re_CollectPot = re.compile( 

231 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(.+?\) )?(collected|showed \[.*\] and won) \((%(LS)s)?(?P<POT>[%(NUM)s]+)\)" 

232 % substitutions, 

233 re.MULTILINE, 

234 ) 

235 

236 def compilePlayerRegexs(self, hand): 

237 pass 

238 

239 def readSupportedGames(self): 

240 return [ 

241 ["ring", "hold", "nl"], 

242 ["ring", "hold", "pl"], 

243 ["ring", "hold", "fl"], 

244 # ["ring", "stud", "fl"], 

245 # ["ring", "draw", "fl"], 

246 # ["ring", "draw", "pl"], 

247 # ["ring", "draw", "nl"], 

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

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

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

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

252 # ["tour", "draw", "fl"], 

253 # ["tour", "draw", "pl"], 

254 # ["tour", "draw", "nl"], 

255 ] 

256 

257 def determineGameType(self, handText): 

258 info = {} 

259 m = self.re_GameInfo.search(handText) 

260 if not m: 

261 # BetOnline starts writing the hh the moment you sit down. 

262 # Test if the hh contains the join line, and throw a partial if so. 

263 m2 = self.re_JoinsTable.search(handText) 

264 if not m2: 

265 tmp = handText[0:200] 

266 log.error(("BetOnlineToFpdb.determineGameType: '%s'") % tmp) 

267 raise FpdbParseError 

268 else: 

269 raise FpdbHandPartial( 

270 "BetOnlineToFpdb.determineGameType: " + ("Partial hand history: 'Player joining table'") 

271 ) 

272 

273 mg = m.groupdict() 

274 if mg["LIMIT"]: 

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

276 if info["limitType"] == "pl": 

277 m = self.re_HeroCards.search(handText) 

278 if m and len(m.group("NEWCARDS").split(" ")) == 4: 

279 (info["base"], info["category"]) = self.games["Omaha"] 

280 else: 

281 info["limitType"] = self.limits["No Limit"] 

282 if "SKIN" in mg: 

283 self.skin = self.skins[mg["SKIN"]] 

284 if "GAME" in mg and not info.get("base"): 

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

286 if "SB" in mg: 

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

288 if "BB" in mg: 

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

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

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

292 else: 

293 info["currency"] = "USD" 

294 if "MIXED" in mg: 

295 if mg["MIXED"] is not None: 

296 info["mix"] = self.mixes[mg["MIXED"]] 

297 

298 if "TOURNO" in mg and mg["TOURNO"] is None: 

299 info["type"] = "ring" 

300 else: 

301 info["type"] = "tour" 

302 

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

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

305 try: 

306 info["sb"] = self.Lim_Blinds[info["BB"]][0] 

307 info["bb"] = self.Lim_Blinds[info["BB"]][1] 

308 except KeyError: 

309 tmp = handText[0:200] 

310 log.error( 

311 ("BetOnlineToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") 

312 % (mg["BB"], tmp) 

313 ) 

314 raise FpdbParseError 

315 else: 

316 info["sb"] = str((old_div(Decimal(info["SB"]), 2)).quantize(Decimal("0.01"))) 

317 info["bb"] = str(Decimal(info["SB"]).quantize(Decimal("0.01"))) 

318 

319 return info 

320 

321 def readHandInfo(self, hand): 

322 info = {} 

323 if self.skin in ("ActionPoker", "GearPoker"): 

324 m = self.re_HandInfo2.search(hand.handText, re.DOTALL) 

325 else: 

326 m = self.re_HandInfo1.search(hand.handText, re.DOTALL) 

327 m2 = self.re_GameInfo.search(hand.handText) 

328 if m is None or m2 is None: 

329 tmp = hand.handText[0:200] 

330 log.error(("BetOnlineToFpdb.readHandInfo: '%s'") % tmp) 

331 raise FpdbParseError 

332 

333 info.update(m.groupdict()) 

334 info.update(m2.groupdict()) 

335 

336 # print 'DEBUG:', info 

337 for key in info: 

338 if key == "DATETIME": 

339 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other) 

340 # 2008/08/17 - 01:14:43 (ET) 

341 # 2008/09/07 06:23:14 ET 

342 

343 datetimestr, time_zone = "2000/01/01 00:00:00", "ET" # default used if time not found 

344 if self.skin not in ("ActionPoker", "GearPoker"): 

345 m1 = self.re_DateTime1.finditer(info[key]) 

346 for a in m1: 

347 seconds = "00" 

348 if a.group("S"): 

349 seconds = a.group("S") 

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

351 a.group("Y"), 

352 a.group("M"), 

353 a.group("D"), 

354 a.group("H"), 

355 a.group("MIN"), 

356 seconds, 

357 ) 

358 tz = a.group("TZ") # just assume ET?? 

359 if tz == "GMT Standard Time": 

360 time_zone = "GMT" 

361 elif tz in ("Pacific Daylight Time", "Pacific Standard Time"): 

362 time_zone = "PT" 

363 else: 

364 time_zone = "ET" 

365 else: 

366 m2 = self.re_DateTime2.finditer(info[key]) 

367 for a in m2: 

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

369 a.group("Y"), 

370 a.group("M"), 

371 a.group("D"), 

372 a.group("H"), 

373 a.group("MIN"), 

374 a.group("S"), 

375 ) 

376 time_zone = "ET" 

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

378 hand.startTime = datetime.datetime.strptime( 

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

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

381 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, time_zone, "UTC") 

382 if key == "HID": 

383 hand.handid = info[key] 

384 if key == "MONEY": 

385 if info[key] == "(Play Money) ": 

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

387 if key == "TOURNO": 

388 hand.tourNo = info[key] 

389 if key == "BUYIN": 

390 if hand.tourNo is not None: 

391 # print "DEBUG: info['BUYIN']: %s" % info['BUYIN'] 

392 # print "DEBUG: info['BIAMT']: %s" % info['BIAMT'] 

393 # print "DEBUG: info['BIRAKE']: %s" % info['BIRAKE'] 

394 # print "DEBUG: info['BOUNTY']: %s" % info['BOUNTY'] 

395 if not info[key] or info[key] == "Freeroll": 

396 hand.buyin = 0 

397 hand.fee = 0 

398 hand.buyinCurrency = "FREE" 

399 else: 

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

401 hand.buyinCurrency = "USD" 

402 elif info[key].find("€") != -1: 

403 hand.buyinCurrency = "EUR" 

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

405 hand.buyinCurrency = "play" 

406 else: 

407 # FIXME: handle other currencies, play money 

408 raise FpdbParseError( 

409 ("BetOnlineToFpdb.readHandInfo: Failed to detect currency.") 

410 + " " 

411 + ("Hand ID") 

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

413 ) 

414 

415 info["BIAMT"] = info["BIAMT"].strip("$€") 

416 if info["BOUNTY"] is not None: 

417 # There is a bounty, Which means we need to switch BOUNTY and BIRAKE values 

418 tmp = info["BOUNTY"] 

419 info["BOUNTY"] = info["BIRAKE"] 

420 info["BIRAKE"] = tmp 

421 info["BOUNTY"] = info["BOUNTY"].strip("$€") # Strip here where it isn't 'None' 

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

423 hand.isKO = True 

424 else: 

425 hand.isKO = False 

426 

427 info["BIRAKE"] = info["BIRAKE"].strip("$€") 

428 

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

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

431 if key == "LEVEL": 

432 hand.level = info[key] 

433 

434 if key == "TABLE": 

435 if hand.tourNo is not None: 

436 hand.tablename = re.split("-", info[key])[1] 

437 else: 

438 hand.tablename = info[key] 

439 if key == "BUTTON": 

440 hand.buttonpos = info[key] 

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

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

443 if not self.re_Board1.search(hand.handText) and self.skin not in ("ActionPoker", "GearPoker"): 

444 raise FpdbHandPartial("readHandInfo: " + ("Partial hand history") + ": '%s'" % hand.handid) 

445 

446 def readButton(self, hand): 

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

448 if m: 

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

450 else: 

451 log.info("readButton: " + ("not found")) 

452 

453 def readPlayerStacks(self, hand): 

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

455 for a in m: 

456 pname = self.unknownPlayer(hand, a.group("PNAME")) 

457 hand.addPlayer(int(a.group("SEAT")), pname, self.clearMoneyString(a.group("CASH"))) 

458 

459 def markStreets(self, hand): 

460 # There is no marker between deal and draw in Stars single draw games 

461 # this upsets the accounting, incorrectly sets handsPlayers.cardxx and 

462 # in consequence the mucked-display is incorrect. 

463 # Attempt to fix by inserting a DRAW marker into the hand text attribute 

464 

465 if hand.gametype["category"] in ("27_1draw", "fivedraw"): 

466 # isolate the first discard/stand pat line (thanks Carl for the regex) 

467 discard_split = re.split(r"(?:(.+(?: stands pat|: discards).+))", hand.handText, re.DOTALL) 

468 if len(hand.handText) == len(discard_split[0]): 

469 # handText was not split, no DRAW street occurred 

470 pass 

471 else: 

472 # DRAW street found, reassemble, with DRAW marker added 

473 discard_split[0] += "*** DRAW ***\r\n" 

474 hand.handText = "" 

475 for i in discard_split: 

476 hand.handText += i 

477 

478 # PREFLOP = ** Dealing down cards ** 

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

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

481 m = re.search( 

482 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" 

483 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=\*\*\* TURN \*\*\*)|.+))?" 

484 r"(\*\*\* TURN \*\*\* \[\S\S\S? \S\S\S? \S\S\S?](?P<TURN>\[\S\S\S?\].+(?=\*\*\* RIVER \*\*\*)|.+))?" 

485 r"(\*\*\* RIVER \*\*\* \[\S\S\S? \S\S\S? \S\S\S? \S\S\S?](?P<RIVER>\[\S\S\S?\].+))?", 

486 hand.handText, 

487 re.DOTALL, 

488 ) 

489 m2 = self.re_Board1.search(hand.handText) 

490 if m and m2: 

491 if m2.group("FLOP") and not m.group("FLOP"): 

492 m = re.search( 

493 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=Board )|.+)" 

494 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?)?\])?", 

495 hand.handText, 

496 re.DOTALL, 

497 ) 

498 elif m2.group("TURN") and not m.group("TURN"): 

499 m = re.search( 

500 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" 

501 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=Board )|.+))?" 

502 r"(Board \[(?P<BFLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<TURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\])?", 

503 hand.handText, 

504 re.DOTALL, 

505 ) 

506 elif m2.group("RIVER") and not m.group("RIVER"): 

507 m = re.search( 

508 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" 

509 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=\*\*\* TURN \*\*\*)|.+))?" 

510 r"(\*\*\* TURN \*\*\* \[\S\S\S? \S\S\S? \S\S\S?](?P<TURN>\[\S\S\S?\].+(?=Board )|.+))?" 

511 r"(Board \[(?P<BFLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<BTURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\])?", 

512 hand.handText, 

513 re.DOTALL, 

514 ) 

515 elif hand.gametype["base"] in ("stud"): 

516 m = re.search( 

517 r"(?P<ANTES>.+(?=\*\*\* 3rd STREET \*\*\*)|.+)" 

518 r"(\*\*\* 3rd STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4th STREET \*\*\*)|.+))?" 

519 r"(\*\*\* 4th STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5th STREET \*\*\*)|.+))?" 

520 r"(\*\*\* 5th STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6th STREET \*\*\*)|.+))?" 

521 r"(\*\*\* 6th STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* RIVER \*\*\*)|.+))?" 

522 r"(\*\*\* RIVER \*\*\*(?P<SEVENTH>.+))?", 

523 hand.handText, 

524 re.DOTALL, 

525 ) 

526 elif hand.gametype["base"] in ("draw"): 

527 if hand.gametype["category"] in ("27_1draw", "fivedraw"): 

528 m = re.search( 

529 r"(?P<PREDEAL>.+(?=\*\*\* DEALING HANDS \*\*\*)|.+)" 

530 r"(\*\*\* DEALING HANDS \*\*\*(?P<DEAL>.+(?=\*\*\* DRAW \*\*\*)|.+))?" 

531 r"(\*\*\* DRAW \*\*\*(?P<DRAWONE>.+))?", 

532 hand.handText, 

533 re.DOTALL, 

534 ) 

535 else: 

536 m = re.search( 

537 r"(?P<PREDEAL>.+(?=\*\*\* DEALING HANDS \*\*\*)|.+)" 

538 r"(\*\*\* DEALING HANDS \*\*\*(?P<DEAL>.+(?=\*\*\* FIRST DRAW \*\*\*)|.+))?" 

539 r"(\*\*\* FIRST DRAW \*\*\*(?P<DRAWONE>.+(?=\*\*\* SECOND DRAW \*\*\*)|.+))?" 

540 r"(\*\*\* SECOND DRAW \*\*\*(?P<DRAWTWO>.+(?=\*\*\* THIRD DRAW \*\*\*)|.+))?" 

541 r"(\*\*\* THIRD DRAW \*\*\*(?P<DRAWTHREE>.+))?", 

542 hand.handText, 

543 re.DOTALL, 

544 ) 

545 hand.addStreets(m) 

546 # if m3 and m2: 

547 # if m2.group('RIVER') and not m3.group('RIVER'): 

548 # print hand.streets 

549 

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

551 if street in ( 

552 "FLOP", 

553 "TURN", 

554 "RIVER", 

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

556 if self.skin not in ("ActionPoker", "GearPoker"): 

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

558 if m and m.group(street): 

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

560 cards = [c.replace("10", "T") for c in cards] 

561 hand.setCommunityCards(street, cards) 

562 else: 

563 m = self.re_Board2.search(hand.streets[street]) 

564 cards = m.group("CARDS").split(" ") 

565 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards] 

566 hand.setCommunityCards(street, cards) 

567 

568 def readAntes(self, hand): 

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

570 for player in m: 

571 if player.group("ANTE") != "0.00": 

572 pname = self.unknownPlayer(hand, player.group("PNAME")) 

573 hand.addAnte(pname, self.clearMoneyString(player.group("ANTE"))) 

574 

575 def readBringIn(self, hand): 

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

577 if m: 

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

579 hand.addBringIn(m.group("PNAME"), self.clearMoneyString(m.group("BRINGIN"))) 

580 

581 def readBlinds(self, hand): 

582 liveBlind = True 

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

584 pname = self.unknownPlayer(hand, a.group("PNAME")) 

585 sb = self.clearMoneyString(a.group("SB")) 

586 if liveBlind: 

587 hand.addBlind(pname, "small blind", sb) 

588 liveBlind = False 

589 else: 

590 # Post dead blinds as ante 

591 hand.addBlind(pname, "secondsb", sb) 

592 if not hand.gametype["sb"] and self.skin in ("ActionPoker", "GearPoker"): 

593 hand.gametype["sb"] = sb 

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

595 pname = self.unknownPlayer(hand, a.group("PNAME")) 

596 if a.group("BB") is not None: 

597 bb = self.clearMoneyString(a.group("BB")) 

598 elif hand.gametype["bb"]: 

599 bb = hand.gametype["bb"] 

600 else: 

601 raise FpdbHandPartial("BetOnlineToFpdb.readBlinds: " + ("Partial hand history: 'No blind info'")) 

602 hand.addBlind(pname, "big blind", bb) 

603 if not hand.gametype["bb"] and self.skin in ("ActionPoker", "GearPoker"): 

604 hand.gametype["bb"] = bb 

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

606 if a.group("SBBB") != "0.00": 

607 pname = self.unknownPlayer(hand, a.group("PNAME")) 

608 sbbb = self.clearMoneyString(a.group("SBBB")) 

609 amount = str(Decimal(sbbb) + old_div(Decimal(sbbb), 2)) 

610 hand.addBlind(pname, "both", amount) 

611 else: 

612 pname = self.unknownPlayer(hand, a.group("PNAME")) 

613 hand.addBlind(pname, "secondsb", hand.gametype["sb"]) 

614 self.fixBlinds(hand) 

615 

616 def fixBlinds(self, hand): 

617 # FIXME 

618 # The following should only trigger when a small blind is missing in ActionPoker hands, or the sb/bb is ALL_IN 

619 if self.skin in ("ActionPoker", "GearPoker"): 

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

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

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

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

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

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

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

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

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

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

630 raise FpdbParseError 

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

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

633 

634 def unknownPlayer(self, hand, pname): 

635 if pname == "Unknown player" or not pname: 

636 if not pname: 

637 pname = "Dead" 

638 if pname not in (p[1] for p in hand.players): 

639 hand.addPlayer(-1, pname, "0") 

640 return pname 

641 

642 def readHoleCards(self, hand): 

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

644 # we need to grab hero's cards 

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

646 if street in list(hand.streets.keys()): 

647 m = self.re_HeroCards.finditer(hand.streets[street]) 

648 for found in m: 

649 # if m == None: 

650 # hand.involved = False 

651 # else: 

652 hand.hero = found.group("PNAME") 

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

654 newcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in newcards] 

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_HeroCards.finditer(hand.streets[street]) 

661 for found in m: 

662 player = found.group("PNAME") 

663 if found.group("NEWCARDS") is None: 

664 newcards = [] 

665 else: 

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

667 newcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in newcards] 

668 if found.group("OLDCARDS") is None: 

669 oldcards = [] 

670 else: 

671 oldcards = found.group("OLDCARDS").split(" ") 

672 oldcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in oldcards] 

673 if street == "THIRD" and len(newcards) == 3: # hero in stud game 

674 hand.hero = player 

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

676 hand.addHoleCards( 

677 street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False 

678 ) 

679 else: 

680 hand.addHoleCards( 

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

682 ) 

683 

684 def readAction(self, hand, street): 

685 if street == "PREFLOP": 

686 m0 = self.re_Action.finditer(self.re_Hole.split(hand.handText)[0]) 

687 for action in m0: 

688 pname = self.unknownPlayer(hand, action.group("PNAME")) 

689 if action.group("ATYPE") == " has left the table": 

690 if pname in (p[1] for p in hand.players): 

691 hand.addFold(street, pname) 

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

693 for action in m: 

694 # acts = action.groupdict() 

695 # print "DEBUG: street: %s acts: %s" % (street, acts) 

696 pname = self.unknownPlayer(hand, action.group("PNAME")) 

697 if action.group("ATYPE") in (" folds", " Folds", " has left the table"): 

698 if pname in (p[1] for p in hand.players): 

699 hand.addFold(street, pname) 

700 elif action.group("ATYPE") in (" checks", " Checks"): 

701 hand.addCheck(street, pname) 

702 elif action.group("ATYPE") in (" calls", " Calls"): 

703 hand.addCall(street, pname, self.clearMoneyString(action.group("BET"))) 

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

705 hand.addCallandRaise(street, pname, self.clearMoneyString(action.group("BET"))) 

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

707 hand.addBet(street, pname, self.clearMoneyString(action.group("BET"))) 

708 elif action.group("ATYPE") == " discards": 

709 hand.addDiscard(street, pname, action.group("BET"), action.group("CARDS")) 

710 elif action.group("ATYPE") == " stands pat": 

711 hand.addStandsPat(street, pname, action.group("CARDS")) 

712 else: 

713 log.debug( 

714 ("DEBUG:") 

715 + " " 

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

717 ) 

718 

719 def readShowdownActions(self, hand): 

720 # TODO: pick up mucks also?? 

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

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

723 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards] 

724 hand.addShownCards(cards, shows.group("PNAME")) 

725 

726 def readCollectPot(self, hand): 

727 hand.adjustCollected = True 

728 for m in self.re_CollectPot.finditer(hand.handText): 

729 hand.addCollectPot(player=m.group("PNAME"), pot=m.group("POT")) 

730 for m in self.re_TotalPot.finditer(hand.handText): 

731 if hand.rakes.get("pot"): 

732 hand.rakes["pot"] += Decimal(self.clearMoneyString(m.group("POT"))) 

733 else: 

734 hand.rakes["pot"] = Decimal(self.clearMoneyString(m.group("POT"))) 

735 

736 def readShownCards(self, hand): 

737 for m in self.re_ShownCards.finditer(hand.handText): 

738 if m.group("CARDS") is not None: 

739 pname = self.unknownPlayer(hand, m.group("PNAME")) 

740 cards = m.group("CARDS") 

741 cards = cards.split(" ") # needs to be a list, not a set--stud needs the order 

742 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards if len(c) > 0] 

743 (shown, mucked) = (False, False) 

744 if m.group("SHOWED") == "showed": 

745 shown = True 

746 elif m.group("SHOWED") == "mucked": 

747 mucked = True 

748 if hand.gametype["category"] == "holdem" and len(cards) > 2: 

749 continue 

750 # print "DEBUG: hand.addShownCards(%s, %s, %s, %s)" %(cards, m.group('PNAME'), shown, mucked) 

751 hand.addShownCards(cards=cards, player=pname, shown=shown, mucked=mucked, string=None) 

752 

753 @staticmethod 

754 def getTableTitleRe(type, table_name=None, tournament=None, table_number=None): 

755 """Returns string to search in windows titles""" 

756 if type == "tour": 

757 return r"\(" + re.escape(str(tournament)) + r"\-" + re.escape(str(table_number)) + r"\)" 

758 else: 

759 return re.escape(table_name)