Coverage for MergeToFpdb.py: 0%

450 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-15 19:33 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3# 

4# Copyright 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 

37from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial 

38from decimal import Decimal 

39import re 

40import logging 

41import datetime 

42import MergeStructures 

43 

44# Merge HH Format 

45log = logging.getLogger("parser") 

46 

47 

48class Merge(HandHistoryConverter): 

49 sitename = "Merge" 

50 filetype = "text" 

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

52 siteId = 12 

53 copyGameHeader = True 

54 Structures = MergeStructures.MergeStructures() 

55 

56 limits = { 

57 "No Limit": "nl", 

58 "No Limit ": "nl", 

59 "Limit": "fl", 

60 "Pot Limit": "pl", 

61 "Pot Limit ": "pl", 

62 "Half Pot Limit": "hp", 

63 } 

64 games = { # base, category 

65 "Holdem": ("hold", "holdem"), 

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

67 "Omaha H/L8": ("hold", "omahahilo"), 

68 "2-7 Lowball": ("draw", "27_3draw"), 

69 "A-5 Lowball": ("draw", "a5_3draw"), 

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

71 "5-Draw w/Joker": ("draw", "fivedraw"), 

72 "5-Draw": ("draw", "fivedraw"), 

73 "7-Stud": ("stud", "studhi"), 

74 "7-Stud H/L8": ("stud", "studhilo"), 

75 "5-Stud": ("stud", "5_studhi"), 

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

77 } 

78 

79 mixes = {"HA": "ha", "RASH": "rash", "HO": "ho", "SHOE": "shoe", "HORSE": "horse", "HOSE": "hose", "HAR": "har"} 

80 

81 Lim_Blinds = { 

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

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

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

85 "0.25": ("0.05", "0.10"), 

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

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

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

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

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

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

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

93 "6.00": ("1.50", "3.00"), 

94 "6": ("1.50", "3.00"), 

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

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

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

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

99 "12.00": ("3.00", "6.00"), 

100 "12": ("3.00", "6.00"), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

117 } 

118 

119 Multigametypes = { 

120 "1": ("hold", "holdem"), 

121 "2": ("hold", "holdem"), 

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

123 "9": ("hold", "holdem"), 

124 "23": ("hold", "holdem"), 

125 "34": ("hold", "omahahilo"), 

126 "35": ("hold", "omahahilo"), 

127 "37": ("hold", "omahahilo"), 

128 "38": ("stud", "studhi"), 

129 "39": ("stud", "studhi"), 

130 "41": ("stud", "studhi"), 

131 "42": ("stud", "studhi"), 

132 "43": ("stud", "studhilo"), 

133 "45": ("stud", "studhilo"), 

134 "46": ("stud", "razz"), 

135 "47": ("stud", "razz"), 

136 "49": ("stud", "razz"), 

137 } 

138 

139 # Static regexes 

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

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

142 re_TailSplitHands = re.compile(r"(</game>)") 

143 re_GameInfo = re.compile( 

144 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?', 

145 re.MULTILINE, 

146 ) 

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

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

149 # <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"> 

150 re_HandInfo = re.compile( 

151 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>[^|:]+)[|:]?)?.*>', 

152 re.MULTILINE, 

153 ) 

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

155 re_PlayerInfo = re.compile( 

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

157 re.MULTILINE, 

158 ) 

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

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

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

162 

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

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

165 # not by name but by seat number 

166 re_PostSB = re.compile( 

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

168 re.MULTILINE, 

169 ) 

170 re_PostBB = re.compile( 

171 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?/>', 

172 re.MULTILINE, 

173 ) 

174 re_PostBoth = re.compile( 

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

176 re.MULTILINE, 

177 ) 

178 re_Antes = re.compile( 

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

180 re.MULTILINE, 

181 ) 

182 re_BringIn = re.compile( 

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

184 re.MULTILINE, 

185 ) 

186 re_HeroCards = re.compile( 

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

188 ) 

189 re_Action = re.compile( 

190 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?/>', 

191 re.MULTILINE, 

192 ) 

