Coverage for PokerTrackerToFpdb.py: 0%

428 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-2012, Chaz Littlejohn 

5#  

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

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

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

9# (at your option) any later version. 

10#  

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

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

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

14# GNU General Public License for more details. 

15#  

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

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

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

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

20 

21from __future__ import division 

22 

23from past.utils import old_div 

24#import L10n 

25#_ = L10n.get_translation() 

26 

27# TODO: straighten out discards for draw games 

28 

29import sys 

30from HandHistoryConverter import * 

31import MergeStructures 

32from decimal_wrapper import Decimal 

33 

34# PokerTracker HH Format 

35 

36class PokerTracker(HandHistoryConverter): 

37 

38 # Class Variables 

39 Structures = None 

40 filetype = "text" 

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

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

43 substitutions = { 

44 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes 

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

46 'PLYR': r'(?P<PNAME>.+?)', 

47 'NUM' : u".,\d", 

48 'CUR': u"(\$|€||\£|)", 

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

50 } 

51 

52 # translations from captured groups to fpdb info strings 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

74 } 

75 

76 limits = { 'NL':'nl', 'No Limit':'nl', 'Pot Limit':'pl', 'PL': 'pl', 'FL': 'fl', 'Limit':'fl', 'LIMIT':'fl' } 

77 games = { # base, category 

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

79 "Texas Hold'em" : ('hold','holdem'), 

80 "Holdem" : ('hold','holdem'), 

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

82 'Omaha Hi' : ('hold','omahahi'), 

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

84 } 

85 sites = { 'EverestPoker Game #' : ('Everest', 16), 

86 'GAME #' : ('iPoker', 14), 

87 'MERGE_GAME #' : ('Merge', 12), 

88 'Merge Game #' : ('Merge', 12), 

89 '** Game ID ' : ('Microgaming', 20), 

90 '** Hand # ' : ('Microgaming', 20) 

91 

92 } 

93 currencies = { u'€':'EUR', '$':'USD', '':'T$', u'£':'GBP' } 

94 

95 

96 re_Site = re.compile(u'(?P<SITE>EverestPoker\sGame\s\#|GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#|\*{2}\s(Game\sID|Hand\s\#)\s)\d+') 

97 # Static regexes 

