Coverage for GGPokerToFpdb.py: 0%

393 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-27 18:50 +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 

26import sys 

27from HandHistoryConverter import * 

28from decimal_wrapper import Decimal 

29 

30# PokerStars HH Format 

31 

32class GGPoker(HandHistoryConverter): 

33 

34 # Class Variables 

35 

36 sitename = "GGPoker" 

37 filetype = "text" 

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

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

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

41 substitutions = { 

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

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

44 'PLYR': r'\s?(?P<PNAME>.+?)', 

45 'CUR': u"(\$|\¥|)", 

46 'BRKTS': r'(\(button\) |\(small blind\) |\(big blind\) |\(button blind\) |\(button\) \(small blind\) |\(small blind/button\) |\(button\) \(big blind\) )?', 

47 } 

48 

49 # translations from captured groups to fpdb info strings 

50 Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.08': ('0.02', '0.04'), 

51 '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'), 

52 '0.40': ('0.10', '0.20'), '0.50': ('0.10', '0.25'), 

53 '1.00': ('0.25', '0.50'), '1': ('0.25', '0.50'), 

54 '2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'), 

55 '4.00': ('1.00', '2.00'), '4': ('1.00', '2.00'), 

56 '6.00': ('1.00', '3.00'), '6': ('1.00', '3.00'), 

57 '8.00': ('2.00', '4.00'), '8': ('2.00', '4.00'), 

58 '10.00': ('2.00', '5.00'), '10': ('2.00', '5.00'), 

59 '16.00': ('4.00', '8.00'), '16': ('4.00', '8.00'), 

60 '20.00': ('5.00', '10.00'), '20': ('5.00', '10.00'), 

61 '30.00': ('10.00', '15.00'), '30': ('10.00', '15.00'), 

62 '40.00': ('10.00', '20.00'), '40': ('10.00', '20.00'), 

63 '50.00': ('10.00', '25.00'), '50': ('10.00', '25.00'), 

64 '60.00': ('15.00', '30.00'), '60': ('15.00', '30.00'), 

65 '80.00': ('20.00', '40.00'), '80': ('20.00', '40.00'), 

66 '100.00': ('25.00', '50.00'), '100': ('25.00', '50.00'), 

67 '150.00': ('50.00', '75.00'), '150': ('50.00', '75.00'), 

68 '200.00': ('50.00', '100.00'), '200': ('50.00', '100.00'), 

69 '400.00': ('100.00', '200.00'), '400': ('100.00', '200.00'), 

70 '500.00': ('100.00', '250.00'), '500': ('100.00', '250.00'), 

71 '600.00': ('150.00', '300.00'), '600': ('150.00', '300.00'), 

72 '800.00': ('200.00', '400.00'), '800': ('200.00', '400.00'), 

73 '1000.00': ('250.00', '500.00'), '1000': ('250.00', '500.00'), 

74 '2000.00': ('500.00', '1000.00'), '2000': ('500.00', '1000.00'), 

75 '4000.00': ('1000.00', '2000.00'), '4000': ('1000.00', '2000.00'), 

76 '10000.00': ('2500.00', '5000.00'), '10000': ('2500.00', '5000.00'), 

77 '20000.00': ('5000.00', '10000.00'),'20000': ('5000.00', '10000.00'), 

78 '40000.00': ('10000.00', '20000.00'),'40000': ('10000.00', '20000.00'), 

79 } 

80 

81 limits = { 

82 'No Limit':'nl', 

83 'Pot Limit':'pl', 

84 'Fixed Limit':'fl', 

85 'Limit':'fl', 

86 '(NL postflop)': 'pn' 

87 } 

88 games = { # base, category 

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

90 "ShortDeck" : ('hold','6_holdem'), 

91 'Omaha' : ('hold','omahahi'), 

92 'Omaha Hi/Lo' : ('hold','omahahilo'), 

93 'PLO' : ('hold','omahahi'), 

94 'PLO-5' : ('hold', '5_omahahi'), 

95 'PLO-6' : ('hold', '6_omahahi') 

96 } 

