Coverage for MergeToFpdb.py: 0%

443 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 2010-2011, Matthew Boss 

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 

22from __future__ import division 

23 

24from past.utils import old_div 

25#import L10n 

26#_ = L10n.get_translation() 

27 

28# TODO: 

29# 

30# -- Assumes that the currency of ring games is USD 

31# -- Only accepts 'realmoney="true"' 

32# -- A hand's time-stamp does not record seconds past the minute (a limitation of the history format) 

33# -- hand.maxseats can only be guessed at 

34# -- Cannot parse tables that run it twice 

35# -- Cannot parse hands in which someone is all in in one of the blinds. 

36 

37import sys 

38from HandHistoryConverter import * 

39import MergeStructures 

40from decimal_wrapper import Decimal 

41 

42 

43class Merge(HandHistoryConverter): 

44 sitename = "Merge" 

45 filetype = "text" 

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

47 siteId = 12 

48 copyGameHeader = True 

49 Structures = MergeStructures.MergeStructures() 

50 

51 limits = { 'No Limit':'nl', 'No Limit ':'nl', 'Limit':'fl', 'Pot Limit':'pl', 'Pot Limit ':'pl', 'Half Pot Limit':'hp'} 

52 games = { # base, category 

53 'Holdem' : ('hold','holdem'), 

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

55 'Omaha H/L8' : ('hold','omahahilo'), 

56 '2-7 Lowball' : ('draw','27_3draw'), 

57 'A-5 Lowball' : ('draw','a5_3draw'), 

58 'Badugi' : ('draw','badugi'), 

59 '5-Draw w/Joker' : ('draw','fivedraw'), 

60 '5-Draw' : ('draw','fivedraw'), 

61 '7-Stud' : ('stud','studhi'), 

62 '7-Stud H/L8' : ('stud','studhilo'), 

63 '5-Stud' : ('stud','5_studhi'), 

64 'Razz' : ('stud','razz'), 

65 } 

66 

67 mixes = { 

68 'HA' : 'ha', 

69 'RASH' : 'rash', 

70 'HO' : 'ho', 

71 'SHOE' : 'shoe', 

72 'HORSE' : 'horse', 

73 'HOSE' : 'hose', 

74 'HAR' : 'har' 

75 } 

76 

77 Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), 

78 '0.20': ('0.05', '0.10'), 

79 '0.25': ('0.05', '0.10'), '0.50': ('0.10', '0.25'), 

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

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

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

83 '6.00': ('1.50', '3.00'), '6': ('1.50', '3.00'), 

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

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

86 '12.00': ('3.00', '6.00'), '12': ('3.00', '6.00'), 

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

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

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

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

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

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

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

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

95 } 

96 

97 Multigametypes = { '1': ('hold','holdem'), 

98 '2': ('hold','holdem'), 

99 '4': ('hold','omahahi'), 

100 '9': ('hold', 'holdem'), 

101 '23': ('hold', 'holdem'), 

102 '34': ('hold','omahahilo'), 

103 '35': ('hold','omahahilo'), 

104 '37': ('hold','omahahilo'), 

105 '38': ('stud','studhi'), 

106 '39': ('stud','studhi'), 

107 '41': ('stud','studhi'), 

108 '42': ('stud','studhi'), 

109 '43': ('stud','studhilo'), 

110 '45': ('stud','studhilo'), 

111 '46': ('stud','razz'), 

112 '47': ('stud','razz'), 

113 '49': ('stud','razz') 

114 } 

115 

116 

117 # Static regexes 

118 re_Identify = re.compile(u'<game\sid=\"[0-9]+\-[0-9]+\"\sstarttime') 

119 re_SplitHands = re.compile(r'</game>\n+(?=<)') 

120 re_TailSplitHands = re.compile(r'(</game>)') 

121 re_GameInfo = re.compile(r'<description type="(?P<GAME>Holdem|Omaha|Omaha|Omaha\sH/L8|2\-7\sLowball|A\-5\sLowball|Badugi|5\-Draw\sw/Joker|5\-Draw|7\-Stud|7\-Stud\sH/L8|5\-Stud|Razz|HORSE|RASH|HA|HO|SHOE|HOSE|HAR)(?P<TYPE>\sTournament)?" stakes="(?P<LIMIT>(No Limit|Limit|Pot Limit|Half Pot Limit)\s?)(\sLevel\s\d+\sBlinds)?(\s\(?\$?(?P<SB>[.0-9]+)?/?\$?(?P<BB>[.0-9]+)?(?P<blah>.*)\)?)?"(\sversion="\d+")?\s?/>\s?', re.MULTILINE) 

