Coverage for WinamaxToFpdb.py: 0%

332 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 2008-2011, Carl Gherardi 

5# 

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

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

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

9# (at your option) any later version. 

10# 

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

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

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

14# GNU General Public License for more details. 

15# 

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

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

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

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

20 

21# import L10n 

22# _ = L10n.get_translation() 

23from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial 

24import re 

25import logging 

26import datetime 

27from decimal import Decimal 

28import platform 

29 

30 

31# Winamax HH Format 

32log = logging.getLogger("parser") 

33 

34 

35class Winamax(HandHistoryConverter): 

36 def Trace(f): 

37 def my_f(*args, **kwds): 

38 print(f"entering {f.__name__}") 

39 result = f(*args, **kwds) 

40 print(f"exiting {f.__name__}") 

41 return result 

42 

43 my_f.__name = f.__name__ 

44 my_f.__doc__ = f.__doc__ 

45 return my_f 

46 

47 filter = "Winamax" 

48 siteName = "Winamax" 

49 filetype = "text" 

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

51 siteId = 15 # Needs to match id entry in Sites database 

52 

53 mixes = {} # Legal mixed games 

54 sym = {"USD": "\$", "CAD": "\$", "T$": "", "EUR": "\xe2\x82\xac|\u20ac", "GBP": "\xa3", "play": ""} 

55 # ADD Euro, Sterling, etc HERE 

56 substitutions = { 

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

58 "LS": "\$|\xe2\x82\xac|\u20ac|", # legal currency symbols - Euro(cp1252, utf-8) 

59 } 

60 

61 limits = {"no limit": "nl", "pot limit": "pl", "fixed limit": "fl"} 

62 

63 games = { # base, category 

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

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

66 "Omaha5": ("hold", "5_omahahi"), 

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

68 "5 Card Omaha Hi/Lo": ("hold", "5_omahahi"), # incorrect in file 

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

70 "Omaha8": ("hold", "omahahilo"), 

71 "7-Card Stud": ("stud", "studhi"), 

72 "7stud": ("stud", "studhi"), 

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

74 "7stud8": ("stud", "studhilo"), 

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

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

77 "Lowball27": ("draw", "27_3draw"), 

78 } 

79 mixes = { 

80 "8games": "8game", 

81 "10games": "10game", 

82 "horse": "horse", 

83 } 

84 

85 # Static regexes 

86 # ***** End of hand R5-75443872-57 ***** 

87 re_Identify = re.compile('Winamax\sPoker\s\-\s(CashGame|Go\sFast|HOLD\-UP|Tournament\s")') 

88 re_SplitHands = re.compile(r"\n\n") 

89 

90 re_HandInfo = re.compile( 

91 """ 

92 \s*Winamax\sPoker\s-\s 

93 (?P<RING>(CashGame|Go\sFast\s"[^"]+"|HOLD\-UP\s"[^"]+"))? 

94 (?P<TOUR>Tournament\s 

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

96 buyIn:\s(?P<BUYIN>(?P<BIAMT>[%(LS)s\d\,.]+)?(\s\+?\s|-)(?P<BIRAKE>[%(LS)s\d\,.]+)?\+?(?P<BOUNTY>[%(LS)s\d\.]+)?\s?(?P<TOUR_ISO>%(LEGAL_ISO)s)?|(?P<FREETICKET>[\sa-zA-Z]+))?\s 

97 (level:\s(?P<LEVEL>\d+))? 

98 .*)? 

99 \s-\sHandId:\s\#(?P<HID1>\d+)-(?P<HID2>\d+)-(?P<HID3>\d+)\s-\s # REB says: HID3 is the correct hand number 

100 (?P<GAME>Holdem|Omaha|Omaha5|Omaha8|5\sCard\sOmaha|5\sCard\sOmaha\sHi/Lo|Omaha\sHi/Lo|7\-Card\sStud|7stud|7\-Card\sStud\sHi/Lo|7stud8|Razz|2\-7\sTriple\sDraw|Lowball27)\s 

101 (?P<LIMIT>fixed\slimit|no\slimit|pot\slimit)\s 

102 \( 

103 (((%(LS)s)?(?P<ANTE>[.0-9]+)(%(LS)s)?)/)? 

104 ((%(LS)s)?(?P<SB>[.0-9]+)(%(LS)s)?)/ 

105 ((%(LS)s)?(?P<BB>[.0-9]+)(%(LS)s)?) 

106 \)\s-\s 

107 (?P<DATETIME>.*) 

108 Table:?\s\'(?P<TABLE>[^(]+) 

109 (.(?P<TOURNO>\d+).\#(?P<TABLENO>\d+))?.* 

110 \' 

111 \s(?P<MAXPLAYER>\d+)\-max 

112 \s(?P<MONEY>\(real\smoney\))? 

113 """ 

114 % substitutions, 

115 re.MULTILINE | re.DOTALL | re.VERBOSE, 

116 ) 

