Coverage for GGPokerToFpdb.py: 0%

395 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, Carl Gherardi 

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# GGpoker HH Format 

33log = logging.getLogger("parser") 

34 

35 

36class GGPoker(HandHistoryConverter): 

37 # Class Variables 

38 

39 sitename = "GGPoker" 

40 filetype = "text" 

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

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

43 sym = { 

44 "USD": "\$", 

45 "T$": "", 

46 "play": "", 

47 } # ADD Euro, Sterling, etc HERE 

48 substitutions = { 

49 "LEGAL_ISO": "USD|CNY", # legal ISO currency codes 

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

51 "PLYR": r"\s?(?P<PNAME>.+?)", 

52 "CUR": "(\$|\¥|)", 

53 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button blind\) |\(button\) \(small blind\) |\(small blind/button\) |\(button\) \(big blind\) )?", 

54 } 

55 

56 # translations from captured groups to fpdb info strings 

57 Lim_Blinds = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

85 "50": ("10.00", "25.00"), 

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

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

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

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

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

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

92 "150.00": ("50.00", "75.00"), 

93 "150": ("50.00", "75.00"), 

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

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

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

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

98 "500.00": ("100.00", "250.00"), 

99 "500": ("100.00", "250.00"), 

100 "600.00": ("150.00", "300.00"), 

101 "600": ("150.00", "300.00"), 

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

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

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

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

106 "2000.00": ("500.00", "1000.00"), 

107 "2000": ("500.00", "1000.00"), 

108 "4000.00": ("1000.00", "2000.00"), 

109 "4000": ("1000.00", "2000.00"), 

110 "10000.00": ("2500.00", "5000.00"), 

111 "10000": ("2500.00", "5000.00"), 

112 "20000.00": ("5000.00", "10000.00"), 

113 "20000": ("5000.00", "10000.00"), 

114 "40000.00": ("10000.00", "20000.00"), 

115 "40000": ("10000.00", "20000.00"), 

116 } 

117 

118 limits = {"No Limit": "nl", "Pot Limit": "pl", "Fixed Limit": "fl", "Limit": "fl", "(NL postflop)": "pn"} 

119 games = { # base, category 

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

121 "ShortDeck": ("hold", "6_holdem"), 

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

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

124 "PLO": ("hold", "omahahi"), 

125 "PLO-5": ("hold", "5_omahahi"), 

126 "PLO-6": ("hold", "6_omahahi"), 

127 } 

128 # mixes = { 

129 # 'HORSE': 'horse', 

130 # '8-Game': '8game', 

131 # '8-GAME': '8game', 

132 # 'HOSE': 'hose', 

133 # 'Mixed PLH/PLO': 'plh_plo', 

134 # 'Mixed NLH/PLO': 'nlh_plo', 

135 # 'Mixed Omaha H/L': 'plo_lo', 

136 # 'Mixed Hold\'em': 'mholdem', 

137 # 'Mixed Omaha': 'momaha', 

138 # 'Triple Stud': '3stud' 

139 # } # Legal mixed games 

140 currencies = {"$": "USD", "": "T$", "¥": "CNY"} 

141 

142 # Poker Hand #TM316262814: Tournament #9364957, WSOP #77: $5,000 No Limit Hold'em Main Event [Flight W], $25M GTD Hold'em No Limit - Level10 (1,000/2,000) - 2020/08/30 15:49:08 

143 # Poker Hand #TM247771977: Tournament #5259595, Daily Special $250 Hold'em No Limit - Level2 (60/120) - 2020/06/02 19:17:33 

144 # Static regexes 