122 # <game id="46154255-645" starttime="20111230232051" numholecards="2" gametype="1" seats="9" realmoney="false" data="20111230|Play Money (46154255)|46154255|46154255-645|false"> 

123 # <game id="46165919-1" starttime="20111230161824" numholecards="2" gametype="23" seats="10" realmoney="true" data="20111230|Fun Step 1|46165833-1|46165919-1|true"> 

124 # <game id="46289039-1" starttime="20120101200100" numholecards="2" gametype="23" seats="9" realmoney="true" data="20120101|$200 Freeroll - NL Holdem - 20%3A00|46245544-1|46289039-1|true"> 

125 re_HandInfo = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>.+?)" numholecards="[0-9]+" gametype="[0-9]+" (stakes=".*" )?(multigametype="(?P<MULTIGAMETYPE1>\d+)" )?(seats="(?P<SEATS>[0-9]+)" )?realmoney="(?P<REALMONEY>(true|false))" (multigametype="(?P<MULTIGAMETYPE2>\d+)" )?(data="[0-9]+[|:](?P<TABLENAME>[^|:]+)[|:](?P<TDATA>[^|:]+)[|:]?)?.*>', re.MULTILINE) 

126 re_Button = re.compile(r'<players dealer="(?P<BUTTON>[0-9]+)"\s?>') 

127 re_PlayerInfo = re.compile(r'<player seat="(?P<SEAT>[0-9]+)" nickname="(?P<PNAME>.+)" balance="\$?(?P<CASH>[.0-9]+)" dealtin="(?P<DEALTIN>(true|false))" />', re.MULTILINE) 

128 re_Board = re.compile(r'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re.MULTILINE) 

129 re_Buyin = re.compile(r'\$(?P<BUYIN>[.,0-9]+)\s(?P<TYPE>Freeroll|Satellite|Guaranteed)?', re.MULTILINE) 

130 re_secondGame = re.compile(r'\$?(?P<SB>[.0-9]+)?/?\$?(?P<BB>[.0-9]+)', re.MULTILINE) 

131 

132 # The following are also static regexes: there is no need to call 

133 # compilePlayerRegexes (which does nothing), since players are identified 

134 # not by name but by seat number 

135 re_PostSB = re.compile(r'<event sequence="[0-9]+" type="SMALL_BLIND" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"\s?/>', re.MULTILINE) 

136 re_PostBB = re.compile(r'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"\s?/>', re.MULTILINE) 

137 re_PostBoth = re.compile(r'<event sequence="[0-9]+" type="RETURN_BLIND" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"\s?/>', re.MULTILINE) 

138 re_Antes = re.compile(r'<event sequence="[0-9]+" type="ANTE" (?P<TIMESTAMP>timestamp="\d+" )?player="(?P<PSEAT>[0-9])" amount="(?P<ANTE>[.0-9]+)"\s?/>', re.MULTILINE) 

139 re_BringIn = re.compile(r'<event sequence="[0-9]+" type="BRING_IN" (?P<TIMESTAMP>timestamp="\d+" )?player="(?P<PSEAT>[0-9])" amount="(?P<BRINGIN>[.0-9]+)"\s?/>', re.MULTILINE) 

140 re_HeroCards = re.compile(r'<cards type="(HOLE|DRAW_DRAWN_CARDS)" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re.MULTILINE) 

141 re_Action = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT|DRAW|COMPLETE)"( timestamp="(?P<TIMESTAMP>[0-9]+)")? player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?( text="(?P<TXT>.+)")?\s?/>', re.MULTILINE) 

142 re_AllActions = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT|DRAW|COMPLETE|BIG_BLIND|INITIAL_BLIND|SMALL_BLIND|RETURN_BLIND|BRING_IN|ANTE)"( timestamp="(?P<TIMESTAMP>[0-9]+)")? player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?( text="(?P<TXT>.+)")?\s?/>', re.MULTILINE) 

