Coverage for PokerStarsToFpdb.py: 0%

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

27import re 

28import logging 

29import datetime 

30 

31# PokerStars HH Format 

32log = logging.getLogger("parser") 

33 

34 

35class PokerStars(HandHistoryConverter): 

36 # Class Variables 

37 

38 sitename = "PokerStars" 

39 filetype = "text" 

40 codepage = ("utf8", "cp1252", "ISO-8859-1") 

41 siteId = 2 # Needs to match id entry in Sites database 

42 sym = { 

43 "USD": "\$", 

44 "CAD": "\$", 

45 "T$": "", 

46 "EUR": "\xe2\x82\xac", 

47 "GBP": "\£", 

48 "play": "", 

49 "INR": "\₹", 

50 "CNY": "\¥", 

51 } # ADD Euro, Sterling, etc HERE 

52 substitutions = { 

53 "LEGAL_ISO": "USD|EUR|GBP|CAD|FPP|SC|INR|CNY", # legal ISO currency codes 

54 "LS": r"\$|\xe2\x82\xac|\u20ac|\£|\u20b9|\¥|Rs\.\s|", # legal currency symbols - Euro(cp1252, utf-8) 

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

56 "CUR": r"(\$|\xe2\x82\xac|\u20ac||\£|\u20b9|\¥|Rs\.\s|)", 

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

58 } 

59 

60 # translations from captured groups to fpdb info strings 

61 Lim_Blinds = { 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

120 } 

121 

122 limits = { 

123 "No Limit": "nl", 

124 "NO LIMIT": "nl", 

125 "Pot Limit": "pl", 

126 "POT LIMIT": "pl", 

127 "Fixed Limit": "fl", 

128 "Limit": "fl", 

129 "LIMIT": "fl", 

130 "Pot Limit Pre-Flop, No Limit Post-Flop": "pn", 

131 } 

132 games = { # base, category 

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

134 "HOLD'EM": ("hold", "holdem"), 

135 "6+ Hold'em": ("hold", "6_holdem"), 

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

137 "Fusion": ("hold", "fusion"), 

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

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

140 "OMAHA HI/LO": ("hold", "omahahilo"), 

141 "5 Card Omaha": ("hold", "5_omahahi"), 

142 "Omaha 5 Cards": ("hold", "5_omahahi"), 

143 "5 Card Omaha Hi/Lo": ("hold", "5_omaha8"), 

144 "6 Card Omaha": ("hold", "6_omahahi"), 

145 "6 Card Omaha Hi/Lo": ("hold", "6_omaha8"), 

146 "Omaha 6 Cards": ("hold", "6_omahahi"), 

147 "Courchevel": ("hold", "cour_hi"), 

148 "Courchevel Hi/Lo": ("hold", "cour_hilo"), 

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

150 "RAZZ": ("stud", "razz"), 

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

152 "7 CARD STUD": ("stud", "studhi"), 

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

154 "7 CARD STUD HI/LO": ("stud", "studhilo"), 

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

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

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

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

159 } 

160 mixes = { 

161 "HORSE": "horse", 

162 "8-Game": "8game", 

163 "8-GAME": "8game", 

164 "HOSE": "hose", 

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

166 "Mixed NLH/PLO": "nlh_plo", 

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

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

169 "Mixed Omaha": "momaha", 

170 "Triple Stud": "3stud", 

171 } # Legal mixed games 

172 currencies = {"€": "EUR", "$": "USD", "": "T$", "£": "GBP", "¥": "CNY", "₹": "INR", "Rs. ": "INR"} 

173 

174 # Static regexes 