145 re_GameInfo = re.compile( 

146 """ 

147 Poker\sHand\s\#[A-Z]{0,2}(?P<HID>[0-9]+):\s+ 

148 (\{.*\}\s+)?((?P<TOUR>((Zoom|Rush)\s)?(Tournament))\s\# # open paren of tournament info 

149 (?P<TOURNO>\d+),\s 

150 # here's how I plan to use LS 

151 (?P<TOURNAME>.+?)\s 

152 )? 

153 # close paren of tournament info 

154 (?P<GAME>Hold\'em|Hold\'em|ShortDeck|Omaha|PLO|Omaha\sHi/Lo|PLO\-(5|6))\s 

155 (?P<LIMIT>No\sLimit|Fixed\sLimit|Limit|Pot\sLimit|\(NL\spostflop\))?,?\s* 

156 (-\s)? 

157 (?P<SHOOTOUT>Match.*,\s)? 

158 (Level(?P<LEVEL>[IVXLC\d]+)\s?)? 

159 \(? # open paren of the stakes 

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

161 (ante\s\d+,\s)? 

162 ((?P<SB>[,.0-9]+)/(%(LS)s)?(?P<BB>[,.0-9]+)|(?P<BUB>[,.0-9]+)) 

163 (?P<CAP>\s-\s[%(LS)s]?(?P<CAPAMT>[,.0-9]+)\sCap\s-\s)? # Optional Cap part 

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

165 \) # close paren of the stakes 

166 (?P<BLAH2>\s\[AAMS\sID:\s[A-Z0-9]+\])? # AAMS ID: in .it HH's 

167 \s-\s 

168 (?P<DATETIME>.*$) 

169 """ 

170 % substitutions, 

171 re.MULTILINE | re.VERBOSE, 

172 ) 

173 

174 re_PlayerInfo = re.compile( 

175 """ 

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

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

178 \((%(LS)s)?(?P<CASH>[,.0-9]+)\sin\schips 

179 (,\s(%(LS)s)?(?P<BOUNTY>[,.0-9]+)\sbounty)? 

180 \) 

181 (?P<SITOUT>\sis\ssitting\sout)?""" 

182 % substitutions, 

183 re.MULTILINE | re.VERBOSE, 

184 ) 

185 

186 re_HandInfo = re.compile( 

187 """ 

188 ^\s?Table\s(ID\s)?\'(?P<TABLE>.+?)\'\s 

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

190 (?P<PLAY>\(Play\sMoney\)\s)? 

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

192 re.MULTILINE | re.VERBOSE, 

193 ) 

194 

195 re_Identify = re.compile("Poker\sHand\s\#[A-Z]{0,2}?\d+:") 

196 re_SplitHands = re.compile("(?:\s?\n){2,}") 

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

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

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

200 re_Board2 = re.compile(r"\[(?P<C1>\S\S)\] \[(\S\S)?(?P<C2>\S\S) (?P<C3>\S\S)\]") 

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

202 re_DateTime1 = re.compile( 

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

204 re.MULTILINE, 

205 ) 

206 re_DateTime2 = re.compile( 

207 """(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+)""", re.MULTILINE 

208 ) 

209 # revised re including timezone (not currently used): 

210 # re_DateTime = re.compile("""(?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]+) \(?(?P<TZ>[A-Z0-9]+)""", re.MULTILINE) 

211 

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

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

214 re_PostSB = re.compile(r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[,.0-9]+)" % substitutions, re.MULTILINE) 

215 re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[,.0-9]+)" % substitutions, re.MULTILINE) 

216 re_PostBUB = re.compile(r"^%(PLYR)s: posts button blind %(CUR)s(?P<BUB>[,.0-9]+)" % substitutions, re.MULTILINE) 

217 re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P<ANTE>[,.0-9]+)" % substitutions, re.MULTILINE) 

218 re_BringIn = re.compile( 

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

220 ) 

221 re_PostMissed = re.compile(r"^%(PLYR)s: posts missed blind %(CUR)s(?P<SBBB>[,.0-9]+)" % substitutions, re.MULTILINE) 

222 re_PostStraddle = re.compile(r"^%(PLYR)s: straddle %(CUR)s(?P<STRADDLE>[,.0-9]+)" % substitutions, re.MULTILINE) 

223 re_Action = re.compile( 

224 r""" 

225 ^%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat|\sChooses\sto\sEV\sCashout|\sReceives\sCashout) 