98 re_GameInfo1 = re.compile(u""" 

99 (?P<SITE>GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#)(?P<HID>[0-9\-]+)(\sVersion:[\d\.]+\s(?P<UNCALLED>Uncalled:Y))?(:?\s+|\s\|\s) 

100 (?P<GAME>Holdem|Texas\sHold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\s\s? 

101 ((?P<LIMIT>PL|NL|FL|No\sLimit|Limit|LIMIT|Pot\sLimit)\s\s?)? 

102 (?P<TOUR>Tournament)? 

103 (\(? # open paren of the stakes 

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

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

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

107 (?P<BLAH>\s-\s[%(LS)s\d\.]+\sCap\s-\s)? # Optional Cap part 

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

109 \)? 

110 )?(\s|\s\|\s) # close paren of the stakes 

111 (?P<DATETIME>.*$) 

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

113 

114 re_GameInfo2 = re.compile(u""" 

115 EverestPoker\sGame\s\#(?P<HID>[0-9]+):\s+ 

116 (?P<TOUR>Tourney\sID:\s(?P<TOURNO>\d+),\s)? 

117 Table\s(?P<TABLE>.+)? 

118 \s-\s 

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

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

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

122 \s-\s 

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

124 (?P<GAME>Hold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\s 

125 (-\s)? 

126 (?P<DATETIME>.*$) 

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

128 

129 re_GameInfo3 = re.compile(u""" 

130 (?P<HID>[0-9]+)(\sVersion:\d)?\sstarting\s\-\s(?P<DATETIME>.*$)\s 

131 \*\*(?P<TOUR>.+(?P<SPEED>(Turbo|Hyper))?\((?P<TOURNO>\d+)\):Table)?\s(?P<TABLE>.+)\s 

132 \[((Multi|Single)\sTable\s)?(?P<GAME>Hold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\]\s 

133 \((?P<SB>[%(NUM)s]+)\|(?P<BB>[%(NUM)s]+)\s(?P<LIMIT>NL|FL|PL)\s\-\s(MTT|SNG|STT|(?P<CURRENCY>%(LS)s|)\s?Cash\sGame)(\sseats:(?P<MAX>\d+))?.*\)\s 

134 (?P<PLAY>Real|Play)\sMoney 

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

136 

137 re_PlayerInfo1 = re.compile(u""" 

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

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

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

141 (?P<BUTTON>\sDEALER)?""" % substitutions, 

142 re.MULTILINE|re.VERBOSE) 

143 

144 re_PlayerInfo2 = re.compile(u""" 

145 ^(\-\s)?(?P<PNAME>.*)\s 

146 sitting\sin\sseat\s(?P<SEAT>[0-9]+)\swith\s 

147 (%(LS)s)?(?P<CASH>[%(NUM)s]+) 

148 (?P<BUTTON>\s?\[Dealer\])?""" % substitutions, 

149 re.MULTILINE|re.VERBOSE) 

150 

151 re_HandInfo_Tour = re.compile(""" 

152 ^Table\s(?P<TABLE>.*),\s(?P<TOURNO>\d+)(,\s\d+)?\s 

153 (?P<TOUR>\(Tournament:\s(.+)?\sBuy\-In:\s(?P<BUYIN>(?P<BIAMT>[%(LS)s\d\.]+)\s?\+?\s?(?P<BIRAKE>[%(LS)s\d\.]+))\)) 

154 """ % substitutions 

155 , re.MULTILINE|re.VERBOSE) 

156 

157 re_HandInfo_Cash = re.compile(""" 

158 ^Table\s(?P<TABLE>[^,]+?)(,\sSeats\s(?P<MAX>\d+))?$""" % substitutions 

159 , re.MULTILINE|re.VERBOSE) 

160 

161 re_Identify = re.compile(u'(EverestPoker\sGame\s\#|GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#|\*{2}\s(Game\sID|Hand\s\#)\s)\d+') 

162 re_SplitHands = re.compile('\n\n\n+?') 

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

164 re_Button = re.compile('The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) 

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

166 re_Board2 = re.compile(r":\s(?P<CARDS>.+)\n") 

167 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) 

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

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

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

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

172 

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

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

175 re_PostSB = re.compile(r"^%(PLYR)s:? ((posts|posted) the small blind( of)?|(Post )?SB) (\- )?%(CUR)s(?P<SB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

176 re_PostBB = re.compile(r"^%(PLYR)s:? ((posts|posted) the big blind( of)?|posts the dead blind of|(Post )?BB) (\- )?%(CUR)s(?P<BB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

177 re_Antes = re.compile(r"^%(PLYR)s:? ((posts|posted) (the )?ante( of)?|(Post )?Ante) (\- )?%(CUR)s(?P<ANTE>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

178 re_PostBoth1 = re.compile(r"^%(PLYR)s:? (posts|Post|(Post )?DB) %(CUR)s(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

179 re_PostBoth2 = re.compile(r"^%(PLYR)s:? posted to play \- %(CUR)s(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

180 re_HeroCards1 = re.compile(r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE) 

181 re_HeroCards2 = re.compile(r"rd(s)? to %(PLYR)s: (?P<OLDCARDS>NONE)?(?P<NEWCARDS>.+)\n" % substitutions, re.MULTILINE) 

182 re_Action1 = re.compile(r"""^%(PLYR)s:?(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sBet|\sCheck|\sRaise(\sto)?|\sCall|\sFold|\sAllin)(?P<RAISETO>\s\(NF\))?(\sto)?(\s%(CUR)s(?P<BET>[%(NUM)s]+))?\s*(and\sis\sall.in)?(and\shas\sreached\sthe\s\[%(CUR)s\d\.,]+\scap)?(\son|\scards?)?(\s\[(?P<CARDS>.+?)\])?\s*$"""% substitutions, re.MULTILINE|re.VERBOSE) 

183 re_Action2 = re.compile(r""" 

184 ^%(PLYR)s(?P<ATYPE>\sbet|\schecked|\sraised(\sto)?|\scalled|\sfolded|\swent\sall\-in) 

185 (\s(\-\s)?%(CUR)s(?P<BET>[%(NUM)s]+))?\s*$""" 

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

187 re_ShownCards1 = re.compile("^%(PLYR)s:? (?P<SHOWED>shows|Shows|mucked) \[(?P<CARDS>.*)\]" % substitutions, re.MULTILINE) 

188 re_ShownCards2 = re.compile("^%(PLYR)s (?P<SHOWED>shows|mucks): (?P<CARDS>.+)\n" % substitutions, re.MULTILINE) 

189 re_CollectPot1 = re.compile(r"^%(PLYR)s:? (collects|wins) %(CUR)s(?P<POT>[%(NUM)s]+)" % substitutions, re.MULTILINE) 

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

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

192 re_Tournament = re.compile('\(Tournament:') 

193 re_Hole = re.compile(r"\*\*\sDealing\scard") 

194 re_Currency = re.compile(r"\s\-\s(?P<CURRENCY>%(CUR)s)[%(NUM)s]+\s(Max|Min)" % substitutions) 

195 re_Max = re.compile(r"(\s(?P<MAX>(HU|\d+\sSeat))\s)") 

196 re_FastFold = re.compile(r"^%(PLYR)s\sQuick\sFolded" % substitutions, re.MULTILINE) 

197 

198 def compilePlayerRegexs(self, hand): 

199 pass 

200 

201 def readSupportedGames(self): 

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

203 ["ring", "hold", "pl"], 

204 ["ring", "hold", "fl"], 

205 

206 ["tour", "hold", "nl"], 

207 ["tour", "hold", "pl"], 

208 ["tour", "hold", "fl"], 

209 ] 

210 

211 def determineGameType(self, handText): 

212 m = self.re_Site.search(handText) 

213 if not m: 

214 tmp = handText[0:200] 

215 log.error(("PokerTrackerToFpdb.determineGameType: '%s'") % tmp) 

216 raise FpdbParseError 

217 

218 self.sitename = self.sites[m.group('SITE')][0] 

219 self.siteId = self.sites[m.group('SITE')][1] # Needs to match id entry in Sites database 

220 

221 info = {} 

222 if self.sitename in ('iPoker', 'Merge'): 

223 m = self.re_GameInfo1.search(handText) 

224 elif self.sitename=='Everest': 

225 m = self.re_GameInfo2.search(handText) 

226 elif self.sitename == 'Microgaming': 

227 m = self.re_GameInfo3.search(handText) 

228 if not m: 

229 tmp = handText[0:200] 

230 log.error(("PokerTrackerToFpdb.determineGameType: '%s'") % tmp) 

231 raise FpdbParseError 

232 

233 mg = m.groupdict() 

234 #print 'DEBUG determineGameType', '%r' % mg 

235 if 'LIMIT' in mg and mg['LIMIT'] != None: 

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

237 if 'GAME' in mg: 

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

239 if mg['LIMIT'] == None: 

240 if info['category']=='omahahi': 

241 info['limitType'] = 'pl' 

242 elif info['category']=='holdem': 

243 info['limitType'] = 'nl' 

244 if 'SB' in mg: 

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

246 if 'BB' in mg: 

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

248 if 'CURRENCY' in mg and mg['CURRENCY'] is not None: 

249 if self.sitename == 'Microgaming' and not mg['CURRENCY']: 

250 m1 = self.re_Currency.search(mg['TABLE']) 

251 if m1: mg['CURRENCY'] = m1.group('CURRENCY') 

252 if self.sitename == 'iPoker' and not mg['CURRENCY']: 

253 m1 = self.re_PlayerInfo1.search(handText) 

254 if m1: mg['CURRENCY'] = m1.group('CURRENCY') 

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

256 if 'MIXED' in mg: 

257 if mg['MIXED'] is not None: info['mix'] = self.mixes[mg['MIXED']] 

258 

259 if 'TOUR' in mg and mg['TOUR'] is not None: 

260 info['type'] = 'tour' 

261 info['currency'] = 'T$' 

262 else: 

263 info['type'] = 'ring' 

264 

265 

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

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

268 try: 

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

270 info['sb'] = self.Lim_Blinds[bb][0] 

271 info['bb'] = self.Lim_Blinds[bb][1] 

272 except KeyError: 

273 tmp = handText[0:200] 

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

275 raise FpdbParseError 

276 else: 

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

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

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

280 

281 return info 

282 

283 def readHandInfo(self, hand): 

284 info, m = {}, None 

285 if self.sitename in ('iPoker', 'Merge'): 

286 m3 = self.re_Tournament.search(hand.handText,re.DOTALL) 

287 if m3: 

288 m = self.re_HandInfo_Tour.search(hand.handText,re.DOTALL) 

289 else: 

290 m = self.re_HandInfo_Cash.search(hand.handText,re.DOTALL) 

291 m2 = self.re_GameInfo1.search(hand.handText) 

292 elif self.sitename=='Everest': 

293 m2 = self.re_GameInfo2.search(hand.handText) 

294 elif self.sitename=='Microgaming': 

295 m2 = self.re_GameInfo3.search(hand.handText) 

296 if (m is None and self.sitename not in ('Everest', 'Microgaming')) or m2 is None: 

297 tmp = hand.handText[0:200] 

298 log.error(("PokerTrackerToFpdb.readHandInfo: '%s'") % tmp) 

299 raise FpdbParseError 

300 

301 if self.sitename not in ('Everest', 'Microgaming'): 

302 info.update(m.groupdict()) 

303 info.update(m2.groupdict()) 

304 

305 if self.sitename != 'Everest' and info.get('UNCALLED') is None: 

306 hand.setUncalledBets(True) 

307 

308 #print 'readHandInfo', info 

309 for key in info: 

310 if key == 'DATETIME': 

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

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

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

314 if self.sitename in ('iPoker', 'Microgaming'): 

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

316 elif self.sitename == 'Merge': 

317 m1 = self.re_DateTime2.finditer(info[key]) 

318 elif self.sitename == 'Everest': 

319 m1 = self.re_DateTime3.finditer(info[key]) 

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

321 for a in m1: 

322 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')) 

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

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

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

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

327 if key == 'HID': 

328 if self.sitename == 'Merge': 

329 hand.handid = info[key][:8] + info[key][9:] 

330 else: 

331 hand.handid = info[key] 

332 if key == 'TOURNO': 

333 hand.tourNo = info[key] 

334 if key == 'BUYIN': 

335 if hand.tourNo!=None: 

336 tourneyname = '' 

337 if self.sitename == 'Merge': 

338 if self.Structures is None: 

339 self.Structures = MergeStructures.MergeStructures() 

340 tourneyname = re.split(",", m.group('TABLE'))[0].strip() 

341 structure = self.Structures.lookupSnG(tourneyname, hand.startTime) 

342 if structure!=None: 

343 hand.buyin = int(100*structure['buyIn']) 

344 hand.fee = int(100*structure['fee']) 

345 hand.buyinCurrency=structure['currency'] 

346 hand.maxseats = structure['seats'] 

347 hand.isSng = True 

348 else: 

349 #print 'DEBUG', 'no match for tourney %s tourNo %s' % (tourneyname, hand.tourNo) 

350 hand.buyin = 0 

351 hand.fee = 0 

352 hand.buyinCurrency="NA" 

353 hand.maxseats = None 

354 if self.sitename != 'Merge' or hand.buyin==0: 

355 if info[key] == 'Freeroll' or 'Free' in tourneyname: 

356 hand.buyin = 0 

357 hand.fee = 0 

358 hand.buyinCurrency = "FREE" 

359 else: 

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

361 hand.buyinCurrency="USD" 

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

363 hand.buyinCurrency="GBP" 

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

365 hand.buyinCurrency="EUR" 

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

367 hand.buyinCurrency="play" 

368 else: 

369 #FIXME: handle other currencies, play money 

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

371 raise FpdbParseError 

372 

373 info['BIAMT'] = info['BIAMT'].strip(u'$€£') 

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

375 

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

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

378 if key == 'TABLE': 

379 if hand.gametype['type'] == 'tour': 

380 hand.tablename = '0' 

381 elif hand.gametype['type'] == 'tour' and self.sitename == 'Microgaming': 

382 hand.tablename = info[key] 

383 else: 

384 hand.tablename = re.split(",", info[key])[0] 

385 hand.tablename = hand.tablename.strip() 

386 if 'Blaze' in hand.tablename: 

387 hand.gametype['fast'] = True 

388 if self.sitename == 'Microgaming': 

389 m3 = self.re_Max.search(hand.tablename) 

390 if m3 and m3.group('MAX'): 

391 if m3.group('MAX')=='HU': 

392 hand.maxseats = 2 

393 elif len(m3.group('MAX').split(' '))==2: 

394 hand.maxseats = int(m3.group('MAX').split(' ')[0]) 

395 if key == 'BUTTON': 

396 hand.buttonpos = info[key] 

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

398 seats = int(info[key]) 

399 if seats <=10: 

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

401 

402 if key == 'PLAY' and info['PLAY'] is not None and info['PLAY']=='Play': 

403# hand.currency = 'play' # overrides previously set value 

404 hand.gametype['currency'] = 'play' 

405 

406 if self.re_FastFold.search(hand.handText): 

407 hand.fastFold = True 

408 

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

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

411 

412 def readButton(self, hand): 

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

414 if m: 

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

416 else: 

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

418 

419 def readPlayerStacks(self, hand): 

420 if self.sitename != 'Microgaming': 

421 m = self.re_PlayerInfo1.finditer(hand.handText) 

422 else: 

423 m = self.re_PlayerInfo2.finditer(hand.handText) 

424 for a in m: 

425 #print a.group('SEAT'), a.group('PNAME'), a.group('CASH') 

426 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) 

427 if a.group('BUTTON')!=None: 

428 hand.buttonpos = int(a.group('SEAT')) 

429 if len(hand.players)==1: 

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

431 

432 def markStreets(self, hand): 

433 

434 # PREFLOP = ** Dealing down cards ** 

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

436 if self.sitename == 'Microgaming': 

437 m = re.search(r"\*\* Dealing ca(?P<PREFLOP>.+(?=\*\* Dealing the flop)|.+)" 

438 r"(\*\* Dealing the flop(?P<FLOP>:\s.+(?=\*\* Dealing the turn)|.+))?" 

439 r"(\*\* Dealing the turn(?P<TURN>:\s.+(?=\*\* Dealing the river)|.+))?" 

440 r"(\*\* Dealing the river(?P<RIVER>:\s.+))?", hand.handText,re.DOTALL) 

441 else: 

442 m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" 

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

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

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

446 hand.addStreets(m) 

447 

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

449 if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP) 

450 #print "DEBUG readCommunityCards:", street, hand.streets.group(street) 

451 if self.sitename == 'Microgaming': 

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

453 cards = [c.replace('10', 'T').strip() for c in m.group('CARDS').replace(' of ', '').split(', ')] 

454 else: 

455 m = self.re_Board1.search(hand.streets[street]) 

456 if self.sitename=='iPoker': 

457 cards = [c[1:].replace('10', 'T') + c[0].lower() for c in m.group('CARDS').split(' ')] 

458 else: 

459 cards = [c.replace('10', 'T').strip() for c in m.group('CARDS').split(' ')] 

460 hand.setCommunityCards(street, cards) 

461 

462 def readAntes(self, hand): 

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

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

465 for player in m: 

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

467 self.adjustMergeTourneyStack(hand, player.group('PNAME'), player.group('ANTE')) 

468 hand.addAnte(player.group('PNAME'), player.group('ANTE')) 

469 

470 def readBlinds(self, hand): 

471 liveBlind, bb, sb = True, None, None 

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

473 sb = self.clearMoneyString(a.group('SB')) 

474 if liveBlind: 

475 self.adjustMergeTourneyStack(hand, a.group('PNAME'), a.group('SB')) 

476 hand.addBlind(a.group('PNAME'), 'small blind', sb) 

477 if not hand.gametype['sb']: 

478 hand.gametype['sb'] = sb 

479 liveBlind = False 

480 elif hand.gametype['type'] == 'tour': 

481 self.adjustMergeTourneyStack(hand, a.group('PNAME'), a.group('SB')) 

482 if not hand.gametype['bb']: 

483 hand.gametype['bb'] = sb 

484 hand.addBlind(a.group('PNAME'), 'big blind', sb) 

485 else: 

486 # Post dead blinds as ante 

487 self.adjustMergeTourneyStack(hand, a.group('PNAME'), a.group('SB')) 

488 hand.addBlind(a.group('PNAME'), 'secondsb', sb) 

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

490 bb = self.clearMoneyString(a.group('BB')) 

491 self.adjustMergeTourneyStack(hand, a.group('PNAME'), a.group('BB')) 

492 if not hand.gametype['bb']: 

493 hand.gametype['bb'] = bb 

494 hand.addBlind(a.group('PNAME'), 'big blind', bb) 

495 else: 

496 both = Decimal(hand.gametype['bb']) + old_div(Decimal(hand.gametype['bb']),2) 

497 if both == Decimal(a.group('BB')): 

498 hand.addBlind(a.group('PNAME'), 'both', bb) 

499 else: 

500 hand.addBlind(a.group('PNAME'), 'big blind', bb) 

501 

502 if self.sitename == 'Microgaming': 

503 for a in self.re_PostBoth2.finditer(hand.handText): 

504 if self.clearMoneyString(a.group('SBBB')) == hand.gametype['sb']: 

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

506 else: 

507 bet = self.clearMoneyString(a.group('SBBB')) 

508 amount = str(Decimal(bet) + old_div(Decimal(bet),2)) 

509 hand.addBlind(a.group('PNAME'), 'both', amount) 

510 for a in self.re_Action2.finditer(self.re_Hole.split(hand.handText)[0]): 

511 if a.group('ATYPE') == ' went all-in': 

512 amount = Decimal(self.clearMoneyString(a.group('BET'))) 

513 player = a.group('PNAME') 

514 if bb is None: 

515 hand.addBlind(player, 'big blind', self.clearMoneyString(a.group('BET'))) 

516 self.allInBlind(hand, 'PREFLOP', a, 'big blind') 

517 elif sb is None: 

518 hand.addBlind(player, 'small blind', self.clearMoneyString(a.group('BET'))) 

519 self.allInBlind(hand, 'PREFLOP', a, 'small blind') 

520 else: 

521 for a in self.re_PostBoth1.finditer(hand.handText): 

522 self.adjustMergeTourneyStack(hand, a.group('PNAME'), a.group('SBBB')) 

523 if (Decimal(str(hand.sb)) == Decimal(self.clearMoneyString(a.group('SBBB')))): 

524 hand.addBlind(a.group('PNAME'), 'small blind', self.clearMoneyString(a.group('SBBB'))) 

525 else: 

526 hand.addBlind(a.group('PNAME'), 'both', self.clearMoneyString(a.group('SBBB'))) 

527 

528 # FIXME 

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

530 # see http://sourceforge.net/apps/mantisbt/fpdb/view.php?id=115 

531 if hand.gametype['type'] == 'tour' and self.sitename in ('Merge', 'iPoker'): 

532 if hand.gametype['sb'] == None and hand.gametype['bb'] == None: 

533 hand.gametype['sb'] = "1" 

534 hand.gametype['bb'] = "2" 

535 elif hand.gametype['sb'] == None: 

536 hand.gametype['sb'] = str(old_div(int(Decimal(hand.gametype['bb'])),2)) 

537 elif hand.gametype['bb'] == None: 

538 hand.gametype['bb'] = str(int(Decimal(hand.gametype['sb']))*2) 

539 if old_div(int(Decimal(hand.gametype['bb'])),2) != int(Decimal(hand.gametype['sb'])): 

540 if old_div(int(Decimal(hand.gametype['bb'])),2) < int(Decimal(hand.gametype['sb'])): 

541 hand.gametype['bb'] = str(int(Decimal(hand.gametype['sb']))*2) 

542 else: 

543 hand.gametype['sb'] = str(old_div(int(Decimal(hand.gametype['bb'])),2)) 

544 

545 def readHoleCards(self, hand): 

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

547# we need to grab hero's cards 

548 if self.sitename != 'Microgaming': 

549 re_HeroCards = self.re_HeroCards1 

550 else: 

551 re_HeroCards = self.re_HeroCards2 

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

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

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

555 for found in m: 

556# if m == None: 

557# hand.involved = False 

558# else: 

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

560 if self.sitename=='iPoker': 

561 newcards = [c[1:].replace('10', 'T') + c[0].lower() for c in found.group('NEWCARDS').split(' ')] 

562 elif self.sitename=='Microgaming': 

563 newcards = [c.replace('10', 'T').strip() for c in found.group('NEWCARDS').replace(' of ', '').split(', ')] 

564 else: 

565 newcards = [c.replace('10', 'T').strip() for c in found.group('NEWCARDS').split(' ')] 

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

567 

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

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

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

571 for found in m: 

572 player = found.group('PNAME') 

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

574 newcards = [] 

575 else: 

576 if self.sitename=='iPoker': 

577 newcards = [c[1:].replace('10', 'T') + c[0].lower() for c in found.group('NEWCARDS').split(' ')] 

578 elif self.sitename=='Microgaming': 

579 newcards = [c.replace('10', 'T').strip() for c in found.group('NEWCARDS').replace(' of ', '').split(', ')] 

580 else: 

581 newcards = [c.replace('10', 'T').strip() for c in found.group('NEWCARDS').split(' ')] 

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

583 oldcards = [] 

584 else: 

585 if self.sitename=='iPoker': 

586 oldcards = [c[1:].replace('10', 'T') + c[0].lower() for c in found.group('OLDCARDS').split(' ')] 

587 else: 

588 oldcards = [c.replace('10', 'T').strip() for c in found.group('OLDCARDS').split(' ')] 

589 

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

591 

592 

593 def readAction(self, hand, street): 

594 if self.sitename != 'Microgaming': 

595 m = self.re_Action1.finditer(hand.streets[street]) 

596 else: 

597 m = self.re_Action2.finditer(hand.streets[street]) 

598 curr_pot = Decimal('0') 

599 for action in m: 

600 acts = action.groupdict() 

601 #print "DEBUG: acts: %s" %acts 

602 if action.group('ATYPE') in (' folds', ' Fold', ' folded'): 

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

604 elif action.group('ATYPE') in (' checks', ' Check', ' checked'): 

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

606 elif action.group('ATYPE') in (' calls', ' Call', ' called'): 

607 hand.addCall( street, action.group('PNAME'), action.group('BET') ) 

608 elif action.group('ATYPE') in (' raises', ' Raise', ' raised', ' raised to', ' Raise to'): 

609 amount = Decimal(self.clearMoneyString(action.group('BET'))) 

610 if self.sitename == 'Merge': 

611 hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) 

612 elif self.sitename == 'Microgaming' or action.group('ATYPE')==' Raise to': 

613 hand.addCallandRaise(street, action.group('PNAME'), action.group('BET') ) 

614 else: 

615 if curr_pot > amount: 

616 hand.addCall( street, action.group('PNAME'), action.group('BET') ) 

617 #elif not action.group('RAISETO') and action.group('ATYPE')==' Raise': 

618 # hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) 

619 else: 

620 hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) 

621 curr_pot = amount 

622 elif action.group('ATYPE') in (' bets', ' Bet', ' bet'): 

623 if self.sitename == 'Microgaming' and street in ('PREFLOP', 'THIRD', 'DEAL'): 

624 hand.addCallandRaise(street, action.group('PNAME'), action.group('BET') ) 

625 else: 

626 hand.addBet( street, action.group('PNAME'), action.group('BET') ) 

627 curr_pot = Decimal(self.clearMoneyString(action.group('BET'))) 

628 elif action.group('ATYPE') in (' Allin', ' went all-in'): 

629 amount = Decimal(self.clearMoneyString(action.group('BET'))) 

630 hand.addAllIn(street, action.group('PNAME'), action.group('BET')) 

631 if curr_pot > amount and curr_pot > Decimal('0') and self.sitename == 'Microgaming': 

632 hand.setUncalledBets(False) 

633 curr_pot = amount 

634 else: 

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

636 

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

638 if street in ('PREFLOP', 'DEAL'): 

639 player = action.group('PNAME') 

640 if hand.stacks[player]==0: 

641 hand.setUncalledBets(True) 

642 

643 def adjustMergeTourneyStack(self, hand, player, amount): 

644 if self.sitename == 'Merge': 

645 amount = Decimal(self.clearMoneyString(amount)) 

646 if hand.gametype['type'] == 'tour': 

647 for p in hand.players: 

648 if p[1]==player: 

649 stack = Decimal(p[2]) 

650 stack += amount 

651 p[2] = str(stack) 

652 hand.stacks[player] += amount 

653 

654 def readCollectPot(self,hand): 

655 if self.sitename == 'Microgaming': 

656 for m in self.re_CollectPot2.finditer(hand.handText): 

657 hand.addCollectPot(player=m.group('PNAME'),pot=re.sub(u',',u'',m.group('POT'))) 

658 else: 

659 for m in self.re_CollectPot1.finditer(hand.handText): 

660 hand.addCollectPot(player=m.group('PNAME'),pot=re.sub(u',',u'',m.group('POT'))) 

661 

662 def readShowdownActions(self, hand): 

663 pass 

664 

665 def readShownCards(self,hand): 

666 found = [] 

667 if self.sitename=='Microgaming': 

668 re_ShownCards = self.re_ShownCards2 

669 else: 

670 re_ShownCards = self.re_ShownCards1 

671 for m in re_ShownCards.finditer(hand.handText): 

672 if m.group('CARDS') is not None and m.group('PNAME') not in found: 

673 if self.sitename=='iPoker': 

674 cards = [c[1:].replace('10', 'T') + c[0].lower() for c in m.group('CARDS').split(' ')] 

675 elif self.sitename=='Microgaming': 

676 cards = [c.replace('10', 'T').strip() for c in m.group('CARDS').replace(' of ', '').split(', ')] 

677 else: 

678 cards = [c.replace('10', 'T').strip() for c in m.group('CARDS').split(' ')] 

679 

680 (shown, mucked) = (False, False) 

681 if m.group('SHOWED') in ('shows', 'Shows'): shown = True 

682 elif m.group('SHOWED') in ('mucked', 'mucks'): mucked = True 

683 found.append(m.group('PNAME')) 

684 

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

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

687