175 re_GameInfo = re.compile( 

176 """ 

177 (?P<SITE>PokerStars|POKERSTARS|Hive\sPoker|Full\sTilt|PokerMaster|Run\sIt\sOnce\sPoker|BetOnline|PokerBros|MPLPoker|SupremaPoker)(?P<TITLE>\sGame|\sHand|\sHome\sGame|\sHome\sGame\sHand|Game|\s(Zoom|Rush)\sHand|\sGAME)\s\#(?P<HID>[0-9]+):\s+ 

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

179 (?P<TOURNO>\d+),\s(Table\s\#(?P<HIVETABLE>\d+),\s)? 

180 # here's how I plan to use LS 

181 (?P<BUYIN>(?P<BIAMT>[%(LS)s\d\.]+)?\+?(?P<BIRAKE>[%(LS)s\d\.]+)?\+?(?P<BOUNTY>[%(LS)s\d\.]+)?\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?|Freeroll|)(\s+)?(-\s)? 

182 (\s.+?,)? 

183 )? 

184 # close paren of tournament info 

185 (?P<MIXED>HORSE|8\-Game|8\-GAME|HOSE|Mixed\sOmaha\sH/L|Mixed\sHold\'em|Mixed\sPLH/PLO|Mixed\sNLH/PLO|Mixed\sOmaha|Triple\sStud)?\s?\(? 

186 (?P<SPLIT>Split)?\s? 

187 (?P<GAME>Hold\'em|HOLD\'EM|Hold\'em|6\+\sHold\'em|Razz|RAZZ|Fusion|7\sCard\sStud|7\sCARD\sSTUD|7\sCard\sStud\sHi/Lo|7\sCARD\sSTUD\sHI/LO|Omaha|OMAHA|Omaha\sHi/Lo|OMAHA\sHI/LO|Badugi|Triple\sDraw\s2\-7\sLowball|Single\sDraw\s2\-7\sLowball|5\sCard\sDraw|(5|6)\sCard\sOmaha(\sHi/Lo)?|Omaha\s(5|6)\sCards|Courchevel(\sHi/Lo)?)\s 

188 (?P<LIMIT>No\sLimit|NO\sLIMIT|Fixed\sLimit|Limit|LIMIT|Pot\sLimit|POT\sLIMIT|Pot\sLimit\sPre\-Flop,\sNo\sLimit\sPost\-Flop)\)?,?\s 

189 (-\s)? 

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

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

192 \(? # open paren of the stakes 

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

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

195 ((?P<SB>[.0-9]+)/(%(LS)s)?(?P<BB>[.0-9]+)|Button\sBlind\s(?P<CURRENCY1>%(LS)s|)(?P<BUB>[.0-9]+)\s\-\sAnte\s(%(LS)s)?[.0-9]+\s) 

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

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

198 \) # close paren of the stakes 

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

200 \s-\s 

201 (?P<DATETIME>.*$) 

202 """ 

203 % substitutions, 

204 re.MULTILINE | re.VERBOSE, 

205 ) 

206 

207 re_PlayerInfo = re.compile( 

208 """ 

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

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

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

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

213 \) 

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

215 % substitutions, 

216 re.MULTILINE | re.VERBOSE, 

217 ) 

218 

219 re_HandInfo = re.compile( 

220 """ 

221 \s?Table\s(ID\s)?\'(?P<TABLE>.+?)\'(\(\d+\))?\s 

222 ((?P<MAX>\d+)-[Mm]ax\s)? 

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

224 (\(Real\sMoney\)\s)? 

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

226 re.MULTILINE | re.VERBOSE, 

227 ) 

228 

229 re_Identify = re.compile( 

230 "(PokerStars|POKERSTARS|Hive\sPoker|Full\sTilt|PokerMaster|Run\sIt\sOnce\sPoker|BetOnline|PokerBros|MPLPoker|SupremaPoker)(\sGame|\sHand|\sHome\sGame|\sHome\sGame\sHand|Game|\s(Zoom|Rush)\sHand|\sGAME)\s\#\d+:" 

231 ) 

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

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

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

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

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

237 re_DateTime1 = re.compile( 

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

239 re.MULTILINE, 

240 ) 