226 (\s%(CUR)s(?P<BET>[,.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[,.\d]+))? # the number discarded goes in <BET> 

227 \s*(and\sis\sall.in)? 

228 (and\shas\sreached\sthe\s[%(CUR)s\d\.,]+\scap)? 

229 (\son|\scards?)? 

230 (\s\(disconnect\))? 

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

232 % substitutions, 

233 re.MULTILINE | re.VERBOSE, 

234 ) 

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

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

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

238 re_CollectPot = re.compile( 

239 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(collected|showed \[.*\] and (won|collected)) \(?%(CUR)s(?P<POT>[,.\d]+)\)?(, mucked| with.*|)" 

240 % substitutions, 

241 re.MULTILINE, 

242 ) 

243 # Vinsand88 cashed out the hand for $2.19 | Cash Out Fee $0.02 

244 re_CollectPot2 = re.compile(r"^%(PLYR)s collected %(CUR)s(?P<POT>[,.\d]+)" % substitutions, re.MULTILINE) 

245 re_CollectPot3 = re.compile( 

246 r"^%(PLYR)s: Receives Cashout \(%(CUR)s(?P<POT>[,.\d]+)\)" % substitutions, re.MULTILINE 

247 ) 

248 re_CollectPot4 = re.compile( 

249 r"^%(PLYR)s: Pays Cashout Risk \(%(CUR)s(?P<POT>[,.\d]+)\)" % substitutions, re.MULTILINE 

250 ) 

251 re_CashedOut = re.compile(r"(Chooses\sto\sEV\sCashout|Receives\sCashout)") 

252 re_WinningRankOne = re.compile( 

253 "^%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[,\.0-9]+) - congratulations!$" % substitutions, 

254 re.MULTILINE, 

255 ) 

256 re_WinningRankOther = re.compile( 

257 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place and received %(CUR)s(?P<AMT>[,.0-9]+)\.$" 

258 % substitutions, 

259 re.MULTILINE, 

260 ) 

261 re_RankOther = re.compile( 

262 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place$" % substitutions, re.MULTILINE 

263 ) 

264 re_Cancelled = re.compile("Hand\scancelled", re.MULTILINE) 

265 re_Uncalled = re.compile("Uncalled bet \(%(CUR)s(?P<BET>[,.\d]+)\) returned to" % substitutions, re.MULTILINE) 

266 # APTEM-89 wins the $0.27 bounty for eliminating Hero 

267 # ChazDazzle wins the 22000 bounty for eliminating berkovich609 

268 # JKuzja, vecenta split the $50 bounty for eliminating ODYSSES 

269 re_Bounty = re.compile( 

270 "^%(PLYR)s (?P<SPLIT>split|wins) the %(CUR)s(?P<AMT>[,\.0-9]+) bounty for eliminating (?P<ELIMINATED>.+?)$" 

271 % substitutions, 

272 re.MULTILINE, 

273 ) 

274 # Amsterdam71 wins $19.90 for eliminating MuKoJla and their own bounty increases by $19.89 to $155.32 

275 # Amsterdam71 wins $4.60 for splitting the elimination of Frimble11 and their own bounty increases by $4.59 to $41.32 

276 # Amsterdam71 wins the tournament and receives $230.36 - congratulations! 

277 re_Progressive = re.compile( 

278 """ 

279 ^%(PLYR)s\swins\s%(CUR)s(?P<AMT>[,\.0-9]+)\s 

280 for\s(splitting\sthe\selimination\sof|eliminating)\s(?P<ELIMINATED>.+?)\s 

281 and\stheir\sown\sbounty\sincreases\sby\s%(CUR)s(?P<INCREASE>[\.0-9]+)\sto\s%(CUR)s(?P<ENDAMT>[\.0-9]+)$""" 

282 % substitutions, 

283 re.MULTILINE | re.VERBOSE, 

284 ) 