143 re_CollectPot = re.compile(r'<winner amount="(?P<POT>[.0-9]+)" uncalled="(?P<UNCALLED>false|true)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re.MULTILINE) 

144 re_SitsOut = re.compile(r'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE) 

145 re_ShownCards = re.compile(r'<cards type="(?P<SHOWED>SHOWN|MUCKED)" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE) 

146 re_Connection = re.compile(r'<event sequence="[0-9]+" type="(?P<TYPE>RECONNECTED|DISCONNECTED)" timestamp="[0-9]+" player="[0-9]"\s?/>', re.MULTILINE) 

147 re_Cancelled = re.compile(r'<event sequence="\d+" type="GAME_CANCELLED" timestamp="\d+"\s?/>', re.MULTILINE) 

148 re_LeaveTable = re.compile(r'<event sequence="\d+" type="LEAVE" timestamp="\d+" player="\d"\s?/>', re.MULTILINE) 

149 re_PlayerOut = re.compile(r'<event sequence="\d+" type="(PLAYER_OUT|LEAVE)" timestamp="\d+" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE) 

150 re_EndOfHand = re.compile(r'<round id="END_OF_GAME"', re.MULTILINE) 

151 re_DateTime = re.compile(r'(?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) 

152 re_PlayMoney = re.compile(r'realmoney="false"') 

153 

154 def compilePlayerRegexs(self, hand): 

155 pass 

156 

157 def playerNameFromSeatNo(self, seatNo, hand): 

158 # This special function is required because Merge Poker records 

159 # actions by seat number (0 based), not by the player's name 

160 for p in hand.players: 

161 if p[0] == int(seatNo)+1: 

162 return p[1] 

163 

164 def readSupportedGames(self): 

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

166 ["ring", "hold", "pl"], 

167 ["ring", "hold", "fl"], 

168 ["ring", "hold", "hp"], 

169 

170 ["ring", "stud", "fl"], 

171 ["ring", "stud", "pl"], 

172 ["ring", "stud", "nl"], 

173 

174 ["ring", "draw", "fl"], 

175 ["ring", "draw", "pl"], 

176 ["ring", "draw", "nl"], 

177 ["ring", "draw", "hp"], 

178 

179 ["tour", "hold", "nl"], 

180 ["tour", "hold", "pl"], 

181 ["tour", "hold", "fl"], 

182 

183 ["tour", "stud", "fl"], 

184 ["tour", "stud", "pl"], 

185 ["tour", "stud", "nl"], 

186 

187 ["tour", "draw", "fl"], 

188 ["tour", "draw", "pl"], 

189 ["tour", "draw", "nl"], 

190 ] 

191 

192 def parseHeader(self, handText, whole_file): 

193 gametype = self.determineGameType(handText) 

194 if gametype is None: 

195 gametype = self.determineGameType(whole_file) 

196 if gametype is None: 

197 if not re.search('<description', whole_file): 

198 raise FpdbHandPartial("Partial hand history: No <desription> tag") 

199 else: 

200 tmp = handText[0:200] 

201 log.error(("MergeToFpdb.determineGameType: '%s'") % tmp) 

202 raise FpdbParseError 

203 else: 

204 if 'mix' in gametype and gametype['mix']!=None: 

205 self.mergeMultigametypes(handText) 

206 return gametype 

207 

208 def determineGameType(self, handText): 

209 """return dict with keys/values: 

210 'type' in ('ring', 'tour') 

211 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl', 'hp') 

212 'base' in ('hold', 'stud', 'draw') 

213 'category' in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi') 

214 'hilo' in ('h','l','s') 

215 'smallBlind' int? 

216 'bigBlind' int? 

217 'smallBet' 

218 'bigBet' 

219 'currency' in ('USD', 'EUR', 'T$', <countrycode>) 

220or None if we fail to get the info """ 

221 

222 m = self.re_GameInfo.search(handText) 

223 if not m: return None 

224 

225 self.info = {} 

226 mg = m.groupdict() 

227 #print "DEBUG: mg: %s" % mg 

228 

229 if 'LIMIT' in mg: 

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

231 if 'GAME' in mg: 

232 if mg['GAME'] in self.mixes: 

233 self.info['mix'] = self.mixes[mg['GAME']] 

234 self.mergeMultigametypes(handText) 

235 else: 

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

237 if 'SB' in mg: 

238 self.info['sb'] = mg['SB'] 

239 if 'BB' in mg: 

240 self.info['bb'] = mg['BB'] 

241 self.info['secondGame'] = False 

242 if mg['blah'] is not None: 

243 if self.re_secondGame.search(mg['blah']): 

244 self.info['secondGame'] = True 

245 if ' Tournament' == mg['TYPE']: 

246 self.info['type'] = 'tour' 

247 self.info['currency'] = 'T$' 

248 else: 

249 self.info['type'] = 'ring' 

250 if self.re_PlayMoney.search(handText): 

251 self.info['currency'] = 'play' 

252 else: 

253 self.info['currency'] = 'USD' 

254 

255 if self.info['limitType'] == 'fl' and self.info['bb'] is not None and self.info['type'] == 'ring': 

256 try: 

257 self.info['sb'] = self.Lim_Blinds[mg['BB']][0] 

258 self.info['bb'] = self.Lim_Blinds[mg['BB']][1] 

259 except KeyError: 

260 tmp = handText[0:200] 

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

262 raise FpdbParseError 

263 

264 return self.info 

265 

266 def readHandInfo(self, hand): 

267 m = self.re_HandInfo.search(hand.handText) 

268 if m is None: 

269 tmp = hand.handText[0:200] 

270 log.error(("MergeToFpdb.readHandInfo: '%s'") % tmp) 

271 raise FpdbParseError 

272 

273 #print "DEBUG: mg: %s" % m.groupdict() 

274 self.determineErrorType(hand, None) 

275 

276 hand.handid = m.group('HID1') + m.group('HID2') 

277 

278 m1 = self.re_DateTime.search(m.group('DATETIME')) 

279 if m1: 

280 mg = m1.groupdict() 

281 datetimestr = "%s/%s/%s %s:%s:%s" % (mg['Y'], mg['M'],mg['D'],mg['H'],mg['MIN'],mg['S']) 

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

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

284 else: 

285 hand.startTime = datetime.datetime.strptime(m.group('DATETIME')[:14],'%Y%m%d%H%M%S') 

286 

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

288 hand.newFormat = datetime.datetime.strptime('20100908000000','%Y%m%d%H%M%S') 

289 hand.newFormat = HandHistoryConverter.changeTimezone(hand.newFormat, "ET", "UTC") 

290 

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

292 tid_table = m.group('TDATA').split('-') 

293 tid = tid_table[0] 

294 if len(tid_table)>1: 

295 table = tid_table[1] 

296 else: 

297 table = '0' 

298 self.info['tablename'] = m.group('TABLENAME').replace(' - ', ' - ').strip() 

299 self.info['tourNo'] = hand.tourNo 

300 hand.tourNo = tid 

301 hand.tablename = table 

302 structure = self.Structures.lookupSnG(self.info['tablename'], hand.startTime) 

303 if structure!=None: 

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

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

306 hand.buyinCurrency=structure['currency'] 

307 hand.maxseats = structure['seats'] 

308 hand.isSng = True 

309 self.summaryInFile = True 

310 else: 

311 #print 'DEBUG', 'no match for tourney %s tourNo %s' % (self.info['tablename'], tid) 

312 hand.buyin = 0 

313 hand.fee = 0 

314 hand.buyinCurrency="NA" 

315 hand.maxseats = None 

316 if m.group('SEATS')!=None: 

317 hand.maxseats = int(m.group('SEATS')) 

318 else: 

319 #log.debug("HID %s-%s, Table %s" % (m.group('HID1'), m.group('HID2'), m.group('TABLENAME'))) 

320 hand.maxseats = None 

321 if m.group('TABLENAME')!=None: 

322 hand.tablename = m.group('TABLENAME') 

323 else: 

324 hand.tablename = self.base_name 

325 if m.group('SEATS')!=None: 

326 hand.maxseats = int(m.group('SEATS')) 

327 # Check that the hand is complete up to the awarding of the pot; if 

328 # not, the hand is unparseable 

329 if self.re_EndOfHand.search(hand.handText) is None: 

330 self.determineErrorType(hand, "readHandInfo") 

331 

332 def readPlayerStacks(self, hand): 

333 acted = {} 

334 seated = {} 

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

336 for a in m: 

337 seatno = a.group('SEAT') 

338 seated[seatno] = [a.group('PNAME'), a.group('CASH')] 

339 

340 if hand.gametype['type'] == "ring" : 

341 # We can't 100% trust the 'dealtin' field. So read the actions and see if the players acted 

342 m2 = self.re_AllActions.finditer(hand.handText) 

343 fulltable = False 

344 for action in m2: 

345 acted[action.group('PSEAT')] = True 

346 if list(acted.keys()) == list(seated.keys()): # We've faound all players 

347 fulltable = True 

348 break 

349 if fulltable != True: 

350 for seatno in list(seated.keys()): 

351 if seatno not in acted: 

352 del seated[seatno] 

353 

354 for seatno in list(acted.keys()): 

355 if seatno not in seated: 

356 log.error(("MergeToFpdb.readPlayerStacks: '%s' Seat:%s acts but not listed") % (hand.handid, seatno)) 

357 raise FpdbParseError 

358 

359 for seat in seated: 

360 name, stack = seated[seat] 

361 # Merge indexes seats from 0. Add 1 so we don't have to add corner cases everywhere else. 

362 hand.addPlayer(int(seat) + 1, name, stack) 

363 

364 if hand.maxseats==None: 

365 if hand.gametype['type'] == 'tour' and self.maxseats==0: 

366 hand.maxseats = self.guessMaxSeats(hand) 

367 self.maxseats = hand.maxseats 

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

369 hand.maxseats = self.maxseats 

370 else: 

371 hand.maxseats = None 

372 

373 # No players found at all. 

374 if not hand.players: 

375 self.determineErrorType(hand, "readPlayerStacks") 

376 

377 def markStreets(self, hand): 

378 if hand.gametype['base'] == 'hold': 

379 m = re.search(r'<round id="PREFLOP" sequence="[0-9]+"\s?>(?P<PREFLOP>.+(?=<round id="POSTFLOP")|.+)' 

380 r'(<round id="POSTFLOP" sequence="[0-9]+"\s?>(?P<FLOP>.+(?=<round id="POSTTURN")|.+))?' 

381 r'(<round id="POSTTURN" sequence="[0-9]+"\s?>(?P<TURN>.+(?=<round id="POSTRIVER")|.+))?' 

382 r'(<round id="POSTRIVER" sequence="[0-9]+"\s?>(?P<RIVER>.+))?', hand.handText, re.DOTALL) 

383 elif hand.gametype['base'] == 'draw': 

384 if hand.gametype['category'] in ('27_3draw','badugi','a5_3draw'): 

385 m = re.search(r'(?P<PREDEAL>.+(?=<round id="PRE_FIRST_DRAW" sequence="[0-9]+">)|.+)' 

386 r'(<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DEAL>.+(?=<round id="FIRST_DRAW")|.+))?' 

387 r'(<round id="FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DRAWONE>.+(?=<round id="SECOND_DRAW")|.+))?' 

388 r'(<round id="SECOND_DRAW" sequence="[0-9]+"\s?>(?P<DRAWTWO>.+(?=<round id="THIRD_DRAW")|.+))?' 

389 r'(<round id="THIRD_DRAW" sequence="[0-9]+"\s?>(?P<DRAWTHREE>.+))?', hand.handText,re.DOTALL) 

390 else: 

391 m = re.search(r'(?P<PREDEAL>.+(?=<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>)|.+)' 

392 r'(<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DEAL>.+(?=<round id="FIRST_DRAW")|.+))?' 

393 r'(<round id="FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DRAWONE>.+(?=<round id="SECOND_DRAW")|.+))?', hand.handText,re.DOTALL) 

394 elif hand.gametype['base'] == 'stud': 

395 m = re.search(r'(?P<ANTES>.+(?=<round id="BRING_IN" sequence="[0-9]+"\s?>)|.+)' 

396 r'(<round id="BRING_IN" sequence="[0-9]+"\s?>(?P<THIRD>.+(?=<round id="FOURTH_STREET")|.+))?' 

397 r'(<round id="FOURTH_STREET" sequence="[0-9]+"\s?>(?P<FOURTH>.+(?=<round id="FIFTH_STREET")|.+))?' 

398 r'(<round id="FIFTH_STREET" sequence="[0-9]+"\s?>(?P<FIFTH>.+(?=<round id="SIXTH_STREET")|.+))?' 

399 r'(<round id="SIXTH_STREET" sequence="[0-9]+"\s?>(?P<SIXTH>.+(?=<round id="SEVENTH_STREET")|.+))?' 

400 r'(<round id="SEVENTH_STREET" sequence="[0-9]+"\s?>(?P<SEVENTH>.+))?', hand.handText,re.DOTALL) 

401 if m == None: 

402 self.determineErrorType(hand, "markStreets") 

403 hand.addStreets(m) 

404 

405 def readCommunityCards(self, hand, street): 

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

407 if m and street in ('FLOP','TURN','RIVER'): 

408 if street == 'FLOP': 

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

410 elif street in ('TURN','RIVER'): 

411 hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]]) 

412 else: 

413 self.determineErrorType(hand, "readCommunityCards") 

414 

415 def readAntes(self, hand): 

416 for player in self.re_Antes.finditer(hand.handText): 

417 pname = self.playerNameFromSeatNo(player.group('PSEAT'), hand) 

418 #print "DEBUG: hand.addAnte(%s,%s)" %(pname, player.group('ANTE')) 

419 self.adjustMergeTourneyStack(hand, pname, player.group('ANTE')) 

420 hand.addAnte(pname, player.group('ANTE')) 

421 

422 def readBringIn(self, hand): 

423 m = self.re_BringIn.search(hand.handText) 

424 if m: 

425 pname = self.playerNameFromSeatNo(m.group('PSEAT'), hand) 

426 #print "DEBUG: hand.addBringIn(%s,%s)" %(pname, m.group('BRINGIN')) 

427 self.adjustMergeTourneyStack(hand, pname, m.group('BRINGIN')) 

428 hand.addBringIn(pname, m.group('BRINGIN')) 

429 

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

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

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

433 

434 def readBlinds(self, hand): 

435 if (hand.gametype['category'], hand.gametype['limitType']) == ("badugi", "hp"): 

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

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

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

439 else: 

440 if hand.gametype['base'] == 'hold': 

441 street = 'PREFLOP' 

442 elif hand.gametype['base'] == 'draw': 

443 street = 'DEAL' 

444 allinBlinds = {} 

445 blindsantes = hand.handText.split(street)[0] 

446 bb, sb = None, None 

447 for a in self.re_PostSB.finditer(blindsantes): 

448 #print "DEBUG: found sb: '%s' '%s'" %(self.playerNameFromSeatNo(a.group('PSEAT'), hand), a.group('SB')) 

449 sb = a.group('SB') 

450 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand) 