241 re_DateTime2 = re.compile( 

242 """(?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 

243 ) 

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

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

246 

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

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

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

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

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

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

253 re_BringIn = re.compile( 

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

255 ) 

256 re_PostBoth = re.compile( 

257 r"%(PLYR)s: posts small \& big blinds %(CUR)s(?P<SBBB>[,.0-9]+)" % substitutions, re.MULTILINE 

258 ) 

259 re_PostStraddle = re.compile( 

260 r"%(PLYR)s: posts straddle %(CUR)s(?P<STRADDLE>[,.0-9]+)" % substitutions, re.MULTILINE 

261 ) 

262 try: 

263 re_Action = re.compile( 

264 r"""%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat)(\s%(CUR)s(?P<BET>[,.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[,.\d]+))?\s*(and\sis\sall.in)?(and\shas\sreached\sthe\s[%(CUR)s\d\.,]+\scap)?(\son|\scards?)?(\s\(disconnect\))?(\s\[(?P<CARDS>.+?)\])?\s*$""" 

265 % substitutions, 

266 re.MULTILINE | re.VERBOSE, 

267 ) 

268 except Exception as e: 

269 print(f"Error compiling re_Action: {e}") 

270 

271 re_ShowdownAction = re.compile( 

272 r"%s: (shows|mucks|mucked|showed) \[(?P<CARDS>.*)\]" % substitutions["PLYR"], re.MULTILINE 

273 ) 

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

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

276 re_CollectPot = re.compile( 

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

278 % substitutions, 

279 re.MULTILINE, 

280 ) 

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

282 re_CollectPot2 = re.compile( 

283 r"%(PLYR)s (collected|cashed out the hand for) %(CUR)s(?P<POT>[,.\d]+)" % substitutions, re.MULTILINE 

284 ) 

285 re_CashedOut = re.compile(r"cashed\sout\sthe\shand") 

286 re_WinningRankOne = re.compile( 

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

288 re.MULTILINE, 

289 ) 

290 re_WinningRankOther = re.compile( 

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

292 % substitutions, 

293 re.MULTILINE, 

294 ) 

295 re_RankOther = re.compile( 

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

297 ) 

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

299 re_Uncalled = re.compile( 

300 "Uncalled bet \(%(CUR)s(?P<BET>[,.\d]+)\) returned to %(PLYR)s$" % substitutions, re.MULTILINE 

301 ) 

302 re_EmptyCard = re.compile("\[\]", re.MULTILINE) 

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

304 # ChazDazzle wins the 22000 bounty for eliminating berkovich609 

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

306 re_Bounty = re.compile( 

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

308 % substitutions, 

309 re.MULTILINE, 

310 ) 

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

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

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

314 re_Progressive = re.compile( 

315 r""" 

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

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

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

319 % substitutions, 

320 re.MULTILINE | re.VERBOSE, 

321 ) 