193 re_AllActions = re.compile( 

194 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?/>', 

195 re.MULTILINE, 

196 ) 

197 re_CollectPot = re.compile( 

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

199 re.MULTILINE, 

200 ) 

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

202 re_ShownCards = re.compile( 

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

204 ) 

205 re_Connection = re.compile( 

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

207 re.MULTILINE, 

208 ) 

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

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

211 re_PlayerOut = re.compile( 

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

213 ) 

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

215 re_DateTime = re.compile( 

216 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]+)", 

217 re.MULTILINE, 

218 ) 

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

220 

221 def compilePlayerRegexs(self, hand): 

222 pass 

223 

224 def playerNameFromSeatNo(self, seatNo, hand): 

225 # This special function is required because Merge Poker records 

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

227 for p in hand.players: 

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

229 return p[1] 

230 

231 def readSupportedGames(self): 

232 return [ 

233 ["ring", "hold", "nl"], 

234 ["ring", "hold", "pl"], 

235 ["ring", "hold", "fl"], 

236 ["ring", "hold", "hp"], 

237 ["ring", "stud", "fl"], 

238 ["ring", "stud", "pl"], 

239 ["ring", "stud", "nl"], 

240 ["ring", "draw", "fl"], 

241 ["ring", "draw", "pl"], 

242 ["ring", "draw", "nl"], 

243 ["ring", "draw", "hp"], 

244 ["tour", "hold", "nl"], 

245 ["tour", "hold", "pl"], 

246 ["tour", "hold", "fl"], 

247 ["tour", "stud", "fl"], 

248 ["tour", "stud", "pl"], 

249 ["tour", "stud", "nl"], 

250 ["tour", "draw", "fl"], 

251 ["tour", "draw", "pl"], 

252 ["tour", "draw", "nl"], 

253 ] 

254 

255 def parseHeader(self, handText, whole_file): 

256 gametype = self.determineGameType(handText) 

257 if gametype is None: 

258 gametype = self.determineGameType(whole_file) 

259 if gametype is None: 

260 if not re.search("<description", whole_file): 

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

262 else: 

263 tmp = handText[0:200] 

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

265 raise FpdbParseError 

266 else: 

267 if "mix" in gametype and gametype["mix"] is not None: 

268 self.mergeMultigametypes(handText) 

269 return gametype 

270 

271 def determineGameType(self, handText): 