451 self.adjustMergeTourneyStack(hand, player, sb) 

452 hand.addBlind(player,'small blind', sb) 

453 if not hand.gametype['sb'] or hand.gametype['secondGame']: 

454 hand.gametype['sb'] = sb 

455 for a in self.re_PostBB.finditer(blindsantes): 

456 #print "DEBUG: found bb: '%s' '%s'" %(self.playerNameFromSeatNo(a.group('PSEAT'), hand), a.group('BB')) 

457 bb = a.group('BB') 

458 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand) 

459 self.adjustMergeTourneyStack(hand, player, bb) 

460 hand.addBlind(player, 'big blind', bb) 

461 if not hand.gametype['bb'] or hand.gametype['secondGame']: 

462 hand.gametype['bb'] = bb 

463 for a in self.re_PostBoth.finditer(blindsantes): 

464 bb = Decimal(self.info['bb']) 

465 amount = Decimal(a.group('SBBB')) 

466 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand) 

467 self.adjustMergeTourneyStack(hand, player, a.group('SBBB')) 

468 if amount < bb: 

469 hand.addBlind(player, 'small blind', a.group('SBBB')) 

470 elif amount == bb: 

471 hand.addBlind(player, 'big blind', a.group('SBBB')) 

472 else: 

473 hand.addBlind(player, 'both', a.group('SBBB')) 