117 

118 re_TailSplitHands = re.compile(r"\n\s*\n") 

119 re_Button = re.compile(r"Seat\s#(?P<BUTTON>\d+)\sis\sthe\sbutton") 

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

121 re_Total = re.compile(r"Total pot (?P<TOTAL>[\.\d]+).*(No rake|Rake (?P<RAKE>[\.\d]+))" % substitutions) 

122 re_Mixed = re.compile(r"_(?P<MIXED>10games|8games|horse)_") 

123 re_HUTP = re.compile( 

124 r"Hold\-up\sto\sPot:\stotal\s((%(LS)s)?(?P<AMOUNT>[.0-9]+)(%(LS)s)?)" % substitutions, re.MULTILINE | re.VERBOSE 

125 ) 

126 # 2010/09/21 03:10:51 UTC 

127 re_DateTime = re.compile( 

128 """ 

129 (?P<Y>[0-9]{4})/ 

130 (?P<M>[0-9]+)/ 

131 (?P<D>[0-9]+)\s 

132 (?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)\s 

133 UTC 

134 """, 

135 re.MULTILINE | re.VERBOSE, 

136 ) 

137 

138 # Seat 1: some_player (5€) 

139 # Seat 2: some_other_player21 (6.33€) 

140 # Seat 6: I want fold (147894, 29.25€ bounty) 

141 re_PlayerInfo = re.compile( 

142 "Seat\s(?P<SEAT>[0-9]+):\s(?P<PNAME>.*)\s\((%(LS)s)?(?P<CASH>[.0-9]+)(%(LS)s)?(,\s(%(LS)s)?(?P<BOUNTY>[.0-9]+)(%(LS)s)?\sbounty)?\)" 

143 % substitutions 

144 ) 

145 re_PlayerInfoSummary = re.compile("Seat\s(?P<SEAT>[0-9]+):\s(?P<PNAME>.+?)\s" % substitutions) 

146 

147 def compilePlayerRegexs(self, hand): 

148 players = {player[1] for player in hand.players} 

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

150 # we need to recompile the player regexs. 

151 # TODO: should probably rename re_HeroCards and corresponding method, 

152 # since they are used to find all cards on lines starting with "Dealt to:" 

153 # They still identify the hero. 

154 self.compiledPlayers = players 

155 # ANTES/BLINDS 

156 # helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). 

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

158 subst = {"PLYR": player_re, "CUR": self.sym[hand.gametype["currency"]]} 

159 self.re_PostSB = re.compile( 

160 "%(PLYR)s posts small blind (%(CUR)s)?(?P<SB>[\.0-9]+)(%(CUR)s)?(?! out of position)" % subst, 

161 re.MULTILINE, 

162 ) 

163 self.re_PostBB = re.compile( 

164 "%(PLYR)s posts big blind (%(CUR)s)?(?P<BB>[\.0-9]+)(%(CUR)s)?" % subst, re.MULTILINE 

165 ) 