285 re_Rake = re.compile( 

286 """ 

287 Total\spot\s%(CUR)s(?P<POT>[,\.0-9]+)(.+?)?\s\|\sRake\s%(CUR)s(?P<RAKE>[,\.0-9]+)""" 

288 % substitutions, 

289 re.MULTILINE | re.VERBOSE, 

290 ) 

291 

292 re_STP = re.compile( 

293 """ 

294 Cash\sDrop\sto\sPot\s:\stotal\s%(CUR)s(?P<AMOUNT>[,\.0-9]+)""" 

295 % substitutions, 

296 re.MULTILINE | re.VERBOSE, 

297 ) 

298 

299 def compilePlayerRegexs(self, hand): 

300 players = set([player[1] for player in hand.players]) 

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

302 self.compiledPlayers = players 

303 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" 

304 subst = { 

305 "PLYR": player_re, 

306 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) |\(button\) \(big blind\) )?", 

307 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|)", 

308 } 

309 self.re_HeroCards = re.compile( 

310 r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE 

311 ) 

312 self.re_ShownCards = re.compile( 

313 "^Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and (lost|(won|collected) \(%(CUR)s(?P<POT>[,\.\d]+)\)) with (?P<STRING>.+?)(,\sand\s(won\s\(%(CUR)s[\.\d]+\)|lost)\swith\s(?P<STRING2>.*))?)?$" 

314 % subst, 

315 re.MULTILINE, 

316 ) 

317 

318 def readSupportedGames(self): 

319 return [ 

320 ["ring", "hold", "nl"], 

321 ["ring", "hold", "pl"], 

322 ["ring", "hold", "fl"], 

323 ["ring", "hold", "pn"], 

324 ["ring", "stud", "fl"], 

325 ["ring", "draw", "fl"], 

326 ["ring", "draw", "pl"], 

327 ["ring", "draw", "nl"], 

328 ["tour", "hold", "nl"], 

329 ["tour", "hold", "pl"], 

330 ["tour", "hold", "fl"], 

331 ["tour", "hold", "pn"], 

332 ["tour", "stud", "fl"], 

333 ["tour", "draw", "fl"], 

334 ["tour", "draw", "pl"], 

335 ["tour", "draw", "nl"], 

336 ] 

337 

338 def determineGameType(self, handText): 

339 info = {} 

340 m = self.re_GameInfo.search(handText) 

341 if not m: 

342 tmp = handText[0:200] 

343 log.error(("GGPokerToFpdb.determineGameType: '%s'") % tmp) 

344 raise FpdbParseError 

345 

346 mg = m.groupdict() 

347 if "LIMIT" in mg and mg["LIMIT"] is not None: 

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

349 else: 

350 info["limitType"] = "pl" 

351 if "GAME" in mg: 

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

353 if "SB" in mg and mg["SB"] is not None: 

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

355 if "BB" in mg and mg["BB"] is not None: 

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

357 if "BUB" in mg and mg["BUB"] is not None: 

358 info["sb"] = "0" 

359 info["bb"] = self.clearMoneyString(mg["BUB"]) 

360 if "CURRENCY" in mg: 

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

362 if "CAP" in mg and mg["CAP"] is not None: 

363 info["buyinType"] = "cap" 

364 else: 

365 info["buyinType"] = "regular" 

366 if "SPLIT" in mg and mg["SPLIT"] == "Split": 

367 info["split"] = True 

368 else: 

369 info["split"] = False 

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

371 info["type"] = "ring" 

372 else: 

373 info["type"] = "tour" 

374 if "ZOOM" in mg["TOUR"]: 

375 info["fast"] = True 

376 

377 if info.get("currency") in ("T$", None) and info["type"] == "ring": 

378 info["currency"] = "play" 

379 

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

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

382 try: 