272 """return dict with keys/values: 

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

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

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

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

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

278 'smallBlind' int? 

279 'bigBlind' int? 

280 'smallBet' 

281 'bigBet' 

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

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

284 

285 m = self.re_GameInfo.search(handText) 

286 if not m: 

287 return None 

288 

289 self.info = {} 

290 mg = m.groupdict() 

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

292 

293 if "LIMIT" in mg: 

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

295 if "GAME" in mg: 

296 if mg["GAME"] in self.mixes: 

297 self.info["mix"] = self.mixes[mg["GAME"]] 

298 self.mergeMultigametypes(handText) 

299 else: 

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

301 if "SB" in mg: 

302 self.info["sb"] = mg["SB"] 

303 if "BB" in mg: 

304 self.info["bb"] = mg["BB"] 

305 self.info["secondGame"] = False 

306 if mg["blah"] is not None: 

307 if self.re_secondGame.search(mg["blah"]): 

308 self.info["secondGame"] = True 

309 if " Tournament" == mg["TYPE"]: 

310 self.info["type"] = "tour" 

311 self.info["currency"] = "T$" 

312 else: 

313 self.info["type"] = "ring" 

314 if self.re_PlayMoney.search(handText): 

315 self.info["currency"] = "play" 

316 else: 

317 self.info["currency"] = "USD" 

318 

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

320 try: 

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

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

323 except KeyError: 

324 tmp = handText[0:200] 

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

326 raise FpdbParseError 

327 

328 return self.info 

329 

330 def readHandInfo(self, hand): 

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

332 if m is None: 

333 tmp = hand.handText[0:200] 

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

335 raise FpdbParseError 

336 

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

338 self.determineErrorType(hand, None) 

339 

340 hand.handid = m.group("HID1") + m.group("HID2") 

341 

342 m1 = self.re_DateTime.search(m.group("DATETIME")) 

343 if m1: 

344 mg = m1.groupdict() 

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

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

347 hand.startTime = datetime.datetime.strptime( 

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

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

350 else: 

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

352 

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

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

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

356 

357 if hand.gametype["type"] == "tour": 

358 tid_table = m.group("TDATA").split("-") 

359 tid = tid_table[0] 

360 if len(tid_table) > 1: 

361 table = tid_table[1] 

362 else: 

363 table = "0" 

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

365 self.info["tourNo"] = hand.tourNo 

366 hand.tourNo = tid 

367 hand.tablename = table 

368 structure = self.Structures.lookupSnG(self.info["tablename"], hand.startTime) 

369 if structure is not None: 

370 hand.buyin = int(100 * structure["buyIn"]) 

371 hand.fee = int(100 * structure["fee"]) 

372 hand.buyinCurrency = structure["currency"] 

373 hand.maxseats = structure["seats"] 

374 hand.isSng = True 

375 self.summaryInFile = True 

376 else: 

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

378 hand.buyin = 0 

379 hand.fee = 0 

380 hand.buyinCurrency = "NA" 

381 hand.maxseats = None 

382 if m.group("SEATS") is not None: 

383 hand.maxseats = int(m.group("SEATS")) 

384 else: 

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

386 hand.maxseats = None 

387 if m.group("TABLENAME") is not None: 

388 hand.tablename = m.group("TABLENAME") 

389 else: 

390 hand.tablename = self.base_name 

391 if m.group("SEATS") is not None: 

392 hand.maxseats = int(m.group("SEATS")) 

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

394 # not, the hand is unparseable 

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

396 self.determineErrorType(hand, "readHandInfo") 

397 

398 def readPlayerStacks(self, hand): 

399 acted = {} 

400 seated = {} 

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

402 for a in m: 

403 seatno = a.group("SEAT") 

404 seated[seatno] = [a.group("PNAME"), a.group("CASH")] 

405 

406 if hand.gametype["type"] == "ring": 

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

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

409 fulltable = False 

410 for action in m2: 

411 acted[action.group("PSEAT")] = True 

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

413 fulltable = True 

414 break 

415 if fulltable is not True: 

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

417 if seatno not in acted: 

418 del seated[seatno] 

419 

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

421 if seatno not in seated: 

422 log.error( 

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

424 ) 

425 raise FpdbParseError 

426 

427 for seat in seated: 

428 name, stack = seated[seat] 

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

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

431 

432 if hand.maxseats is None: 

433 if hand.gametype["type"] == "tour" and self.maxseats == 0: 

434 hand.maxseats = self.guessMaxSeats(hand) 

435 self.maxseats = hand.maxseats 

436 elif hand.gametype["type"] == "tour": 

437 hand.maxseats = self.maxseats 

438 else: 

439 hand.maxseats = None 

440 

441 # No players found at all. 

442 if not hand.players: 

443 self.determineErrorType(hand, "readPlayerStacks") 

444 

445 def markStreets(self, hand): 

446 if hand.gametype["base"] == "hold": 

447 m = re.search( 

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

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

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

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

452 hand.handText, 

453 re.DOTALL, 

454 ) 

455 elif hand.gametype["base"] == "draw": 

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

457 m = re.search( 

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

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

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

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

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

463 hand.handText, 

464 re.DOTALL, 

465 ) 

466 else: 

467 m = re.search( 

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

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

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

471 hand.handText, 

472 re.DOTALL, 

473 ) 

474 elif hand.gametype["base"] == "stud": 

475 m = re.search( 

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

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

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

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

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

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

482 hand.handText, 

483 re.DOTALL, 

484 ) 

485 if m is None: 

486 self.determineErrorType(hand, "markStreets") 

487 hand.addStreets(m) 

488 

489 def readCommunityCards(self, hand, street): 

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

491 if m and street in ("FLOP", "TURN", "RIVER"): 

492 if street == "FLOP": 

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

494 elif street in ("TURN", "RIVER"): 

495 hand.setCommunityCards(street, [m.group("CARDS").split(",")[-1]]) 

496 else: 

497 self.determineErrorType(hand, "readCommunityCards") 

498 

499 def readAntes(self, hand): 

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

501 pname = self.playerNameFromSeatNo(player.group("PSEAT"), hand) 

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

503 self.adjustMergeTourneyStack(hand, pname, player.group("ANTE")) 

504 hand.addAnte(pname, player.group("ANTE")) 

505 

506 def readBringIn(self, hand): 

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

508 if m: 

509 pname = self.playerNameFromSeatNo(m.group("PSEAT"), hand) 

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

511 self.adjustMergeTourneyStack(hand, pname, m.group("BRINGIN")) 

512 hand.addBringIn(pname, m.group("BRINGIN")) 

513 

514 if hand.gametype["sb"] is None and hand.gametype["bb"] is None: 

515 hand.gametype["sb"] = "1" 

516 hand.gametype["bb"] = "2" 

517 

518 def readBlinds(self, hand): 

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

520 if hand.gametype["sb"] is None and hand.gametype["bb"] is None: 

521 hand.gametype["sb"] = "1" 

522 hand.gametype["bb"] = "2" 

523 else: 

524 if hand.gametype["base"] == "hold": 

525 street = "PREFLOP" 

526 elif hand.gametype["base"] == "draw": 

527 street = "DEAL" 

528 allinBlinds = {} 

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

530 bb, sb = None, None 

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

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

533 sb = a.group("SB") 

534 player = self.playerNameFromSeatNo(a.group("PSEAT"), hand) 

535 self.adjustMergeTourneyStack(hand, player, sb) 

536 hand.addBlind(player, "small blind", sb) 

537 if not hand.gametype["sb"] or hand.gametype["secondGame"]: 

538 hand.gametype["sb"] = sb 

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

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

541 bb = a.group("BB") 

542 player = self.playerNameFromSeatNo(a.group("PSEAT"), hand) 

543 self.adjustMergeTourneyStack(hand, player, bb) 

544 hand.addBlind(player, "big blind", bb) 

545 if not hand.gametype["bb"] or hand.gametype["secondGame"]: 

546 hand.gametype["bb"] = bb 

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

548 bb = Decimal(self.info["bb"]) 

549 amount = Decimal(a.group("SBBB")) 

550 player = self.playerNameFromSeatNo(a.group("PSEAT"), hand) 

551 self.adjustMergeTourneyStack(hand, player, a.group("SBBB")) 

552 if amount < bb: 

553 hand.addBlind(player, "small blind", a.group("SBBB")) 

554 elif amount == bb: 

555 hand.addBlind(player, "big blind", a.group("SBBB")) 

556 else: 

557 hand.addBlind(player, "both", a.group("SBBB")) 

558 if sb is None or bb is None: 

559 m = self.re_Action.finditer(blindsantes) 

560 for action in m: 

561 player = self.playerNameFromSeatNo(action.group("PSEAT"), hand) 

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

563 if sb is None: 

564 if action.group("BET") and action.group("BET") != "0.00": 

565 sb = action.group("BET") 

566 self.adjustMergeTourneyStack(hand, player, sb) 

567 hand.addBlind(player, "small blind", sb) 

568 if not hand.gametype["sb"] or hand.gametype["secondGame"]: 

569 hand.gametype["sb"] = sb 

570 elif action.group("BET") == "0.00": 

571 allinBlinds[player] = "small blind" 

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

573 # raise FpdbParseError 

574 elif sb and bb is None: 

575 if action.group("BET") and action.group("BET") != "0.00": 

576 bb = action.group("BET") 

577 self.adjustMergeTourneyStack(hand, player, bb) 

578 hand.addBlind(player, "big blind", bb) 

579 if not hand.gametype["bb"] or hand.gametype["secondGame"]: 

580 hand.gametype["bb"] = bb 

581 elif action.group("BET") == "0.00": 

582 allinBlinds[player] = "big blind" 

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

584 # raise FpdbParseError 

585 self.fixTourBlinds(hand, allinBlinds) 

586 

587 def fixTourBlinds(self, hand, allinBlinds): 

588 # FIXME 

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

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

591 if hand.gametype["type"] == "tour" or hand.gametype["secondGame"]: 

592 if hand.gametype["sb"] is None and hand.gametype["bb"] is None: 

593 hand.gametype["sb"] = "1" 

594 hand.gametype["bb"] = "2" 

595 elif hand.gametype["sb"] is None: 

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

597 elif hand.gametype["bb"] is None: 

598 hand.gametype["bb"] = str(int(Decimal(hand.gametype["sb"])) * 2) 

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

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

601 hand.gametype["bb"] = str(int(Decimal(hand.gametype["sb"])) * 2) 

602 else: 

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

604 hand.sb = hand.gametype["sb"] 

605 hand.bb = hand.gametype["bb"] 

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

607 if blindtype == "big blind": 

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

609 hand.addBlind(player, "big blind", hand.bb) 

610 else: 

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

612 hand.addBlind(player, "small blind", hand.sb) 

613 

614 def mergeMultigametypes(self, handText): 

615 m2 = self.re_HandInfo.search(handText) 

616 if m2 is None: 

617 tmp = handText[0:200] 

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

619 raise FpdbParseError 

620 multigametype = m2.group("MULTIGAMETYPE1") if m2.group("MULTIGAMETYPE1") else m2.group("MULTIGAMETYPE2") 

621 if multigametype: 

622 try: 

623 (self.info["base"], self.info["category"]) = self.Multigametypes[multigametype] 

624 except KeyError: 

625 tmp = handText[0:200] 

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

627 raise FpdbParseError 

628 

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

630 amount = Decimal(amount) 

631 if hand.gametype["type"] == "tour": 

632 for p in hand.players: 

633 if p[1] == player: 

634 stack = Decimal(p[2]) 

635 stack += amount 

636 p[2] = str(stack) 

637 hand.stacks[player] += amount 

638 

639 def readButton(self, hand): 

640 hand.buttonpos = int(self.re_Button.search(hand.handText).group("BUTTON")) 

641 

642 def readHoleCards(self, hand): 

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

644 # we need to grab hero's cards 

645 herocards = [] 

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

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

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

649 for found in m: 

650 # if m == None: 

651 # hand.involved = False 

652 # else: 

653 hand.hero = self.playerNameFromSeatNo(found.group("PSEAT"), hand) 

654 cards = found.group("CARDS").split(",") 

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

656 

657 for street in hand.holeStreets: 

658 if street in hand.streets: 

659 if not hand.streets[street] or street in ("PREFLOP", "DEAL") or hand.gametype["base"] == "hold": 

660 continue # already done these 

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

662 for found in m: 

663 player = self.playerNameFromSeatNo(found.group("PSEAT"), hand) 

664 if player in hand.stacks: 

665 if found.group("CARDS") is None: 

666 cards = [] 

667 newcards = [] 

668 oldcards = [] 

669 else: 

670 if hand.gametype["base"] == "stud": 

671 cards = found.group("CARDS").replace("null", "").split(",") 

672 cards = [c for c in cards if c != ""] 

673 oldcards = cards[:-1] 

674 newcards = [cards[-1]] 

675 else: 

676 cards = found.group("CARDS").split(",") 

677 oldcards = cards 

678 newcards = [] 

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

680 hand.hero = player 

681 herocards = cards 

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

683 hand.addHoleCards( 

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

685 ) 

686 elif cards != herocards and hand.gametype["base"] == "stud": 

687 if hand.hero == player: 

688 herocards = cards 

689 hand.addHoleCards( 

690 street, 

691 player, 

692 closed=oldcards, 

693 open=newcards, 

694 shown=False, 

695 mucked=False, 

696 dealt=False, 

697 ) 

698 elif len(cards) < 5: 

699 if street == "SEVENTH": 

700 oldcards = [] 

701 newcards = [] 

702 hand.addHoleCards( 

703 street, 

704 player, 

705 closed=oldcards, 

706 open=newcards, 

707 shown=False, 

708 mucked=False, 

709 dealt=False, 

710 ) 

711 elif len(cards) == 7: 

712 for street in hand.holeStreets: 

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

714 hand.addHoleCards( 

715 street, player, closed=cards, open=[], shown=False, mucked=False, dealt=False 

716 ) 

717 elif hand.gametype["base"] == "draw": 

718 hand.addHoleCards( 

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

720 ) 

721 

722 def readAction(self, hand, street): 

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

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

725 for action in m: 

726 player = self.playerNameFromSeatNo(action.group("PSEAT"), hand) 

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

728 if action.group("ATYPE") in ("FOLD", "SIT_OUT"): 

729 hand.addFold(street, player) 

730 elif action.group("ATYPE") == "CHECK": 

731 hand.addCheck(street, player) 

732 elif action.group("ATYPE") == "CALL": 

733 hand.addCall(street, player, action.group("BET")) 

734 elif action.group("ATYPE") == "RAISE": 

735 if hand.startTime < hand.newFormat: 

736 hand.addCallandRaise(street, player, action.group("BET")) 

737 else: 

738 hand.addRaiseTo(street, player, action.group("BET")) 

739 elif action.group("ATYPE") == "BET": 

740 hand.addBet(street, player, action.group("BET")) 

741 elif action.group("ATYPE") == "ALL_IN" and action.group("BET") is not None: 

742 hand.addAllIn(street, player, action.group("BET")) 

743 elif action.group("ATYPE") == "DRAW": 

744 hand.addDiscard(street, player, action.group("TXT")) 

745 elif action.group("ATYPE") == "COMPLETE": 

746 if hand.gametype["base"] != "stud": 

747 hand.addRaiseTo(street, player, action.group("BET")) 

748 else: 

749 hand.addComplete(street, player, action.group("BET")) 

750 else: 

751 log.debug( 

752 ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PSEAT"), action.group("ATYPE")) 

753 ) 

754 

755 def readShowdownActions(self, hand): 

756 pass 

757 

758 def readCollectPot(self, hand): 

759 hand.setUncalledBets(True) 

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

761 pname = self.playerNameFromSeatNo(m.group("PSEAT"), hand) 

762 if pname is not None: 

763 pot = m.group("POT") 

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

765 

766 def readShownCards(self, hand): 

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

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

769 cards = m.group("CARDS") 

770 cards = m.group("CARDS").split(",") 

771 

772 (shown, mucked) = (False, False) 

773 if m.group("SHOWED") == "SHOWN": 

774 shown = True 

775 elif m.group("SHOWED") == "MUCKED": 

776 mucked = True 

777 

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

779 hand.addShownCards( 

780 cards=cards, player=self.playerNameFromSeatNo(m.group("PSEAT"), hand), shown=shown, mucked=mucked 

781 ) 

782 

783 def determineErrorType(self, hand, function): 

784 message = False 

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

786 if m: 

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

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

789 if m: 

790 message = "Found LEAVE. Player left table before hand completed" 

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

792 if m: 

793 message = "Found CANCELLED" 

794 if message is False and function == "markStreets": 

795 message = "Failed to identify all streets" 

796 if message is False and function == "readHandInfo": 

797 message = "END_OF_HAND not found. No obvious reason" 

798 if message: 

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

800 

801 @staticmethod 

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

803 "Returns string to search in windows titles" 

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

805 if type == "tour": 

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

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

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

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

810 log.info( 

811 "Merge.getTableTitleRe: table_name='%s' tournament='%s' table_number='%s'" 

812 % (table_name, tournament, table_number) 

813 ) 

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

815 return regex