97# mixes = { 

98# 'HORSE': 'horse', 

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

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

101# 'HOSE': 'hose', 

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

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

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

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

106# 'Mixed Omaha': 'momaha', 

107# 'Triple Stud': '3stud' 

108# } # Legal mixed games 

109 currencies = { '$':'USD', '':'T$', u'¥':'CNY'} 

110 

111 #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 

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

113 # Static regexes 

114 re_GameInfo = re.compile(u""" 

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

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

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

118 # here's how I plan to use LS 

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

120 )? 

121 # close paren of tournament info 

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

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

124 (-\s)? 

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

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

127 \(? # open paren of the stakes 

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

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

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

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

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

133 \) # close paren of the stakes 

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

135 \s-\s 

136 (?P<DATETIME>.*$) 

137 """ % substitutions, re.MULTILINE|re.VERBOSE) 

138 

139 re_PlayerInfo = re.compile(u""" 

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

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

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

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

144 \) 

145 (?P<SITOUT>\sis\ssitting\sout)?""" % substitutions, 

146 re.MULTILINE|re.VERBOSE) 

147 

148 re_HandInfo = re.compile(""" 

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

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

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

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

153 re.MULTILINE|re.VERBOSE) 

154 

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

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

157 re_TailSplitHands = re.compile('(\n\n\n+)') 

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

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

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

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

162 re_DateTime1 = 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]+)""", re.MULTILINE) 

163 re_DateTime2 = 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]+)""", re.MULTILINE) 

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

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

166 

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

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

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

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

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

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

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

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

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

176 re_Action = re.compile(r""" 

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

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

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

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

181 (\son|\scards?)? 

182 (\s\(disconnect\))? 

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

184 % substitutions, re.MULTILINE|re.VERBOSE) 

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

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

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

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

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

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

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

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

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

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

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

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

197 re_Cancelled = re.compile('Hand\scancelled', re.MULTILINE) 

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

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

200 #ChazDazzle wins the 22000 bounty for eliminating berkovich609 

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

202 re_Bounty = re.compile(u"^%(PLYR)s (?P<SPLIT>split|wins) the %(CUR)s(?P<AMT>[,\.0-9]+) bounty for eliminating (?P<ELIMINATED>.+?)$" % substitutions, re.MULTILINE) 

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

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

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

206 re_Progressive = re.compile(u""" 

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

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

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

210 % substitutions, re.MULTILINE|re.VERBOSE) 

211 re_Rake = re.compile(u""" 

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

213 % substitutions, re.MULTILINE|re.VERBOSE) 

214 