322 re_Rake = re.compile( 

323 r""" 

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

325 % substitutions, 

326 re.MULTILINE | re.VERBOSE, 

327 ) 

328 

329 re_STP = re.compile( 

330 r""" 

331 STP\sadded:\s%(CUR)s(?P<AMOUNT>[,\.0-9]+)""" 

332 % substitutions, 

333 re.MULTILINE | re.VERBOSE, 

334 ) 

335 

336 def compilePlayerRegexs(self, hand): 

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

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

339 self.compiledPlayers = players 

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

341 subst = { 

342 "PLYR": player_re, 

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

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

345 } 

346 if self.siteId == 26: 

347 self.re_HeroCards = re.compile( 

348 r"Dealt\sto\s(?P<PNAME>(?![A-Z][a-z]+\s[A-Z]).+?)(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" 

349 % subst, 

350 re.MULTILINE, 

351 ) 

352 self.re_ShownCards = re.compile( 

353 "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(lost|won\s%(CUR)s[\.\d]+\swith\s(?P<STRING2>.*)))?)?$" 

354 % subst, 

355 re.MULTILINE, 

356 ) 

357 else: 

358 self.re_HeroCards = re.compile( 

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

360 ) 

361 self.re_ShownCards = re.compile( 

362 "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>.*))?)?$" 

363 % subst, 

364 re.MULTILINE, 

365 ) 

366 

367 def readSupportedGames(self): 

368 return [ 

369 ["ring", "hold", "nl"], 

370 ["ring", "hold", "pl"], 

371 ["ring", "hold", "fl"], 

372 ["ring", "hold", "pn"], 

373 ["ring", "stud", "fl"], 

374 ["ring", "draw", "fl"], 

375 ["ring", "draw", "pl"], 

376 ["ring", "draw", "nl"], 

377 ["tour", "hold", "nl"], 

378 ["tour", "hold", "pl"], 

379 ["tour", "hold", "fl"], 

380 ["tour", "hold", "pn"], 

381 ["tour", "stud", "fl"], 

382 ["tour", "draw", "fl"], 

383 ["tour", "draw", "pl"], 

384 ["tour", "draw", "nl"], 

385 ] 

386 

387 def determineGameType(self, handText): 

388 info = {} 

389 m = self.re_GameInfo.search(handText) 

390 if not m: 

391 tmp = handText[0:200] 

392 log.error(("PokerStarsToFpdb.determineGameType: '%s'") % tmp) 

393 raise FpdbParseError 

394 

395 mg = m.groupdict() 

396 if "LIMIT" in mg: 

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

398 if "GAME" in mg: 

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

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

401 info["sb"] = mg["SB"] 

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

403 info["bb"] = mg["BB"] 

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

405 info["sb"] = "0" 

406 info["bb"] = mg["BUB"] 

407 if "CURRENCY1" in mg and mg["CURRENCY1"] is not None: 

408 info["currency"] = self.currencies[mg["CURRENCY1"]] 

409 elif "CURRENCY" in mg: 

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

411 if "MIXED" in mg: 

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

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

414 if "Zoom" in mg["TITLE"] or "Rush" in mg["TITLE"]: 

415 info["fast"] = True 

416 else: 

417 info["fast"] = False 

418 if "Home" in mg["TITLE"]: 

419 info["homeGame"] = True 

420 else: 

421 info["homeGame"] = False 

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

423 info["buyinType"] = "cap" 

424 else: 

425 info["buyinType"] = "regular" 

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

427 info["split"] = True 

428 else: 

429 info["split"] = False 

430 if "SITE" in mg: 

431 if mg["SITE"] == "PokerMaster": 

432 self.sitename = "PokerMaster" 

433 self.siteId = 25 

434 m1 = self.re_HandInfo.search(handText, re.DOTALL) 

435 if m1 and "_5Cards_" in m1.group("TABLE"): 

436 info["category"] = "5_omahahi" 

437 elif mg["SITE"] == "Run It Once Poker": 

438 self.sitename = "Run It Once Poker" 

439 self.siteId = 26 

440 elif mg["SITE"] == "BetOnline": 

441 self.sitename = "BetOnline" 

442 self.siteId = 19 

443 elif mg["SITE"] == "PokerBros": 

444 self.sitename = "PokerBros" 

445 self.siteId = 29 

446 

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

448 info["type"] = "ring" 

449 else: 

450 info["type"] = "tour" 

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

452 info["fast"] = True 

453 

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

455 info["currency"] = "play" 

456 

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

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

459 try: 

460 info["sb"] = self.Lim_Blinds[mg["BB"]][0] 

461 info["bb"] = self.Lim_Blinds[mg["BB"]][1] 

462 except KeyError: 

463 tmp = handText[0:200] 

464 log.error( 

465 ("PokerStarsToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") 

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

467 ) 

468 raise FpdbParseError 

469 else: 

470 info["sb"] = str((float(mg["SB"]) / 2).quantize(float("0.01"))) 

471 info["bb"] = str(float(mg["SB"]).quantize(float("0.01"))) 

472 

473 return info 

474 

475 def readHandInfo(self, hand): 

476 # First check if partial 

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

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

479 

480 info = {} 

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

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

483 if m is None or m2 is None: 

484 tmp = hand.handText[0:200] 

485 log.error(("PokerStarsToFpdb.readHandInfo: '%s'") % tmp) 

486 raise FpdbParseError 

487 

488 info.update(m.groupdict()) 

489 info.update(m2.groupdict()) 

490 

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

492 for key in info: 

493 if key == "DATETIME": 

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

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

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

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

498 if self.siteId == 26: 

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

500 for a in m2: 

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

502 a.group("Y"), 

503 a.group("M"), 

504 a.group("D"), 

505 a.group("H"), 

506 a.group("MIN"), 

507 "00", 

508 ) 

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

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

511 hand.startTime = datetime.datetime.strptime( 

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

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

514 else: 

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

516 for a in m1: 

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

518 a.group("Y"), 

519 a.group("M"), 

520 a.group("D"), 

521 a.group("H"), 

522 a.group("MIN"), 

523 a.group("S"), 

524 ) 

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

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

527 hand.startTime = datetime.datetime.strptime( 

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

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

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

531 

532 if key == "HID": 

533 hand.handid = info[key] 

534 if key == "TOURNO" and info[key] is not None: 

535 hand.tourNo = info[key][-18:] 

536 if key == "BUYIN": 

537 if hand.tourNo is not None: 

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

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

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

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

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

543 hand.buyin = 0 

544 hand.fee = 0 

545 hand.buyinCurrency = "FREE" 

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

547 hand.buyin = 0 

548 hand.fee = 0 

549 hand.buyinCurrency = "NA" 

550 else: 

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

552 hand.buyinCurrency = "USD" 

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

554 hand.buyinCurrency = "GBP" 

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

556 hand.buyinCurrency = "EUR" 

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

558 hand.buyinCurrency = "INR" 

559 elif info[key].find("Rs. ") != -1: 

560 hand.buyinCurrency = "INR" 

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

562 hand.buyinCurrency = "CNY" 

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

564 hand.buyinCurrency = "PSFP" 

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

566 hand.buyinCurrency = "PSFP" 

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

568 hand.buyinCurrency = "play" 

569 else: 

570 # FIXME: handle other currencies, play money 

571 log.error( 

572 ("PokerStarsToFpdb.readHandInfo: Failed to detect currency.") 

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

574 ) 

575 raise FpdbParseError 

576 

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

578 

579 if hand.buyinCurrency != "PSFP": 

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

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

582 tmp = info["BOUNTY"] 

583 info["BOUNTY"] = info["BIRAKE"] 

584 info["BIRAKE"] = tmp 

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

586 hand.koBounty = int(100 * float(info["BOUNTY"])) 

587 hand.isKO = True 

588 else: 

589 hand.isKO = False 

590 

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

592 

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

594 hand.fee = int(100 * float(info["BIRAKE"])) 

595 else: 

596 hand.buyin = int(100 * float(info["BIAMT"])) 

597 hand.fee = 0 

598 if "Zoom" in info["TITLE"] or "Rush" in info["TITLE"]: 

599 hand.isFast = True 

600 else: 

601 hand.isFast = False 

602 if "Home" in info["TITLE"]: 

603 hand.isHomeGame = True 

604 else: 

605 hand.isHomeGame = False 

606 if key == "LEVEL": 

607 hand.level = info[key] 

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

609 hand.isShootout = True 

610 if key == "TABLE": 

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

612 if info["TOURNO"] is not None and info["HIVETABLE"] is not None: 

613 hand.tablename = info["HIVETABLE"] 

614 elif hand.tourNo is not None and len(tablesplit) > 1: 

615 hand.tablename = tablesplit[1] 

616 else: 

617 hand.tablename = info[key] 

618 if key == "BUTTON": 

619 hand.buttonpos = info[key] 

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

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

622 

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

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

625 

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

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

628 

629 def readButton(self, hand): 

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

631 if m: 

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

633 else: 

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

635 

636 def readPlayerStacks(self, hand): 

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

638 m = self.re_PlayerInfo.finditer(pre) 

639 for a in m: 

640 hand.addPlayer( 

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

642 a.group("PNAME"), 

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

644 None, 

645 a.group("SITOUT"), 

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

647 ) 

648 

649 def markStreets(self, hand): 

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

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

652 # in consequence the mucked-display is incorrect. 

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

654 

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

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

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

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

659 # handText was not split, no DRAW street occurred 

660 pass 

661 else: 

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

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

664 hand.handText = "" 

665 for i in discard_split: 

666 hand.handText += i 

667 

668 # PREFLOP = ** Dealing down cards ** 

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

670 if hand.gametype["split"]: 

671 m = re.search( 

672 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FIRST\sFLOP \*\*\*)|.+)" 

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

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

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

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

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

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

679 hand.handText, 

680 re.DOTALL, 

681 ) 

682 elif hand.gametype["base"] in ("hold"): 

683 if self.siteId == 19: 

684 m = re.search( 

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

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

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

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

689 hand.handText, 

690 re.DOTALL, 

691 ) 

692 else: 

693 m = re.search( 

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

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

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

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

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

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

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

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

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

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

704 hand.handText, 

705 re.DOTALL, 

706 ) 

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

708 m = re.search( 

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

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

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

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

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

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

715 hand.handText, 

716 re.DOTALL, 

717 ) 

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

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

720 m = re.search( 

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

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

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

724 hand.handText, 

725 re.DOTALL, 

726 ) 

727 else: 

728 m = re.search( 

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

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

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

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

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

734 hand.handText, 

735 re.DOTALL, 

736 ) 

737 print("type", type(m), m) 

738 mg = m.groupdict() 

739 print("type mg", type(mg), mg) 

740 hand.addStreets(m) 

741 

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

743 if self.re_EmptyCard.search(hand.streets[street]): 

744 raise FpdbHandPartial(("Blank community card")) 

745 if ( 

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

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

748 m2 = self.re_Board2.search(hand.streets[street]) 

749 if m2: 

750 hand.setCommunityCards(street, [m2.group("C1"), m2.group("C2"), m2.group("C3")]) 

751 else: 

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

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

754 if street in ("FLOP1", "TURN1", "RIVER1", "FLOP2", "TURN2", "RIVER2"): 

755 hand.runItTimes = 2 

756 

757 def readSTP(self, hand): 

758 # log.debug(_("read Splash the Pot")) 

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

760 if m: 

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

762 

763 def readAntes(self, hand): 

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

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

766 for player in m: 

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

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

769 

770 def readBringIn(self, hand): 

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

772 if m: 

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

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

775 

776 def readBlinds(self, hand): 

777 liveBlind = True 

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

779 if liveBlind: 

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

781 liveBlind = False 

782 else: 

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

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

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

786 else: 

787 # Post dead blinds as ante 

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

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

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

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

792 hand.addBlind(a.group("PNAME"), "both", self.clearMoneyString(a.group("SBBB"))) 

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

794 hand.addBlind(a.group("PNAME"), "straddle", self.clearMoneyString(a.group("STRADDLE"))) 

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

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

797 

798 def readHoleCards(self, hand): 

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

800 # we need to grab hero's cards 

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

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

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

804 for found in m: 

805 # if m == None: 

806 # hand.involved = False 

807 # else: 

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

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

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

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

812 

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

814 print("text", text) 

815 print(list(hand.streets.items())) 

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

817 continue # already done these 

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

819 for found in m: 

820 player = found.group("PNAME") 

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

822 newcards = [] 

823 else: 

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

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

826 oldcards = [] 

827 else: 

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

829 

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

831 hand.hero = player 

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

833 hand.addHoleCards( 

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

835 ) 

836 else: 

837 hand.addHoleCards( 

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

839 ) 

840 

841 def readAction(self, hand, street): 

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

843 s = street + "2" 

844 else: 

845 s = street 

846 if not hand.streets[s]: 

847 return 

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

849 for action in m: 

850 # acts = action.groupdict() 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

869 else: 

870 log.debug( 

871 ("DEBUG:") 

872 + " " 

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

874 ) 

875 m = self.re_Uncalled.search(hand.streets[s]) 

876 if m: 

877 hand.addUncalled(street, m.group("PNAME"), m.group("BET")) 

878 

879 def readShowdownActions(self, hand): 

880 # TODO: pick up mucks also?? 

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

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

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

884 

885 def readTourneyResults(self, hand): 

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

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

888 koAmounts = {} 

889 winner = None 

890 # %(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]+) 

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

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

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

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

895 koAmounts[a.group("PNAME")] += 100 * float(a.group("AMT")) 

896 hand.endBounty[a.group("PNAME")] = 100 * float(a.group("ENDAMT")) 

897 hand.isProgressive = True 

898 

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

900 if m: 

901 winner = m.group("PNAME") 

902 

903 if hand.koBounty > 0: 

904 for pname, amount in list(koAmounts.items()): 

905 if pname == winner: 

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

907 hand.koCounts[pname] = (amount + hand.endBounty[pname]) / float(hand.koBounty) 

908 else: 

909 # end = 0 

910 hand.koCounts[pname] = amount / float(hand.koBounty) 

911 else: 

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

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

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

915 for pname in pnames: 

916 if pname not in hand.koCounts: 

917 hand.koCounts[pname] = 0 

918 hand.koCounts[pname] += 1 / float(len(pnames)) 

919 else: 

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

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

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

923 

924 def readCollectPot(self, hand): 

925 # Bovada walks are calculated incorrectly in converted PokerStars hands 

926 acts, bovadaUncalled_v1, bovadaUncalled_v2, blindsantes, adjustment = ( 

927 hand.actions.get("PREFLOP"), 

928 False, 

929 False, 

930 0, 

931 0, 

932 ) 

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

934 if "Big Blind" in names or "Small Blind" in names or "Dealer" in names or self.siteId == 26: 

935 if acts is not None and len([a for a in acts if a[1] != "folds"]) == 0: 

936 m0 = self.re_Uncalled.search(hand.handText) 

937 if m0 and float(m0.group("BET")) == float(hand.bb): 

938 bovadaUncalled_v2 = True 

939 elif m0 is None: 

940 bovadaUncalled_v1 = True 

941 has_sb = len([a[2] for a in hand.actions.get("BLINDSANTES") if a[1] == "small blind"]) > 0 

942 adjustment = (float(hand.bb) - float(hand.sb)) if has_sb else float(hand.bb) 

943 blindsantes = sum([a[2] for a in hand.actions.get("BLINDSANTES")]) 

944 i = 0 

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

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

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

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

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

950 if bovadaUncalled_v1 and float(pot) == (blindsantes + hand.pot.stp): 

951 hand.addCollectPot(player=m.group("PNAME"), pot=str(float(pot) - adjustment)) 

952 elif bovadaUncalled_v2: 

953 hand.addCollectPot(player=m.group("PNAME"), pot=str(float(pot) * 2)) 

954 else: 

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

956 i += 1 

957 if i == 0: 

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

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

960 if bovadaUncalled_v1 and float(pot) == (blindsantes + hand.pot.stp): 

961 hand.addCollectPot(player=m.group("PNAME"), pot=str(float(pot) - adjustment)) 

962 elif bovadaUncalled_v2: 

963 hand.addCollectPot(player=m.group("PNAME"), pot=str(float(pot) * 2)) 

964 else: 

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

966 

967 def readShownCards(self, hand): 

968 if self.siteId == 26: 

969 re_RevealedCards = re.compile( 

970 r"Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % self.substitutions, 

971 re.MULTILINE, 

972 ) 

973 m = re_RevealedCards.finditer(hand.handText) 

974 for found in m: 

975 cards = found.group("NEWCARDS").split(" ") 

976 hand.addShownCards(cards=cards, player=found.group("PNAME"), shown=True, mucked=False) 

977 

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

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

980 cards = m.group("CARDS") 

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

982 string = m.group("STRING") 

983 if m.group("STRING2"): 

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

985 

986 (shown, mucked) = (False, False) 

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

988 shown = True 

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

990 mucked = True 

991 

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

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

994 

995 def readSummaryInfo(self, summaryInfoList): 

996 """Implement the abstract method from HandHistoryConverter.""" 

997 # Add the actual implementation here, or use a placeholder if not needed 

998 log.info("Reading summary info for PokerStars.") 

999 return True 

1000 

1001 @staticmethod 

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

1003 "Returns string to search in windows titles" 

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

1005 print("regex cash ", regex) 

1006 if type == "tour": 

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

1008 print("regex tour: ", regex) 

1009 log.info( 

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

1011 % (table_name, tournament, table_number) 

1012 ) 

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

1014 print("regex:") 

1015 print(regex) 

1016 return regex