166 self.re_DenySB = re.compile("(?P<PNAME>.*) deny SB" % subst, re.MULTILINE) 

167 self.re_Antes = re.compile( 

168 r"^%(PLYR)s posts ante (%(CUR)s)?(?P<ANTE>[\.0-9]+)(%(CUR)s)?" % subst, re.MULTILINE 

169 ) 

170 self.re_BringIn = re.compile( 

171 r"^%(PLYR)s (brings in|bring\-in) (%(CUR)s)?(?P<BRINGIN>[\.0-9]+)(%(CUR)s)?" % subst, re.MULTILINE 

172 ) 

173 self.re_PostBoth = re.compile( 

174 "(?P<PNAME>.*): posts small \& big blind \( (%(CUR)s)?(?P<SBBB>[\.0-9]+)(%(CUR)s)?\)" % subst 

175 ) 

176 self.re_PostDead = re.compile( 

177 "(?P<PNAME>.*) posts dead blind \((%(CUR)s)?(?P<DEAD>[\.0-9]+)(%(CUR)s)?\)" % subst, re.MULTILINE 

178 ) 

179 self.re_PostSecondSB = re.compile( 

180 "%(PLYR)s posts small blind (%(CUR)s)?(?P<SB>[\.0-9]+)(%(CUR)s)? out of position" % subst, re.MULTILINE 

181 ) 

182 self.re_HeroCards = re.compile( 

183 "Dealt\sto\s%(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst 

184 ) 

185 

186 # no discards action observed yet 

187 self.re_Action = re.compile( 

188 "(, )?(?P<PNAME>.*?)(?P<ATYPE> bets| checks| raises| calls| folds| stands\spat)( \-?(%(CUR)s)?(?P<BET>[\d\.]+)(%(CUR)s)?)?( to (%(CUR)s)?(?P<BETTO>[\d\.]+)(%(CUR)s)?)?( and is all-in)?" 

189 % subst 

190 ) 

191 self.re_ShowdownAction = re.compile( 

192 "(?P<PNAME>[^\(\)\n]*) (\((small blind|big blind|button)\) )?shows \[(?P<CARDS>.+)\]" 

193 ) 

194 

195 self.re_CollectPot = re.compile( 

196 "\s*(?P<PNAME>.*)\scollected\s(%(CUR)s)?(?P<POT>[\.\d]+)(%(CUR)s)?.*" % subst 

197 ) 

198 self.re_ShownCards = re.compile( 

199 "^Seat (?P<SEAT>[0-9]+): %(PLYR)s (\((small blind|big blind|button)\) )?showed \[(?P<CARDS>.*)\].+? with (?P<STRING>.*)" 

200 % subst, 

201 re.MULTILINE, 

202 ) 

203 

204 def readSupportedGames(self): 

205 return [ 

206 ["ring", "hold", "fl"], 

207 ["ring", "hold", "nl"], 

208 ["ring", "hold", "pl"], 

209 ["ring", "stud", "fl"], 

210 ["ring", "draw", "fl"], 

211 ["ring", "draw", "pl"], 

212 ["ring", "draw", "nl"], 

213 ["tour", "hold", "fl"], 

214 ["tour", "hold", "nl"], 

215 ["tour", "hold", "pl"], 

216 ["tour", "stud", "fl"], 

217 ["tour", "draw", "fl"], 

218 ["tour", "draw", "pl"], 

219 ["tour", "draw", "nl"], 

220 ] 

221 

222 def determineGameType(self, handText): 

223 # Inspect the handText and return the gametype dict 

224 # gametype dict is: {'limitType': xxx, 'base': xxx, 'category': xxx} 

225 info = {} 

226 

227 m = self.re_HandInfo.search(handText) 

228 if not m: 

229 tmp = handText[0:200] 

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

231 raise FpdbParseError 

232 

233 mg = m.groupdict() 

234 

235 if mg.get("TOUR"): 

