Coverage for WinamaxToFpdb.py: 0%
328 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-18 00:10 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-18 00:10 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
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########################################################################
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
31# Winamax HH Format
32log = logging.getLogger("parser")
35class Winamax(HandHistoryConverter):
36 def Trace(f):
37 def my_f(*args, **kwds):
38 log.debug(f"entering {f.__name__}")
39 result = f(*args, **kwds)
40 log.debug(f"exiting {f.__name__}")
41 return result
43 my_f.__name = f.__name__
44 my_f.__doc__ = f.__doc__
45 return my_f
47 filter = "Winamax"
48 siteName = "Winamax"
49 filetype = "text"
50 codepage = ("utf8", "cp1252")
51 siteId = 15 # Needs to match id entry in Sites database
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 }
61 limits = {"no limit": "nl", "pot limit": "pl", "fixed limit": "fl"}
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 }
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")
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 )
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 )
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)
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 )
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 )
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 )
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 ]
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 = {}
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
233 mg = m.groupdict()
235 if mg.get("TOUR"):
236 info["type"] = "tour"
237 info["currency"] = "T$"
238 elif mg.get("RING"):
239 info["type"] = "ring"
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"]
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")))
263 return info
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
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 log.debug(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'])}"
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])
306 if key == "BUYIN" and hand.tourNo is not None:
307 log.debug(f"DEBUG: info['BUYIN']: {info['BUYIN']}")
308 log.debug(f"DEBUG: info['BIAMT']: {info['BIAMT']}")
309 log.debug(f"DEBUG: info['BIRAKE']: {info['BIRAKE']}")
310 log.debug(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(",", ".")
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"
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
346 info["BIRAKE"] = info["BIRAKE"].strip("$€")
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"
360 if key == "LEVEL":
361 hand.level = info[key]
363 hand.mixed = None
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 = {}
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")]
382 if len(plist.keys()) < 2:
383 raise FpdbHandPartial(f"Less than 2 players in hand! {hand.handid}.")
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 )
417 try:
418 hand.addStreets(m)
419 log.debug("adding street", m.group(0))
420 log.debug("---")
421 except Exception:
422 log.info("Failed to add streets. handtext=%s")
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
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")
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(','))
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(" "))
449 def readBlinds(self, hand):
450 # found_small, found_big = False, False
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)
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 log.debug(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 log.debug(
469 f"DEBUG: Found dead blind: addBlind({a.group('PNAME')}, 'secondsb/both', {a.group('SB')}, {hand.sb})"
470 )
471 if Decimal(a.group("SB")) > Decimal(hand.sb):
472 hand.addBlind(a.group("PNAME"), "both", a.group("SB"))
473 else:
474 hand.addBlind(a.group("PNAME"), "secondsb", a.group("SB"))
476 def readAntes(self, hand):
477 log.debug("reading antes")
478 m = self.re_Antes.finditer(hand.handText)
479 for player in m:
480 logging.debug(f"hand.addAnte({player.group('PNAME')},{player.group('ANTE')})")
481 hand.addAnte(player.group("PNAME"), player.group("ANTE"))
483 def readBringIn(self, hand):
484 if m := self.re_BringIn.search(hand.handText, re.DOTALL):
485 logging.debug(f"readBringIn: {m.group('PNAME')} for {m.group('BRINGIN')}")
486 hand.addBringIn(m.group("PNAME"), m.group("BRINGIN"))
488 def readSTP(self, hand):
489 if m := self.re_HUTP.search(hand.handText):
490 hand.addSTP(m.group("AMOUNT"))
492 def readHoleCards(self, hand):
493 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
494 # we need to grab hero's cards
495 for street in ("PREFLOP", "DEAL", "BLINDSANTES"):
496 if street in hand.streets.keys():
497 m = self.re_HeroCards.finditer(hand.streets[street])
498 for found in m:
499 if newcards := [c for c in found.group("NEWCARDS").split(" ") if c != "X"]:
500 hand.hero = found.group("PNAME")
502 print(f"DEBUG: {hand.handid} addHoleCards({street}, {hand.hero}, {newcards})")
503 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
504 log.debug(f"Hero cards {hand.hero}: {newcards}")
506 for street, text in list(hand.streets.items()):
507 if not text or street in ("PREFLOP", "DEAL", "BLINDSANTES"):
508 continue # already done these
509 m = self.re_HeroCards.finditer(hand.streets[street])
510 for found in m:
511 player = found.group("PNAME")
512 if found.group("NEWCARDS") is None:
513 newcards = []
514 else:
515 newcards = [c for c in found.group("NEWCARDS").split(" ") if c != "X"]
516 if found.group("OLDCARDS") is None:
517 oldcards = []
518 else:
519 oldcards = [c for c in found.group("OLDCARDS").split(" ") if c != "X"]
521 if street == "THIRD" and len(newcards) == 3: # hero in stud game
522 hand.hero = player
523 hand.dealt.add(player) # need this for stud??
524 hand.addHoleCards(
525 street,
526 player,
527 closed=newcards[:2],
528 open=[newcards[2]],
529 shown=False,
530 mucked=False,
531 dealt=False,
532 )
533 else:
534 hand.addHoleCards(
535 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
536 )
538 def readAction(self, hand, street):
539 streetsplit = hand.streets[street].split("*** SUMMARY ***")
540 m = self.re_Action.finditer(streetsplit[0])
541 for action in m:
542 acts = action.groupdict()
543 log.debug(f"DEBUG: acts: {acts}")
544 if action.group("ATYPE") == " folds":
545 hand.addFold(street, action.group("PNAME"))
546 elif action.group("ATYPE") == " checks":
547 hand.addCheck(street, action.group("PNAME"))
548 elif action.group("ATYPE") == " calls":
549 hand.addCall(street, action.group("PNAME"), action.group("BET"))
550 elif action.group("ATYPE") == " raises":
551 if bringin := [
552 act[2] for act in hand.actions[street] if act[0] == action.group("PNAME") and act[1] == "bringin"
553 ]:
554 betto = str(Decimal(action.group("BETTO")) + bringin[0])
555 else:
556 betto = action.group("BETTO")
557 hand.addRaiseTo(street, action.group("PNAME"), betto)
558 elif action.group("ATYPE") == " bets":
559 if street in ("PREFLOP", "DEAL", "THIRD", "BLINDSANTES"):
560 hand.addRaiseBy(street, action.group("PNAME"), action.group("BET"))
561 else:
562 hand.addBet(street, action.group("PNAME"), action.group("BET"))
563 elif action.group("ATYPE") == " discards":
564 hand.addDiscard(street, action.group("PNAME"), action.group("BET"), action.group("DISCARDED"))
565 elif action.group("ATYPE") == " stands pat":
566 hand.addStandsPat(street, action.group("PNAME"))
567 else:
568 log.fatal(f"DEBUG:Unimplemented readAction: '{action.group('PNAME')}' '{action.group('ATYPE')}'")
569 log.info(f"Processed {acts}")
570 log.info("committed=", hand.pot.committed)
572 def readShowdownActions(self, hand):
573 for shows in self.re_ShowdownAction.finditer(hand.handText):
574 log.debug(f"add show actions {shows}")
575 cards = shows.group("CARDS")
576 cards = cards.split(" ")
577 log.debug(f"DEBUG: addShownCards({cards}, {shows.group('PNAME')})")
578 hand.addShownCards(cards, shows.group("PNAME"))
580 def readCollectPot(self, hand):
581 hand.setUncalledBets(True)
582 for m in self.re_CollectPot.finditer(hand.handText):
583 hand.addCollectPot(player=m.group("PNAME"), pot=m.group("POT"))
585 def readShownCards(self, hand):
586 for m in self.re_ShownCards.finditer(hand.handText):
587 log.debug(f"Read shown cards: {m.group(0)}")
588 cards = m.group("CARDS")
589 cards = cards.split(" ") # needs to be a list, not a set--stud needs the order
590 (shown, mucked) = (False, False)
591 if m.group("CARDS") is not None:
592 shown = True
593 string = m.group("STRING")
594 log.debug(m.group("PNAME"), cards, shown, mucked)
595 hand.addShownCards(cards=cards, player=m.group("PNAME"), shown=shown, mucked=mucked, string=string)
597 def readSummaryInfo(self, summaryInfoList):
598 """Implement the abstract method from HandHistoryConverter."""
599 # Add the actual implementation here, or use a placeholder if not needed
600 log.info("Reading summary info for Winamax.")
601 return True
603 def readTourneyResults(self, hand):
604 """Implement the abstract method from HandHistoryConverter."""
605 # Add the actual implementation here, or use a placeholder if not needed
606 log.info("Reading tournay result info for Winamax.")
607 pass
609 @staticmethod
610 def getTableTitleRe(type, table_name=None, tournament=None, table_number=None):
611 log.info(
612 f"Winamax.getTableTitleRe: table_name='{table_name}' tournament='{tournament}' table_number='{table_number}'"
613 )
614 sysPlatform = platform.system() # Linux, Windows, Darwin
615 if sysPlatform[:5] == "Linux":
616 regex = f"Winamax {table_name}"
617 else:
618 regex = f"Winamax {table_name} /"
619 log.debug(f"regex get table cash title: {regex}")
620 if tournament:
621 regex = r"Winamax\s+([^\(]+)\(%s\)\(#0*%s\)" % (tournament, table_number)
623 log.debug("regex get mtt sng expresso cash title:", regex)
624 log.info(f"Winamax.getTableTitleRe: returns: '{regex}'")
625 return regex