383 info["sb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][0] 

384 info["bb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][1] 

385 except KeyError: 

386 tmp = handText[0:200] 

387 log.error( 

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

389 ) 

390 raise FpdbParseError 

391 else: 

392 info["sb"] = str((Decimal(self.clearMoneyString(mg["SB"])) / 2).quantize(Decimal("0.01"))) 

393 info["bb"] = str(Decimal(self.clearMoneyString(mg["SB"])).quantize(Decimal("0.01"))) 

394 

395 return info 

396 

397 def readHandInfo(self, hand): 

398 # First check if partial 

399 if hand.handText.count("*** SUMMARY ***") != 1: 

400 raise FpdbHandPartial(("Hand is not cleanly split into pre and post Summary")) 

401 

402 info = {} 

403 m = self.re_HandInfo.search(hand.handText, re.DOTALL) 

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

405 if m is None or m2 is None: 

406 tmp = hand.handText[0:200] 

407 log.error(("GGPokerToFpdb.readHandInfo: '%s'") % tmp) 

408 raise FpdbParseError 

409 

410 info.update(m.groupdict()) 

411 info.update(m2.groupdict()) 

412 

413 # log.debug("readHandInfo: %s" % info) 

414 for key in info: 

415 if key == "DATETIME": 

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

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

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

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

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

421 for a in m1: 

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

423 a.group("Y"), 

424 a.group("M"), 

425 a.group("D"), 

426 a.group("H"), 

427 a.group("MIN"), 

428 a.group("S"), 

429 ) 

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

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

432 hand.startTime = datetime.datetime.strptime( 

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

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

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

436 

437 if key == "HID": 

438 hand.handid = info[key] 

439 if key == "TOURNO": 

440 hand.tourNo = info[key] 

441 if key == "BUYIN": 

442 if hand.tourNo is not None: 

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

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

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

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

447 if info[key].strip() == "Freeroll": 

448 hand.buyin = 0 

449 hand.fee = 0 

450 hand.buyinCurrency = "FREE" 

451 elif info[key].strip() == "": 

452 hand.buyin = 0 

453 hand.fee = 0 

454 hand.buyinCurrency = "NA" 

455 else: 

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

457 hand.buyinCurrency = "USD" 

458 elif info[key].find("£") != -1: 

459 hand.buyinCurrency = "GBP" 

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

461 hand.buyinCurrency = "EUR" 

462 elif info[key].find("₹") != -1: 

463 hand.buyinCurrency = "INR" 

464 elif info[key].find("¥") != -1: 

465 hand.buyinCurrency = "CNY" 

466 elif info[key].find("FPP") != -1: 

467 hand.buyinCurrency = "PSFP" 

468 elif info[key].find("SC") != -1: 

469 hand.buyinCurrency = "PSFP" 

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

471 hand.buyinCurrency = "play" 

472 else: 

473 # FIXME: handle other currencies, play money 

474 log.error( 

475 ("GGPokerToFpdb.readHandInfo: Failed to detect currency.") 

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

477 ) 

478 raise FpdbParseError 

479 

480 info["BIAMT"] = info["BIAMT"].strip("$€£FPPSC₹") 

481 

482 if hand.buyinCurrency != "PSFP": 

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

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

485 tmp = info["BOUNTY"] 

486 info["BOUNTY"] = info["BIRAKE"] 

487 info["BIRAKE"] = tmp 

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

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

490 hand.isKO = True 

491 else: 

492 hand.isKO = False 

493 

494 info["BIRAKE"] = info["BIRAKE"].strip("$€£₹") 

495 

496 hand.buyin = int(100 * Decimal(info["BIAMT"])) + hand.koBounty 

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

498 else: 

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

500 hand.fee = 0 

501 if key == "LEVEL": 

502 hand.level = info[key] 

503 if key == "SHOOTOUT" and info[key] is not None: 

504 hand.isShootout = True 

505 if key == "TABLE": 

506 tablesplit = re.split(" ", info[key]) 

507 if hand.tourNo is not None and len(tablesplit) > 1: 

508 hand.tablename = tablesplit[1] 

509 else: 

510 hand.tablename = info[key] 

511 if key == "TOURNAME": 

512 hand.tourneyName = info[key] 

513 if key == "BUTTON": 

514 hand.buttonpos = info[key] 

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

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

517 

518 if "Zoom" in self.in_path or "Rush" in self.in_path: 

519 (hand.gametype["fast"], hand.isFast) = (True, True) 

520 

521 if self.re_Cancelled.search(hand.handText): 

522 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid) 

523 

524 def readButton(self, hand): 

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

526 if m: 

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

528 else: 

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

530 

531 def readPlayerStacks(self, hand): 

532 pre, post = hand.handText.split("*** SUMMARY ***") 

533 m = self.re_PlayerInfo.finditer(pre) 

534 for a in m: 

535 hand.addPlayer( 

536 int(a.group("SEAT")), 

537 a.group("PNAME"), 

538 self.clearMoneyString(a.group("CASH")), 

539 None, 

540 a.group("SITOUT"), 

541 self.clearMoneyString(a.group("BOUNTY")), 

542 ) 

543 

544 def markStreets(self, hand): 

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

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

547 # in consequence the mucked-display is incorrect. 

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

549 

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

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

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

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

554 # handText was not split, no DRAW street occurred 

555 pass 

556 else: 

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

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

559 hand.handText = "" 

560 for i in discard_split: 

561 hand.handText += i 

562 

563 # PREFLOP = ** Dealing down cards ** 

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

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

566 m = re.search( 

567 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>(.+(?P<FLOPET>\[\S\S\]))?.+(?=\*\*\* (FIRST\s)?FLOP \*\*\*)|.+)" 

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

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

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

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

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

573 r"(\*\*\* FIRST RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER1>\[\S\S\].+?(?=\*\*\* SECOND (FLOP|TURN|RIVER) \*\*\*)|.+))?" 

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

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

576 r"(\*\*\* SECOND RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER2>\[\S\S\].+?(?=\*\*\* THIRD (FLOP|TURN|RIVER) \*\*\*)|.+))?" 

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

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

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

580 hand.handText, 

581 re.DOTALL, 

582 ) 