236 info["type"] = "tour" 

237 info["currency"] = "T$" 

238 elif mg.get("RING"): 

239 info["type"] = "ring" 

240 

241 info["currency"] = "EUR" if mg.get("MONEY") else "play" 

242 info["fast"] = "Go Fast" in mg.get("RING") 

243 if "LIMIT" in mg: 

244 if mg["LIMIT"] in self.limits: 

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

246 else: 

247 tmp = handText[0:100] 

248 log.error("WinamaxToFpdb.determineGameType: Limit not found in %s." % tmp) 

249 raise FpdbParseError 

250 if "GAME" in mg: 

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

252 if m := self.re_Mixed.search(self.in_path): 

253 info["mix"] = self.mixes[m.groupdict()["MIXED"]] 

254 if "SB" in mg: 

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

256 if "BB" in mg: 

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

258 

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

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

261 info["bb"] = str(Decimal(mg["SB"]).quantize(Decimal("0.01"))) 

262 

263 return info 

264 

265 def readHandInfo(self, hand): 

266 info = {} 

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

268 if m is None: 

269 tmp = hand.handText[:200] 

270 log.error(f"WinamaxToFpdb.readHandInfo: '{tmp}'") 

271 raise FpdbParseError 

272 

273 info |= m.groupdict() 

274 log.debug(f"readHandInfo: {info}") 

275 for key, value in info.items(): 

276 if key == "DATETIME": 

277 if a := self.re_DateTime.search(value): 

278 datetimestr = ( 

279 f"{a.group('Y')}/{a.group('M')}/{a.group('D')}" 

280 f" {a.group('H')}:{a.group('MIN')}:{a.group('S')}" 

281 ) 

282 else: 

283 datetimestr = "2010/Jan/01 01:01:01" 

284 log.error(f"readHandInfo: DATETIME not matched: '{info[key]}'") 

285 print(f"DEBUG: readHandInfo: DATETIME not matched: '{info[key]}'") 

286 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") 

287 elif key == "HID1": 

288 # Need to remove non-alphanumerics for MySQL 

289 # Concatenating all three or just HID2 + HID3 can produce out of range values 

290 # HID should not be greater than 14 characters to ensure this 

291 hand.handid = f"{int(info['HID1'][:14])}{int(info['HID2'])}" 

292 

293 elif key == "TOURNO": 

294 hand.tourNo = info[key] 

295 if key == "TABLE": 

296 hand.tablename = info[key] 

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

298 hand.tablename = info["TABLENO"] 

299 hand.roundPenny = True 

300 # TODO: long-term solution for table naming on Winamax. 

301 if hand.tablename.endswith("No Limit Hold'em"): 

302 hand.tablename = hand.tablename[: -len("No Limit Hold'em")] + "NLHE" 

303 if key == "MAXPLAYER" and info[key] is not None: 

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

305 

306 if key == "BUYIN" and hand.tourNo is not None: 

307 print(f"DEBUG: info['BUYIN']: {info['BUYIN']}") 

308 print(f"DEBUG: info['BIAMT']: {info['BIAMT']}") 

309 print(f"DEBUG: info['BIRAKE']: {info['BIRAKE']}") 

310 print(f"DEBUG: info['BOUNTY']: {info['BOUNTY']}") 

311 for k in ["BIAMT", "BIRAKE"]: 

312 if k in info and info[k]: 

313 info[k] = info[k].replace(",", ".") 

314 

315 if info["FREETICKET"] is not None: 

316 hand.buyin = 0 

317 hand.fee = 0 

318 hand.buyinCurrency = "FREE" 

319 else: 

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

321 hand.buyinCurrency = "USD" 

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

323 hand.buyinCurrency = "EUR" 

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

325 hand.buyinCurrency = "WIFP" 

326 elif info[key].find("Free") != -1: 

327 hand.buyinCurrency = "WIFP" 

328 elif info["MONEY"]: 