215 re_STP = re.compile(u""" 

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

217 % substitutions, re.MULTILINE|re.VERBOSE) 

218 

219 def compilePlayerRegexs(self, hand): 

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

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

222 self.compiledPlayers = players 

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

224 subst = { 

225 'PLYR': player_re, 

226 'BRKTS': r'(\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) |\(button\) \(big blind\) )?', 

227 'CUR': u"(\$|\xe2\x82\xac|\u20ac||\£|)" 

228 } 

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

230 self.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>.*))?)?$" % subst, re.MULTILINE) 

231 

232 

233 def readSupportedGames(self): 

234 return [["ring", "hold", "nl"], 

235 ["ring", "hold", "pl"], 

236 ["ring", "hold", "fl"], 

237 ["ring", "hold", "pn"], 

238 

239 ["ring", "stud", "fl"], 

240 

241 ["ring", "draw", "fl"], 

242 ["ring", "draw", "pl"], 

243 ["ring", "draw", "nl"], 

244 

245 ["tour", "hold", "nl"], 

246 ["tour", "hold", "pl"], 

247 ["tour", "hold", "fl"], 

248 ["tour", "hold", "pn"], 

249 

250 ["tour", "stud", "fl"], 

251 

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 tmp = handText[0:200] 

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

263 raise FpdbParseError 

264 

265 mg = m.groupdict() 

266 if 'LIMIT' in mg and mg['LIMIT'] is not None: 

267 info['limitType'] = self.limits[mg['LIMIT']] 

268 else: 

269 info['limitType'] = 'pl' 

270 if 'GAME' in mg: 

271 (info['base'], info['category']) = self.games[mg['GAME']] 

272 if 'SB' in mg and mg['SB'] is not None: 

273 info['sb'] = self.clearMoneyString(mg['SB']) 

274 if 'BB' in mg and mg['BB'] is not None: 

275 info['bb'] = self.clearMoneyString(mg['BB']) 

276 if 'BUB' in mg and mg['BUB'] is not None: 

277 info['sb'] = '0' 

278 info['bb'] = self.clearMoneyString(mg['BUB']) 

279 if 'CURRENCY' in mg: 

280 info['currency'] = self.currencies[mg['CURRENCY']] 

281 if 'CAP' in mg and mg['CAP'] is not None: 

282 info['buyinType'] = 'cap' 

283 else: 

284 info['buyinType'] = 'regular' 

285 if 'SPLIT' in mg and mg['SPLIT'] == 'Split': 

286 info['split'] = True 

287 else: 

288 info['split'] = False 

289 if 'TOURNO' in mg and mg['TOURNO'] is None: 

290 info['type'] = 'ring' 

291 else: 

292 info['type'] = 'tour' 

293 if 'ZOOM' in mg['TOUR']: 

294 info['fast'] = True 

295 

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

297 info['currency'] = 'play' 

298 

299 if info['limitType'] == 'fl' and info['bb'] is not None: 

300 if info['type'] == 'ring': 

301 try: 

302 info['sb'] = self.Lim_Blinds[self.clearMoneyString(mg['BB'])][0] 

303 info['bb'] = self.Lim_Blinds[self.clearMoneyString(mg['BB'])][1] 

304 except KeyError: 

305 tmp = handText[0:200] 

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

307 raise FpdbParseError 

308 else: 

309 info['sb'] = str((old_div(Decimal(self.clearMoneyString(mg['SB'])),2)).quantize(Decimal("0.01"))) 

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

311 

312 return info 

313 

314 def readHandInfo(self, hand): 

315 #First check if partial 

316 if hand.handText.count('*** SUMMARY ***')!=1: 

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

318 

319 info = {} 

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

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

322 if m is None or m2 is None: 

323 tmp = hand.handText[0:200] 

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

325 raise FpdbParseError 

326 

327 info.update(m.groupdict()) 

328 info.update(m2.groupdict()) 

329 

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

331 for key in info: 

332 if key == 'DATETIME': 

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

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

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

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

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

338 for a in m1: 

339 datetimestr = "%s/%s/%s %s:%s:%s" % (a.group('Y'), a.group('M'),a.group('D'),a.group('H'),a.group('MIN'),a.group('S')) 

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

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

342 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET" 

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

344 

345 if key == 'HID': 

346 hand.handid = info[key] 

347 if key == 'TOURNO': 

348 hand.tourNo = info[key] 

349 if key == 'BUYIN': 

350 if hand.tourNo!=None: 

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

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

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

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

355 if info[key].strip() == 'Freeroll': 

356 hand.buyin = 0 

357 hand.fee = 0 

358 hand.buyinCurrency = "FREE" 

359 elif info[key].strip() == '': 

360 hand.buyin = 0 

361 hand.fee = 0 

362 hand.buyinCurrency = "NA" 

363 else: 

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

365 hand.buyinCurrency="USD" 

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

367 hand.buyinCurrency="GBP" 

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

369 hand.buyinCurrency="EUR" 

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

371 hand.buyinCurrency="INR" 

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

373 hand.buyinCurrency="CNY" 

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

375 hand.buyinCurrency="PSFP" 

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

377 hand.buyinCurrency="PSFP" 

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

379 hand.buyinCurrency="play" 

380 else: 

381 #FIXME: handle other currencies, play money 

382 log.error(("GGPokerToFpdb.readHandInfo: Failed to detect currency.") + " Hand ID: %s: '%s'" % (hand.handid, info[key])) 

383 raise FpdbParseError 

384 

385 info['BIAMT'] = info['BIAMT'].strip(u'$€£FPPSC₹') 

386 

387 if hand.buyinCurrency!="PSFP": 

388 if info['BOUNTY'] != None: 

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

390 tmp = info['BOUNTY'] 

391 info['BOUNTY'] = info['BIRAKE'] 

392 info['BIRAKE'] = tmp 

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

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

395 hand.isKO = True 

396 else: 

397 hand.isKO = False 

398 

399 info['BIRAKE'] = info['BIRAKE'].strip(u'$€£₹') 

400 

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

402 hand.fee = int(100*Decimal(info['BIRAKE'])) 

403 else: 

404 hand.buyin = int(100*Decimal(info['BIAMT'])) 

405 hand.fee = 0 

406 if key == 'LEVEL': 

407 hand.level = info[key] 

408 if key == 'SHOOTOUT' and info[key] != None: 

409 hand.isShootout = True 

410 if key == 'TABLE': 

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

412 if hand.tourNo != None and len(tablesplit)>1: 

413 hand.tablename = tablesplit[1] 

414 else: 

415 hand.tablename = info[key] 

416 if key == 'TOURNAME': 

417 hand.tourneyName = info[key] 

418 if key == 'BUTTON': 

419 hand.buttonpos = info[key] 

420 if key == 'MAX' and info[key] != None: 

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

422 

423 if 'Zoom' in self.in_path or 'Rush' in self.in_path: 

424 (hand.gametype['fast'], hand.isFast) = (True, True) 

425 

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

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

428 

429 def readButton(self, hand): 

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

431 if m: 

432 hand.buttonpos = int(m.group('BUTTON')) 

433 else: 

434 log.info('readButton: ' + ('not found')) 

435 

436 def readPlayerStacks(self, hand): 

437 pre, post = hand.handText.split('*** SUMMARY ***') 

438 m = self.re_PlayerInfo.finditer(pre) 

439 for a in m: 

440 hand.addPlayer( 

441 int(a.group('SEAT')), 

442 a.group('PNAME'), 

443 self.clearMoneyString(a.group('CASH')), 

444 None, 

445 a.group('SITOUT'), 

446 self.clearMoneyString(a.group('BOUNTY')) 

447 ) 

448 

449 def markStreets(self, hand): 

450 

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

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

453 # in consequence the mucked-display is incorrect. 

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

455 

456 if hand.gametype['category'] in ('27_1draw', 'fivedraw'): 

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

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

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

460 # handText was not split, no DRAW street occurred 

461 pass 

462 else: 

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

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

465 hand.handText = "" 

466 for i in discard_split: 

467 hand.handText += i 

468 

469 # PREFLOP = ** Dealing down cards ** 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

484 r"(\*\*\* THIRD RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER3>\[\S\S\].+))?", hand.handText,re.DOTALL) 

485 hand.addStreets(m) 

486 if 'AFO' in hand.tablename: 

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

488 if m1: 

489 hand.streets.update({ 

490 'FLOP': '[%s] %s' % (m1.group('FLOP'),hand.streets['FLOP']), 

491 'TURN': '[%s]' % m1.group('TURN'), 

492 'RIVER': '[%s]' % m1.group('RIVER') 

493 }) 

494 

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

496 if street!='FLOPET' or hand.streets.get('FLOP')==None: # a list of streets which get dealt community cards (i.e. all but PREFLOP) 

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

498 hand.setCommunityCards(street, m.group('CARDS').split(' ')) 

499 if street in ('FLOP3', 'TURN3', 'RIVER3'): 

500 hand.runItTimes = 3 

501 elif street in ('FLOP2', 'TURN2', 'RIVER2'): 

502 hand.runItTimes = 2 

503 

504 def readSTP(self, hand): 

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

506 if m: 

507 hand.addSTP(m.group('AMOUNT')) 

508 

509 def readAntes(self, hand): 

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

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

512 for player in m: 

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

514 hand.addAnte(player.group('PNAME'), self.clearMoneyString(player.group('ANTE'))) 

515 

516 def readBringIn(self, hand): 

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

518 if m: 

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

520 hand.addBringIn(m.group('PNAME'), self.clearMoneyString(m.group('BRINGIN'))) 

521 

522 def readBlinds(self, hand): 

523 liveBlind, straddles = True, {} 

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

525 if liveBlind: 

526 hand.addBlind(a.group('PNAME'), 'small blind', self.clearMoneyString(a.group('SB'))) 

527 liveBlind = False 

528 else: 

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

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

531 hand.addBlind(a.group('PNAME'), 'small blind', self.clearMoneyString(a.group('SB'))) 

532 else: 

533 # Post dead blinds as ante 

534 hand.addBlind(a.group('PNAME'), 'secondsb', self.clearMoneyString(a.group('SB'))) 

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

536 hand.addBlind(a.group('PNAME'), 'secondsb', self.clearMoneyString(a.group('SBBB'))) 

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

538 if straddles.get(a.group('PNAME')) is None: 

539 straddles[a.group('PNAME')] = self.clearMoneyString(a.group('STRADDLE')) 

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

541 straddles[a.group('PNAME')] = self.clearMoneyString(a.group('STRADDLE')) 

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

543 hand.addBlind(p, 'straddle', amount) 

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

545 if straddles.get(a.group('PNAME')) is None: 

546 hand.addBlind(a.group('PNAME'), 'big blind', self.clearMoneyString(a.group('BB'))) 

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

548 hand.addBlind(a.group('PNAME'), 'button blind', self.clearMoneyString(a.group('BUB'))) 

549 

550 def readHoleCards(self, hand): 

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

552# we need to grab hero's cards 

553 for street in ('PREFLOP', 'DEAL'): 

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

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

556 for found in m: 

557# if m == None: 

558# hand.involved = False 

559# else: 

560 hand.hero = found.group('PNAME') 

561 if 'cards' not in found.group('NEWCARDS'): 

562 newcards = found.group('NEWCARDS').split(' ') 

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

564 

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

566 if not text or street in ('PREFLOP', 'DEAL'): continue # already done these 

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

568 for found in m: 

569 player = found.group('PNAME') 

570 if found.group('NEWCARDS') is None: 

571 newcards = [] 

572 else: 

573 newcards = found.group('NEWCARDS').split(' ') 

574 if found.group('OLDCARDS') is None: 

575 oldcards = [] 

576 else: 

577 oldcards = found.group('OLDCARDS').split(' ') 

578 

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

580 hand.hero = player 

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

582 hand.addHoleCards(street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False) 

583 else: 

584 hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False) 

585 

586 

587 def readAction(self, hand, street): 

588 if hand.gametype['split'] and street in hand.communityStreets: 

589 s = street + '2' 

590 else: 

591 s = street 

592 if not hand.streets[s]: 

593 return 

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

595 for action in m: 

596 acts = action.groupdict() 

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

598 if action.group('ATYPE') == ' folds': 

599 hand.addFold( street, action.group('PNAME')) 

600 elif action.group('ATYPE') == ' checks': 

601 hand.addCheck( street, action.group('PNAME')) 

602 elif action.group('ATYPE') == ' Chooses to EV Cashout' or action.group('ATYPE') == ' Receives Cashout': 

603 hand.addCashout( street, action.group('PNAME')) 

604 elif action.group('ATYPE') == ' calls': 

605 hand.addCall( street, action.group('PNAME'), self.clearMoneyString(action.group('BET')) ) 

606 elif action.group('ATYPE') == ' raises': 

607 if action.group('BETTO') is not None: 

608 hand.addRaiseTo( street, action.group('PNAME'), self.clearMoneyString(action.group('BETTO')) ) 

609 elif action.group('BET') is not None: 

610 hand.addCallandRaise( street, action.group('PNAME'), self.clearMoneyString(action.group('BET')) ) 

611 elif action.group('ATYPE') == ' bets': 

612 hand.addBet( street, action.group('PNAME'), self.clearMoneyString(action.group('BET')) ) 

613 elif action.group('ATYPE') == ' discards': 

614 hand.addDiscard(street, action.group('PNAME'), action.group('BET'), action.group('CARDS')) 

615 elif action.group('ATYPE') == ' stands pat': 

616 hand.addStandsPat( street, action.group('PNAME'), action.group('CARDS')) 

617 else: 

618 log.debug(("DEBUG:") + " " + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group('PNAME'), action.group('ATYPE'))) 

619 

620 

621 def readShowdownActions(self, hand): 

622# TODO: pick up mucks also?? 

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

624 cards = shows.group('CARDS').split(' ') 

625 hand.addShownCards(cards, shows.group('PNAME')) 

626 

627 def readTourneyResults(self, hand): 

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

629 if self.re_Bounty.search(hand.handText) == None: 

630 koAmounts = {} 

631 winner = None 

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

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

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

635 if a.group('PNAME') not in koAmounts: 

636 koAmounts[a.group('PNAME')] = 0 

637 koAmounts[a.group('PNAME')] += 100*Decimal(a.group('AMT')) 

638 hand.endBounty[a.group('PNAME')] = 100*Decimal(a.group('ENDAMT')) 

639 hand.isProgressive = True 

640 

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

642 if m: winner = m.group('PNAME') 

643 

644 if hand.koBounty > 0: 

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

646 if pname == winner: 

647 end = (amount + hand.endBounty[pname]) 

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

649 else: 

650 end = 0 

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

652 else: 

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

654 if a.group('SPLIT') == 'split': 

655 pnames = a.group('PNAME').split(', ') 

656 for pname in pnames: 

657 if pname not in hand.koCounts: 

658 hand.koCounts[pname] = 0 

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

660 else: 

661 if a.group('PNAME') not in hand.koCounts: 

662 hand.koCounts[a.group('PNAME')] = 0 

663 hand.koCounts[a.group('PNAME')] += 1 

664 

665 def readCollectPot(self,hand): 

666 #Bovada walks are calculated incorrectly in converted PokerStars hands 

667 acts, bovadaUncalled_v1, bovadaUncalled_v2, blindsantes, adjustment = hand.actions.get('PREFLOP'), False, False, 0, 0 

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

669 i=0 

670 pre, post = hand.handText.split('*** SUMMARY ***') 

671 hand.cashedOut = self.re_CashedOut.search(pre) != None 

672 if hand.runItTimes==0 and hand.cashedOut == False: 

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

674 pot = self.clearMoneyString(m.group('POT')) 

675 hand.addCollectPot(player=m.group('PNAME'),pot=pot) 

676 i+=1 

677 if i==0: 

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

679 pot = self.clearMoneyString(m.group('POT')) 

680 hand.addCollectPot(player=m.group('PNAME'),pot=pot) 

681 if hand.cashedOut: 

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

683 pot = self.clearMoneyString(m.group('POT')) 

684 hand.addCollectPot(player=m.group('PNAME'),pot=pot) 

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

686 pot = '-' + self.clearMoneyString(m.group('POT')) 

687 hand.addCollectPot(player=m.group('PNAME'),pot=pot) 

688 def readShownCards(self,hand): 

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

690 if m.group('CARDS') is not None: 

691 cards = m.group('CARDS') 

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

693 string = m.group('STRING') 

694 if m.group('STRING2'): 

695 string += '|' + m.group('STRING2') 

696 

697 (shown, mucked) = (False, False) 

698 if m.group('SHOWED') == "showed": shown = True 

699 elif m.group('SHOWED') == "mucked": mucked = True 

700 

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

702 hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked, string=string) 

703 

704 

705 @staticmethod 

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

707 "Returns string to search in windows titles" 

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

709 if type=="tour": 

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

711 log.info("Stars.getTableTitleRe: table_name='%s' tournament='%s' table_number='%s'" % (table_name, tournament, table_number)) 

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

713 return regex