474 if sb is None or bb is None: 

475 m = self.re_Action.finditer(blindsantes) 

476 for action in m: 

477 player = self.playerNameFromSeatNo(action.group('PSEAT'), hand) 

478 #print "DEBUG: found: '%s' '%s'" %(self.playerNameFromSeatNo(action.group('PSEAT'), hand), action.group('BET')) 

479 if sb is None: 

480 if action.group('BET') and action.group('BET')!= '0.00': 

481 sb = action.group('BET') 

482 self.adjustMergeTourneyStack(hand, player, sb) 

483 hand.addBlind(player, 'small blind', sb) 

484 if not hand.gametype['sb'] or hand.gametype['secondGame']: 

485 hand.gametype['sb'] = sb 

486 elif action.group('BET') == '0.00': 

487 allinBlinds[player] = 'small blind' 

488 #log.error((("MergeToFpdb.readBlinds: Cannot calcualte tourney all-in blind for hand '%s'")) % hand.handid) 

489 #raise FpdbParseError 

490 elif sb and bb is None: 

491 if action.group('BET') and action.group('BET')!= '0.00': 

492 bb = action.group('BET') 

493 self.adjustMergeTourneyStack(hand, player, bb) 

494 hand.addBlind(player, 'big blind', bb) 

495 if not hand.gametype['bb'] or hand.gametype['secondGame']: 