329 hand.buyinCurrency = "EUR" 

330 else: 

331 hand.buyinCurrency = "play" 

332 

333 info["BIAMT"] = info["BIAMT"].strip("$€FPP") if info["BIAMT"] is not None else 0 

334 if hand.buyinCurrency != "WIFP": 

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

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

337 tmp = info["BOUNTY"] 

338 info["BOUNTY"] = info["BIRAKE"] 

339 info["BIRAKE"] = tmp 

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

341 hand.koBounty = int(100 * Decimal(info["BOUNTY"])) 

342 hand.isKO = True 

343 else: 

344 hand.isKO = False 

345 

346 info["BIRAKE"] = info["BIRAKE"].strip("$€") 

347 

348 # TODO: Is this correct? Old code tried to 

349 # conditionally multiply by 100, but we 

350 # want hand.buyin in 100ths of 

351 # dollars/euros (so hand.buyin = 90 for $0.90 BI). 

352 hand.buyin = int(100 * Decimal(info["BIAMT"])) 

353 hand.fee = int(100 * Decimal(info["BIRAKE"])) 

354 else: 

355 hand.buyin = int(Decimal(info["BIAMT"])) 

356 hand.fee = 0 

357 if hand.buyin == 0 and hand.fee == 0: 

358 hand.buyinCurrency = "FREE" 

359 

360 if key == "LEVEL": 

361 hand.level = info[key] 

362 

363 hand.mixed = None 

364 

365 def readPlayerStacks(self, hand): 

366 # Split hand text for Winamax, as the players listed in the hh preamble and the summary will differ 

367 # if someone is sitting out. 

368 # Going to parse both and only add players in the summary. 

369 handsplit = hand.handText.split("*** SUMMARY ***") 

370 if len(handsplit) != 2: 

371 raise FpdbHandPartial(f"Hand is not cleanly split into pre and post Summary {hand.handid}.") 

372 pre, post = handsplit 

373 m = self.re_PlayerInfo.finditer(pre) 

374 plist = {} 

375 

376 # Get list of players in header. 

377 for a in m: 

378 if plist.get(a.group("PNAME")) is None: 

379 hand.addPlayer(int(a.group("SEAT")), a.group("PNAME"), a.group("CASH")) 

380 plist[a.group("PNAME")] = [int(a.group("SEAT")), a.group("CASH")] 

381 

382 if len(plist.keys()) < 2: 

383 raise FpdbHandPartial(f"Less than 2 players in hand! {hand.handid}.") 

384 

385 def markStreets(self, hand): 

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

387 m = re.search( 

388 r"\*\*\* ANTE/BLINDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)" 

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

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

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

392 hand.handText, 

393 re.DOTALL, 

394 ) 

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

396 m = re.search( 

397 r"\*\*\* ANTE/BLINDS \*\*\*(?P<ANTES>.+(?=\*\*\* (3rd STREET|THIRD) \*\*\*)|.+)" 

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

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

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

401 r"(\*\*\* (6th STREET|SIXTH) \*\*\*(?P<SIXTH>.+(?=\*\*\* (7th STREET|SEVENTH) \*\*\*)|.+))?" 

402 r"(\*\*\* (7th STREET|SEVENTH) \*\*\*(?P<SEVENTH>.+))?", 

403 hand.handText, 

404 re.DOTALL, 

405 ) 

406 else: 

407 m = re.search( 

408 r"\*\*\* ANTE/BLINDS \*\*\*(?P<PREDEAL>.+(?=\*\*\* FIRST\-BET \*\*\*)|.+)" 

409 r"(\*\*\* FIRST\-BET \*\*\*(?P<DEAL>.+(?=\*\*\* FIRST\-DRAW \*\*\*)|.+))?" 

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

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

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

413 hand.handText, 

414 re.DOTALL, 

415 ) 

416 

417 try: 

418 hand.addStreets(m) 

419 print("adding street", m.group(0)) 

420 print("---") 