583 hand.addStreets(m) 

584 if "AFO" in hand.tablename: 

585 m1 = self.re_Board3.search(hand.handText) 

586 if m1: 

587 hand.streets.update( 

588 { 

589 "FLOP": "[%s] %s" % (m1.group("FLOP"), hand.streets["FLOP"]), 

590 "TURN": "[%s]" % m1.group("TURN"), 

591 "RIVER": "[%s]" % m1.group("RIVER"), 

592 } 

593 ) 

594 

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

596 if ( 

597 street != "FLOPET" or hand.streets.get("FLOP") is None 

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

599 m = self.re_Board.search(hand.streets[street]) 

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

601 if street in ("FLOP3", "TURN3", "RIVER3"): 

602 hand.runItTimes = 3 

603 elif street in ("FLOP2", "TURN2", "RIVER2"): 

604 hand.runItTimes = 2 

605 

606 def readSTP(self, hand): 

607 m = self.re_STP.search(hand.handText) 

608 if m: 

609 hand.addSTP(m.group("AMOUNT")) 

610 

611 def readAntes(self, hand): 

612 log.debug(("reading antes")) 

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

614 for player in m: 

615 # ~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE'))) 

616 hand.addAnte(player.group("PNAME"), self.clearMoneyString(player.group("ANTE"))) 

617 

618 def readBringIn(self, hand): 

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

620 if m: 

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

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

623 

624 def readBlinds(self, hand): 

625 liveBlind, straddles = True, {} 

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

627 if liveBlind: 

628 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB"))) 

629 liveBlind = False 

630 else: 

631 names = [p[1] for p in hand.players] 

632 if "Big Blind" in names or "Small Blind" in names or "Dealer" in names: 

633 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB"))) 

634 else: 

635 # Post dead blinds as ante 

636 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SB"))) 

637 for a in self.re_PostMissed.finditer(hand.handText): 

638 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SBBB"))) 

639 for a in self.re_PostStraddle.finditer(hand.handText): 

640 if straddles.get(a.group("PNAME")) is None: 

641 straddles[a.group("PNAME")] = self.clearMoneyString(a.group("STRADDLE")) 

642 elif Decimal(straddles[a.group("PNAME")]) < Decimal(self.clearMoneyString(a.group("STRADDLE"))): 

643 straddles[a.group("PNAME")] = self.clearMoneyString(a.group("STRADDLE")) 

644 for p, amount in list(straddles.items()): 

645 hand.addBlind(p, "straddle", amount) 

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

647 if straddles.get(a.group("PNAME")) is None: 

648 hand.addBlind(a.group("PNAME"), "big blind", self.clearMoneyString(a.group("BB"))) 

649 for a in self.re_PostBUB.finditer(hand.handText): 

650 hand.addBlind(a.group("PNAME"), "button blind", self.clearMoneyString(a.group("BUB"))) 

651 

652 def readHoleCards(self, hand): 

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

654 # we need to grab hero's cards 

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

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

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

658 for found in m: 

659 # if m == None: 

660 # hand.involved = False 

661 # else: 

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

663 if "cards" not in found.group("NEWCARDS"): 

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

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

666 

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

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

669 continue # already done these 

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

671 for found in m: 

672 player = found.group("PNAME") 

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

674 newcards = [] 

675 else: 

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

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

678 oldcards = [] 

679 else: 

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

681 

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

683 hand.hero = player 

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

685 hand.addHoleCards( 

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

687 ) 

688 else: 

689 hand.addHoleCards( 

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

691 ) 

692 

693 def readAction(self, hand, street): 

694 if hand.gametype["split"] and street in hand.communityStreets: 

695 s = street + "2" 

696 else: 

697 s = street 

698 if not hand.streets[s]: 

699 return 

700 m = self.re_Action.finditer(hand.streets[s]) 

701 for action in m: 

702 # acts = action.groupdict() 

703 # log.error("DEBUG: %s acts: %s" % (street, acts)) 

704 if action.group("ATYPE") == " folds": 

705 hand.addFold(street, action.group("PNAME")) 

706 elif action.group("ATYPE") == " checks": 

707 hand.addCheck(street, action.group("PNAME")) 

708 elif action.group("ATYPE") == " Chooses to EV Cashout" or action.group("ATYPE") == " Receives Cashout": 

709 hand.addCashout(street, action.group("PNAME")) 

710 elif action.group("ATYPE") == " calls": 

711 hand.addCall(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

712 elif action.group("ATYPE") == " raises": 

713 if action.group("BETTO") is not None: 

714 hand.addRaiseTo(street, action.group("PNAME"), self.clearMoneyString(action.group("BETTO"))) 

715 elif action.group("BET") is not None: 

716 hand.addCallandRaise(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

717 elif action.group("ATYPE") == " bets": 

718 hand.addBet(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

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

720 hand.addDiscard(street, action.group("PNAME"), action.group("BET"), action.group("CARDS")) 

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

722 hand.addStandsPat(street, action.group("PNAME"), action.group("CARDS")) 

723 else: 

724 log.debug( 

725 ("DEBUG:") 

726 + " " 

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

728 ) 

729 

730 def readShowdownActions(self, hand): 

731 # TODO: pick up mucks also?? 

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

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

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

735 

736 def readTourneyResults(self, hand): 

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

738 if self.re_Bounty.search(hand.handText) is None: 

739 koAmounts = {} 

740 winner = None 

741 # %(PLYR)s wins %(CUR)s(?P<AMT>[\.0-9]+) for eliminating (?P<ELIMINATED>.+?) and their own bounty increases by %(CUR)s(?P<INCREASE>[\.0-9]+) to %(CUR)s(?P<ENDAMT>[\.0-9]+) 

742 # re_WinningRankOne = re.compile(u"^%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[\.0-9]+) - congratulations!$" % substitutions, re.MULTILINE) 

743 for a in self.re_Progressive.finditer(hand.handText): 

744 if a.group("PNAME") not in koAmounts: 

745 koAmounts[a.group("PNAME")] = 0 

746 koAmounts[a.group("PNAME")] += 100 * Decimal(a.group("AMT")) 

747 hand.endBounty[a.group("PNAME")] = 100 * Decimal(a.group("ENDAMT")) 

748 hand.isProgressive = True 

749 

750 m = self.re_WinningRankOne.search(hand.handText) 

751 if m: 

752 winner = m.group("PNAME") 

753 

754 if hand.koBounty > 0: 

755 for pname, amount in koAmounts.iteritems(): 

756 if pname == winner: 

757 # end = amount + hand.endBounty[pname] 

758 hand.koCounts[pname] = (amount + hand.endBounty[pname]) / Decimal(hand.koBounty) 

759 else: 

760 # end = 0 

761 hand.koCounts[pname] = amount / Decimal(hand.koBounty) 

762 else: 

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

764 if a.group("SPLIT") == "split": 

765 pnames = a.group("PNAME").split(", ") 

766 for pname in pnames: 

767 if pname not in hand.koCounts: 

768 hand.koCounts[pname] = 0 

769 hand.koCounts[pname] += 1 / Decimal(len(pnames)) 

770 else: 

771 if a.group("PNAME") not in hand.koCounts: 

772 hand.koCounts[a.group("PNAME")] = 0 

773 hand.koCounts[a.group("PNAME")] += 1 

774 

775 def readCollectPot(self, hand): 

776 # Bovada walks are calculated incorrectly in converted PokerStars hands 

777 # acts, bovadaUncalled_v1, bovadaUncalled_v2, blindsantes, adjustment = ( 

778 # hand.actions.get("PREFLOP"), 

779 # False, 

780 # False, 

781 # 0, 

782 # 0, 

783 # ) 

784 # names = [p[1] for p in hand.players] 

785 i = 0 

786 pre, post = hand.handText.split("*** SUMMARY ***") 

787 hand.cashedOut = self.re_CashedOut.search(pre) is not None 

788 if hand.runItTimes == 0 and hand.cashedOut is False: 

789 for m in self.re_CollectPot.finditer(post): 

790 pot = self.clearMoneyString(m.group("POT")) 

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

792 i += 1 

793 if i == 0: 

794 for m in self.re_CollectPot2.finditer(pre): 

795 pot = self.clearMoneyString(m.group("POT")) 

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

797 if hand.cashedOut: 

798 for m in self.re_CollectPot3.finditer(pre): 

799 pot = self.clearMoneyString(m.group("POT")) 

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

801 for m in self.re_CollectPot4.finditer(pre): 

802 pot = "-" + self.clearMoneyString(m.group("POT")) 

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

804 

805 def readShownCards(self, hand): 

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

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

808 cards = m.group("CARDS") 

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

810 string = m.group("STRING") 

811 if m.group("STRING2"): 

812 string += "|" + m.group("STRING2") 

813 

814 (shown, mucked) = (False, False) 

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

816 shown = True 

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

818 mucked = True 

819 

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

821 hand.addShownCards(cards=cards, player=m.group("PNAME"), shown=shown, mucked=mucked, string=string) 

822 

823 @staticmethod 

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

825 "Returns string to search in windows titles" 

826 regex = re.escape(str(table_name)) 

827 if type == "tour": 

828 regex = re.escape(str(tournament)) + ".* (Table|Tisch) " + re.escape(str(table_number)) 

829 log.info( 

830 "Stars.getTableTitleRe: table_name='%s' tournament='%s' table_number='%s'" 

831 % (table_name, tournament, table_number) 

832 ) 

833 log.info("Stars.getTableTitleRe: returns: '%s'" % (regex)) 

834 return regex