496 hand.gametype['bb'] = bb 

497 elif action.group('BET') == '0.00': 

498 allinBlinds[player] = 'big blind' 

499 #log.error((("MergeToFpdb.readBlinds: Cannot calcualte tourney all-in blind for hand '%s'")) % hand.handid) 

500 #raise FpdbParseError 

501 self.fixTourBlinds(hand, allinBlinds) 

502 

503 def fixTourBlinds(self, hand, allinBlinds): 

504 # FIXME 

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

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

507 if hand.gametype['type'] == 'tour' or hand.gametype['secondGame']: 

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

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

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

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

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

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

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

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

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

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

518 else: 

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

520 hand.sb = hand.gametype['sb'] 

521 hand.bb = hand.gametype['bb'] 

522 for player, blindtype in list(allinBlinds.items()): 

523 if blindtype=='big blind': 

524 self.adjustMergeTourneyStack(hand, player, hand.bb) 

525 hand.addBlind(player, 'big blind', hand.bb) 

526 else: 

527 self.adjustMergeTourneyStack(hand, player, hand.sb) 

528 hand.addBlind(player, 'small blind', hand.sb) 

529 

530 def mergeMultigametypes(self, handText): 

531 m2 = self.re_HandInfo.search(handText) 

532 if m2 is None: 