421 except Exception: 

422 log.info("Failed to add streets. handtext=%s") 

423 

424 # Needs to return a list in the format 

425 # ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb, 

426 # addtional players are assumed to post a bb oop 

427 

428 def readButton(self, hand): 

429 if m := self.re_Button.search(hand.handText): 

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

431 log.debug("readButton: button on pos %d" % hand.buttonpos) 

432 else: 

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

434 

435 # def readCommunityCards(self, hand, street): 

436 # #print hand.streets.group(street) 

437 # if street in ('FLOP','TURN','RIVER'): 

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

439 # m = self.re_Board.search(hand.streets.group(street)) 

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

441 

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

443 if street in ("FLOP", "TURN", "RIVER"): 

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

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

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

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

448 

449 def readBlinds(self, hand): 

450 # found_small, found_big = False, False 

451 

452 m = self.re_PostSB.search(hand.handText) 

453 if m is not None: 

454 hand.addBlind(m.group("PNAME"), "small blind", m.group("SB")) 

455 # found_small = True 

456 else: 

457 log.debug("No small blind") 

458 hand.addBlind(None, None, None) 

459 

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

461 hand.addBlind(a.group("PNAME"), "big blind", a.group("BB")) 

462 amount = Decimal(a.group("BB").replace(",", "")) 

463 hand.lastBet["PREFLOP"] = amount 

464 for a in self.re_PostDead.finditer(hand.handText): 

465 print(f"DEBUG: Found dead blind: addBlind({a.group('PNAME')}, 'secondsb', {a.group('DEAD')})") 

466 hand.addBlind(a.group("PNAME"), "secondsb", a.group("DEAD")) 

467 for a in self.re_PostSecondSB.finditer(hand.handText): 

468 print(f"DEBUG: Found dead blind: addBlind({a.group('PNAME')}, 'secondsb/both', {a.group('SB')}, {hand.sb})") 

469 if Decimal(a.group("SB")) > Decimal(hand.sb): 

470 hand.addBlind(a.group("PNAME"), "both", a.group("SB")) 

471 else: 

472 hand.addBlind(a.group("PNAME"), "secondsb", a.group("SB")) 

473 

474 def readAntes(self, hand): 

475 log.debug("reading antes") 

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

477 for player in m: 

478 logging.debug(f"hand.addAnte({player.group('PNAME')},{player.group('ANTE')})") 

479 hand.addAnte(player.group("PNAME"), player.group("ANTE")) 

480 

481 def readBringIn(self, hand): 

482 if m := self.re_BringIn.search(hand.handText, re.DOTALL): 

483 logging.debug(f"readBringIn: {m.group('PNAME')} for {m.group('BRINGIN')}") 

484 hand.addBringIn(m.group("PNAME"), m.group("BRINGIN")) 

485 

486 def readSTP(self, hand): 

487 if m := self.re_HUTP.search(hand.handText): 

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

489 

490 def readHoleCards(self, hand): 

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

492 # we need to grab hero's cards 

493 for street in ("PREFLOP", "DEAL", "BLINDSANTES"): 

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

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

496 for found in m: 

497 if newcards := [c for c in found.group("NEWCARDS").split(" ") if c != "X"]: 

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

499 

500 print(f"DEBUG: {hand.handid} addHoleCards({street}, {hand.hero}, {newcards})") 

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

502 log.debug(f"Hero cards {hand.hero}: {newcards}") 

503 

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

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

506 continue # already done these 

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

508 for found in m: 

509 player = found.group("PNAME") 

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

511 newcards = [] 

512 else: 

513 newcards = [c for c in found.group("NEWCARDS").split(" ") if c != "X"] 

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

515 oldcards = [] 

516 else: 

517 oldcards = [c for c in found.group("OLDCARDS").split(" ") if c != "X"] 

518 

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

520 hand.hero = player 

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