533 tmp = handText[0:200] 

534 log.error(("MergeToFpdb.readHandInfo: '%s'") % tmp) 

535 raise FpdbParseError 

536 multigametype = m2.group('MULTIGAMETYPE1') if m2.group('MULTIGAMETYPE1') else m2.group('MULTIGAMETYPE2') 

537 if multigametype: 

538 try: 

539 (self.info['base'], self.info['category']) = self.Multigametypes[multigametype] 

540 except KeyError: 

541 tmp = handText[0:200] 

542 log.error(("MergeToFpdb.determineGameType: Multigametypes has no lookup for '%s'") % multigametype) 

543 raise FpdbParseError 

544 

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

546 amount = Decimal(amount) 

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

548 for p in hand.players: 

549 if p[1]==player: 

550 stack = Decimal(p[2]) 

551 stack += amount 

552 p[2] = str(stack) 

553 hand.stacks[player] += amount 

554 

555 def readButton(self, hand): 

556 hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) 

557 

558 def readHoleCards(self, hand): 

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

560# we need to grab hero's cards 

561 herocards = [] 

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

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

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

565 for found in m: 

566# if m == None: 

567# hand.involved = False 

568# else: 

569 hand.hero = self.playerNameFromSeatNo(found.group('PSEAT'), hand) 

570 cards = found.group('CARDS').split(',') 

571 hand.addHoleCards(street, hand.hero, closed=cards, shown=False, mucked=False, dealt=True) 

572 

573 for street in hand.holeStreets: 

574 if street in hand.streets: 

575 if not hand.streets[street] or street in ('PREFLOP', 'DEAL') or hand.gametype['base'] == 'hold': continue # already done these 

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

577 for found in m: 

578 player = self.playerNameFromSeatNo(found.group('PSEAT'), hand) 

579 if player in hand.stacks: 

580 if found.group('CARDS') is None: 

581 cards = [] 

582 newcards = [] 

583 oldcards = [] 

584 else: 

585 if hand.gametype['base'] == 'stud': 

586 cards = found.group('CARDS').replace('null', '').split(',') 

587 cards = [c for c in cards if c!=''] 

588 oldcards = cards[:-1] 

589 newcards = [cards[-1]] 

590 else: 

591 cards = found.group('CARDS').split(',') 

592 oldcards = cards 

593 newcards = [] 

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

595 hand.hero = player 

596 herocards = cards 

597 hand.dealt.add(hand.hero) # need this for stud?? 

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