522 hand.addHoleCards( 

523 street, 

524 player, 

525 closed=newcards[:2], 

526 open=[newcards[2]], 

527 shown=False, 

528 mucked=False, 

529 dealt=False, 

530 ) 

531 else: 

532 hand.addHoleCards( 

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

534 ) 

535 

536 def readAction(self, hand, street): 

537 streetsplit = hand.streets[street].split("*** SUMMARY ***") 

538 m = self.re_Action.finditer(streetsplit[0]) 

539 for action in m: 

540 acts = action.groupdict() 

541 print(f"DEBUG: acts: {acts}") 

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

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

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

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

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

547 hand.addCall(street, action.group("PNAME"), action.group("BET")) 

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

549 if bringin := [ 

550 act[2] for act in hand.actions[street] if act[0] == action.group("PNAME") and act[1] == "bringin" 

551 ]: 

552 betto = str(Decimal(action.group("BETTO")) + bringin[0]) 

553 else: 

554 betto = action.group("BETTO") 

555 hand.addRaiseTo(street, action.group("PNAME"), betto) 

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

557 if street in ("PREFLOP", "DEAL", "THIRD", "BLINDSANTES"): 

558 hand.addRaiseBy(street, action.group("PNAME"), action.group("BET")) 

559 else: 

560 hand.addBet(street, action.group("PNAME"), action.group("BET")) 

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

562 hand.addDiscard(street, action.group("PNAME"), action.group("BET"), action.group("DISCARDED")) 

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

564 hand.addStandsPat(street, action.group("PNAME")) 

565 else: 

566 log.fatal(f"DEBUG:Unimplemented readAction: '{action.group('PNAME')}' '{action.group('ATYPE')}'") 

567 print(f"Processed {acts}") 

568 print("committed=", hand.pot.committed) 

569 

570 def readShowdownActions(self, hand): 

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

572 log.debug(f"add show actions {shows}") 

573 cards = shows.group("CARDS") 

574 cards = cards.split(" ") 

575 print(f"DEBUG: addShownCards({cards}, {shows.group('PNAME')})") 

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

577 

578 def readCollectPot(self, hand): 

579 hand.setUncalledBets(True) 

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

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

582 

583 def readShownCards(self, hand): 

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

585 log.debug(f"Read shown cards: {m.group(0)}") 

586 cards = m.group("CARDS") 

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

588 (shown, mucked) = (False, False) 

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

590 shown = True 

591 string = m.group("STRING") 

592 print(m.group("PNAME"), cards, shown, mucked) 

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

594 

595 def readSummaryInfo(self, summaryInfoList): 

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

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

598 log.info("Reading summary info for Winamax.") 

599 return True 

600 

601 def readTourneyResults(self, hand): 

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

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

604 log.info("Reading tournay result info for Winamax.") 

605 pass 

606 

607 @staticmethod 

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

609 log.info( 

610 f"Winamax.getTableTitleRe: table_name='{table_name}' tournament='{tournament}' table_number='{table_number}'" 

611 ) 

612 sysPlatform = platform.system() # Linux, Windows, Darwin 

613 if sysPlatform[:5] == "Linux": 

614 regex = f"Winamax {table_name}" 

615 else: 

616 regex = f"Winamax {table_name} /" 

617 print("regex get table cash title:", regex) 

618 if tournament: 

619 if table_number > 99 or (table_number >= 100 or table_number <= 9) and table_number == 0: 

620 regex = r"Winamax\s+([^\(]+)\(%s\)\(#%s\)" % (tournament, table_number) 

621 elif table_number < 100 and table_number > 9: 

622 regex = r"Winamax\s+([^\(]+)\(%s\)\(#0%s\)" % (tournament, table_number) 

623 else: 

624 regex = r"Winamax\s+([^\(]+)\(%s\)\(#00%s\)" % (tournament, table_number) 

625 

626 print("regex get mtt sng expresso cash title:", regex) 

627 log.info(f"Winamax.getTableTitleRe: returns: '{regex}'") 

628 return regex