599 elif (cards != herocards and hand.gametype['base'] == 'stud'): 

600 if hand.hero == player: 

601 herocards = cards 

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

603 elif (len(cards)<5): 

604 if street == 'SEVENTH': 

605 oldcards = [] 

606 newcards = [] 

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

608 elif (len(cards)==7): 

609 for street in hand.holeStreets: 

610 hand.holecards[street][player] = [[], []] 

611 hand.addHoleCards(street, player, closed=cards, open=[], shown=False, mucked=False, dealt=False) 

612 elif (hand.gametype['base'] == 'draw'): 

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

614 

615 def readAction(self, hand, street): 

616 #log.debug("readAction (%s)" % street) 

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

618 for action in m: 

619 player = self.playerNameFromSeatNo(action.group('PSEAT'), hand) 

620 if player in hand.stacks and player not in hand.folded: 

621 if action.group('ATYPE') in ('FOLD', 'SIT_OUT'): 

622 hand.addFold(street, player) 

623 elif action.group('ATYPE') == 'CHECK': 

624 hand.addCheck(street, player) 

625 elif action.group('ATYPE') == 'CALL': 

626 hand.addCall(street, player, action.group('BET')) 

627 elif action.group('ATYPE') == 'RAISE': 

628 if hand.startTime < hand.newFormat: 

629 hand.addCallandRaise(street, player, action.group('BET')) 

630 else: 

631 hand.addRaiseTo(street, player, action.group('BET')) 

632 elif action.group('ATYPE') == 'BET': 

633 hand.addBet(street, player, action.group('BET')) 

634 elif action.group('ATYPE') == 'ALL_IN' and action.group('BET') != None: 

635 hand.addAllIn(street, player, action.group('BET')) 

636 elif action.group('ATYPE') == 'DRAW': 

637 hand.addDiscard(street, player, action.group('TXT')) 

638 elif action.group('ATYPE') == 'COMPLETE': 

639 if hand.gametype['base'] != 'stud': 

640 hand.addRaiseTo(street, player, action.group('BET')) 

641 else: 

642 hand.addComplete( street, player, action.group('BET') ) 

643 else: 

644 log.debug(("Unimplemented %s: '%s' '%s'") % ("readAction", action.group('PSEAT'), action.group('ATYPE'))) 

645 

646 def readShowdownActions(self, hand): 

647 pass 

648 

649 def readCollectPot(self, hand): 

650 hand.setUncalledBets(True) 

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

652 pname = self.playerNameFromSeatNo(m.group('PSEAT'), hand) 

653 if pname!=None: 

654 pot = m.group('POT') 

655 hand.addCollectPot(player=pname, pot=pot) 

656 

657 def readShownCards(self, hand): 

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

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

660 cards = m.group('CARDS') 

661 cards = m.group('CARDS').split(',') 

662 

663 (shown, mucked) = (False, False) 

664 if m.group('SHOWED') == "SHOWN": shown = True 

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

666 

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

668 hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'),hand), shown=shown, mucked=mucked) 

669 

670 def determineErrorType(self, hand, function): 

671 message = False 

672 m = self.re_Connection.search(hand.handText) 

673 if m: 

674 message = ("Found %s. Hand missing information.") % m.group('TYPE') 

675 m = self.re_LeaveTable.search(hand.handText) 

676 if m: 

677 message = ("Found LEAVE. Player left table before hand completed") 

678 m = self.re_Cancelled.search(hand.handText) 

679 if m: 

680 message = ("Found CANCELLED") 

681 if message == False and function == "markStreets": 

682 message = ("Failed to identify all streets") 

683 if message == False and function == "readHandInfo": 

684 message = ("END_OF_HAND not found. No obvious reason") 

685 if message: 

686 raise FpdbHandPartial("Partial hand history: %s '%s' %s" % (function, hand.handid, message)) 

687 

688 @staticmethod 

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

690 "Returns string to search in windows titles" 

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

692 if type=="tour": 

693 # Ignoring table number as it doesn't appear to be in the window title 

694 # "$200 Freeroll - NL Holdem - 20:00 (46302299) - Table 1" -- the table number doesn't matter, it seems to always be 1 in the HH. 

695 # "Fun Step 1 (4358174) - Table 1" 

696 regex = re.escape(str(tournament)) 

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

698 log.info("Merge.getTableTitleRe: returns: '%s'" % (regex)) 

699 return regex