Coverage for KingsClubToFpdb.py: 0%
328 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-15 19:33 +0000
« 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########################################################################
21# import L10n
22# _ = L10n.get_translation()
24# TODO: straighten out discards for draw games
26from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial
27from decimal import Decimal
28import re
29import logging
30import datetime
32# KingsClub HH Format
33log = logging.getLogger("parser")
36class KingsClub(HandHistoryConverter):
37 # Class Variables
39 sitename = "KingsClub"
40 filetype = "text"
41 codepage = ("utf8", "cp1252", "ISO-8859-1")
42 siteId = 28 # Needs to match id entry in Sites database
43 sym = {
44 "USD": "\$",
45 "CAD": "\$",
46 "T$": "",
47 "EUR": "\xe2\x82\xac",
48 "GBP": "\£",
49 "play": "",
50 "INR": "\₹",
51 "CNY": "\¥",
52 } # ADD Euro, Sterling, etc HERE
53 substitutions = {
54 "LEGAL_ISO": "USD|EUR|GBP|CAD|FPP|SC|INR|CNY", # legal ISO currency codes
55 "LS": "\$|\xe2\x82\xac|\u20ac|\£|\u20b9|\¥|", # legal currency symbols - Euro(cp1252, utf-8)
56 "PLYR": r"\s?(?P<PNAME>.+?)",
57 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|\u20b9|\¥|)",
58 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button blind\) |\(button\) \(small blind\) |\(small blind/button\) |\(button\) \(big blind\) )?",
59 }
61 # translations from captured groups to fpdb info strings
62 Lim_Blinds = {
63 "0.04": ("0.01", "0.02"),
64 "0.08": ("0.02", "0.04"),
65 "0.10": ("0.02", "0.05"),
66 "0.20": ("0.05", "0.10"),
67 "0.40": ("0.10", "0.20"),
68 "0.50": ("0.10", "0.25"),
69 "1.00": ("0.25", "0.50"),
70 "1": ("0.25", "0.50"),
71 "2.00": ("0.50", "1.00"),
72 "2": ("0.50", "1.00"),
73 "4.00": ("1.00", "2.00"),
74 "4": ("1.00", "2.00"),
75 "6.00": ("1.00", "3.00"),
76 "6": ("1.00", "3.00"),
77 "8.00": ("2.00", "4.00"),
78 "8": ("2.00", "4.00"),
79 "10.00": ("2.00", "5.00"),
80 "10": ("2.00", "5.00"),
81 "16.00": ("4.00", "8.00"),
82 "16": ("4.00", "8.00"),
83 "20.00": ("5.00", "10.00"),
84 "20": ("5.00", "10.00"),
85 "30.00": ("10.00", "15.00"),
86 "30": ("10.00", "15.00"),
87 "40.00": ("10.00", "20.00"),
88 "40": ("10.00", "20.00"),
89 "50.00": ("10.00", "25.00"),
90 "50": ("10.00", "25.00"),
91 "60.00": ("15.00", "30.00"),
92 "60": ("15.00", "30.00"),
93 "80.00": ("20.00", "40.00"),
94 "80": ("20.00", "40.00"),
95 "100.00": ("25.00", "50.00"),
96 "100": ("25.00", "50.00"),
97 "150.00": ("50.00", "75.00"),
98 "150": ("50.00", "75.00"),
99 "200.00": ("50.00", "100.00"),
100 "200": ("50.00", "100.00"),
101 "400.00": ("100.00", "200.00"),
102 "400": ("100.00", "200.00"),
103 "500.00": ("100.00", "250.00"),
104 "500": ("100.00", "250.00"),
105 "600.00": ("150.00", "300.00"),
106 "600": ("150.00", "300.00"),
107 "800.00": ("200.00", "400.00"),
108 "800": ("200.00", "400.00"),
109 "1000.00": ("250.00", "500.00"),
110 "1000": ("250.00", "500.00"),
111 "2000.00": ("500.00", "1000.00"),
112 "2000": ("500.00", "1000.00"),
113 "4000.00": ("1000.00", "2000.00"),
114 "4000": ("1000.00", "2000.00"),
115 "10000.00": ("2500.00", "5000.00"),
116 "10000": ("2500.00", "5000.00"),
117 "20000.00": ("5000.00", "10000.00"),
118 "20000": ("5000.00", "10000.00"),
119 "40000.00": ("10000.00", "20000.00"),
120 "40000": ("10000.00", "20000.00"),
121 }
123 limits = {"No Limit": "nl", "Pot Limit": "pl", "Limit": "fl"}
124 games = { # base, category
125 "Holdem": ("hold", "holdem"),
126 "Omaha": ("hold", "omahahi"),
127 "Omaha Hi-Lo": ("hold", "omahahilo"),
128 "Big O": ("hold", "5_omaha8"),
129 "Omaha 5 Card": ("hold", "5_omahahi"),
130 "Omaha 6 Card": ("hold", "6_omahahi"),
131 "Short Deck Holdem": ("hold", "6_holdem"),
132 "Razz": ("stud", "razz"),
133 "Seven Card Stud": ("stud", "studhi"),
134 "Seven Card Stud Hi-Lo": ("stud", "studhilo"),
135 "Badugi": ("draw", "badugi"),
136 "2-7 Triple Draw": ("draw", "27_3draw"),
137 "2-7 Single Draw": ("draw", "27_1draw"),
138 "5 Card Draw": ("draw", "fivedraw"),
139 "A-5 Triple Draw": ("draw", "a5_3draw"),
140 "A-5 Single Draw": ("draw", "a5_1draw"),
141 "2-7 Razz": ("stud", "27_razz"),
142 "Badacey": ("draw", "badacey"),
143 "Badeucey": ("draw", "badeucey"),
144 "2-7 Drawmaha": ("draw", "drawmaha"),
145 "Captain": ("draw", "drawmaha"),
146 }
147 mixes = {
148 "HORSE": "horse",
149 "8-Game": "8game",
150 "8-GAME": "8game",
151 "HOSE": "hose",
152 "Mixed PLH/PLO": "plh_plo",
153 "Mixed NLH/PLO": "nlh_plo",
154 "Mixed Omaha H/L": "plo_lo",
155 "Mixed Hold'em": "mholdem",
156 "Mixed Omaha": "momaha",
157 "Triple Stud": "3stud",
158 } # Legal mixed games
159 currencies = {"€": "EUR", "$": "USD", "": "T$", "£": "GBP", "¥": "CNY", "₹": "INR"}
161 # Static regexes
162 re_GameInfo = re.compile(
163 """
164 \#(?P<HID>[0-9]+):\s+
165 (?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\s
166 (?P<GAME>Holdem|Short\sDeck\sHoldem|Razz|Seven\sCard\sStud|Seven\sCard\sStud\sHi\-Lo|Omaha|Omaha\s(5|6)\sCard|Omaha\sHi\-Lo|Badugi|2\-7\sTriple\sDraw|2\-7\sSingle\sDraw|5\sCard\sDraw|Big\sO|2\-7\sRazz|Badacey|Badeucey|A\-5\sTriple\sDraw|A\-5\sSingle\sDraw|2\-7\sDrawmaha|Captain)\s
167 \-\s(?P<SB>[,.0-9]+)/(?P<BB>[,.0-9]+)
168 """
169 % substitutions,
170 re.MULTILINE | re.VERBOSE,
171 )
173 re_PlayerInfo = re.compile(
174 """
175 ^\s?Seat\s(?P<SEAT>[0-9]+):\s
176 (?P<PNAME>.*)\s
177 \((%(LS)s)?(?P<CASH>[,.0-9]+)
178 \)"""
179 % substitutions,
180 re.MULTILINE | re.VERBOSE,
181 )
183 re_HandInfo = re.compile(
184 """
185 ^\s?Table\s(ID\s)?\'(?P<TABLE>.+?)\'\s
186 ((?P<MAX>\d+)-max\s)?
187 (?P<PLAY>\(Play\sMoney\)\s)?
188 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
189 re.MULTILINE | re.VERBOSE,
190 )
192 re_TourNo = re.compile("Table\s'T(?P<TOURNO>\d+)\s\[(?P<TABLENO>\d+)\]'")
194 re_Identify = re.compile("^\#\d+:")
195 re_SplitHands = re.compile("(?:\s?\n){2,}")
196 re_TailSplitHands = re.compile("(\n\n\n+)")
197 re_Button = re.compile("Seat #(?P<BUTTON>\d+) is the button", re.MULTILINE)
198 re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
199 re_Board2 = re.compile(r"\[(?P<C1>\S\S)\] \[(\S\S)?(?P<C2>\S\S) (?P<C3>\S\S)\]")
200 re_DateTime = re.compile(
201 """(?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]+)""",
202 re.MULTILINE,
203 )
204 # revised re including timezone (not currently used):
205 # re_DateTime = re.compile("""(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+) \(?(?P<TZ>[A-Z0-9]+)""", re.MULTILINE)
207 # These used to be compiled per player, but regression tests say
208 # we don't have to, and it makes life faster.
209 re_PostSB = re.compile(r"^%(PLYR)s: posts the small blind %(CUR)s(?P<SB>[,.0-9]+)" % substitutions, re.MULTILINE)
210 re_PostBB = re.compile(r"^%(PLYR)s: posts the big blind %(CUR)s(?P<BB>[,.0-9]+)" % substitutions, re.MULTILINE)
211 re_Antes = re.compile(r"^%(PLYR)s: posts ante %(CUR)s(?P<ANTE>[,.0-9]+)" % substitutions, re.MULTILINE)
212 re_BringIn = re.compile(
213 r"^%(PLYR)s brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[,.0-9]+)" % substitutions, re.MULTILINE
214 )
215 re_PostBoth = re.compile(r"^%(PLYR)s: posts blind %(CUR)s(?P<SBBB>[,.0-9]+)" % substitutions, re.MULTILINE)
216 re_PostStraddle = re.compile(
217 r"^%(PLYR)s: (posts )?straddles? %(CUR)s(?P<STRADDLE>[,.0-9]+)" % substitutions, re.MULTILINE
218 )
219 re_Action = re.compile(
220 r"""^%(PLYR)s(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat|\sdraws)(\s%(CUR)s(?P<BET>[,.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[,.\d]+))?\s*(,\sand\sis\sall.in)?(and\shas\sreached\sthe\s\[%(CUR)s\d\.,]+\scap)?(\son|\scards?)?(\s\(disconnect\))?(\s\[(?P<CARDS>.+?)\]\sdraws\s\[(?P<DRAWS1>.+?)\](\s\[(?P<DRAWS2>.+?)\])?)?\s*$"""
221 % substitutions,
222 re.MULTILINE | re.VERBOSE,
223 )
224 re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % substitutions["PLYR"], re.MULTILINE)
225 re_sitsOut = re.compile("^%s sits out" % substitutions["PLYR"], re.MULTILINE)
226 # re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and (lost|(won|collected) \(%(CUR)s(?P<POT>[.\d]+)\)) with (?P<STRING>.+?)(,\sand\s(won\s\(%(CUR)s[.\d]+\)|lost)\swith\s(?P<STRING2>.*))?)?$" % substitutions, re.MULTILINE)
227 # bd43 wins (279.50) from pot
228 re_CollectPot = re.compile(
229 r"^%(PLYR)s wins (pot )?(side pot \d )?\((?P<POT>[,.\d]+)\)( from pot)?" % substitutions, re.MULTILINE
230 )
231 re_CashedOut = re.compile(r"cashed\sout\sthe\shand")
232 re_WinningRankOne = re.compile(
233 "^%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[,\.0-9]+) - congratulations!$" % substitutions,
234 re.MULTILINE,
235 )
236 re_WinningRankOther = re.compile(
237 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place and received %(CUR)s(?P<AMT>[,.0-9]+)\.$"
238 % substitutions,
239 re.MULTILINE,
240 )
241 re_RankOther = re.compile(
242 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place$" % substitutions, re.MULTILINE
243 )
244 re_Cancelled = re.compile("Hand\scancelled", re.MULTILINE)
245 re_Uncalled = re.compile("Uncalled bet \(%(CUR)s(?P<BET>[,.\d]+)\) returned to" % substitutions, re.MULTILINE)
246 # APTEM-89 wins the $0.27 bounty for eliminating Hero
247 # ChazDazzle wins the 22000 bounty for eliminating berkovich609
248 # JKuzja, vecenta split the $50 bounty for eliminating ODYSSES
249 re_Bounty = re.compile(
250 "^%(PLYR)s (?P<SPLIT>split|wins) the %(CUR)s(?P<AMT>[,\.0-9]+) bounty for eliminating (?P<ELIMINATED>.+?)$"
251 % substitutions,
252 re.MULTILINE,
253 )
254 # Amsterdam71 wins $19.90 for eliminating MuKoJla and their own bounty increases by $19.89 to $155.32
255 # Amsterdam71 wins $4.60 for splitting the elimination of Frimble11 and their own bounty increases by $4.59 to $41.32
256 # Amsterdam71 wins the tournament and receives $230.36 - congratulations!
257 re_Progressive = re.compile(
258 """
259 ^%(PLYR)s\swins\s%(CUR)s(?P<AMT>[,\.0-9]+)\s
260 for\s(splitting\sthe\selimination\sof|eliminating)\s(?P<ELIMINATED>.+?)\s
261 and\stheir\sown\sbounty\sincreases\sby\s%(CUR)s(?P<INCREASE>[\.0-9]+)\sto\s%(CUR)s(?P<ENDAMT>[\.0-9]+)$"""
262 % substitutions,
263 re.MULTILINE | re.VERBOSE,
264 )
266 re_STP = re.compile(
267 """
268 STP\sadded:\s%(CUR)s(?P<AMOUNT>[,\.0-9]+)"""
269 % substitutions,
270 re.MULTILINE | re.VERBOSE,
271 )
273 re_Rake = re.compile(r"^Rake\s(?P<RAKE>[,.0-9]+)$", re.MULTILINE)
274 re_Split = re.compile(r"\*\*\* BOARD 1 - FLOP \*\*\*")
275 re_Table = re.compile(r"^\s?Table\s(ID\s)?\'(?P<TABLE>.+?)\'\s", re.MULTILINE | re.VERBOSE)
277 def compilePlayerRegexs(self, hand):
278 players = set([player[1] for player in hand.players])
279 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
280 self.compiledPlayers = players
281 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
282 subst = {
283 "PLYR": player_re,
284 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) |\(button\) \(big blind\) )?",
285 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|)",
286 }
287 self.re_HeroCards = re.compile(
288 r"^Dealt to %(PLYR)s:(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE
289 )
291 def readSupportedGames(self):
292 return [
293 ["ring", "hold", "nl"],
294 ["ring", "hold", "pl"],
295 ["ring", "hold", "fl"],
296 ["ring", "hold", "pn"],
297 ["ring", "stud", "fl"],
298 ["ring", "draw", "fl"],
299 ["ring", "draw", "pl"],
300 ["ring", "draw", "nl"],
301 ["tour", "hold", "nl"],
302 ["tour", "hold", "pl"],
303 ["tour", "hold", "fl"],
304 ["tour", "hold", "pn"],
305 ["tour", "stud", "fl"],
306 ["tour", "draw", "fl"],
307 ["tour", "draw", "pl"],
308 ["tour", "draw", "nl"],
309 ]
311 def determineGameType(self, handText):
312 info = {}
313 m = self.re_GameInfo.search(handText)
314 if not m:
315 tmp = handText[0:200]
316 log.error(("KingsClubToFpdb.determineGameType: '%s'") % tmp)
317 raise FpdbParseError
319 mg = m.groupdict()
320 if "LIMIT" in mg:
321 info["limitType"] = self.limits[mg["LIMIT"]]
322 if "GAME" in mg:
323 (info["base"], info["category"]) = self.games[mg["GAME"]]
324 if "SB" in mg and mg["SB"] is not None:
325 info["sb"] = self.clearMoneyString(mg["SB"])
326 if "BB" in mg and mg["BB"] is not None:
327 info["bb"] = self.clearMoneyString(mg["BB"])
329 m1 = self.re_TourNo.search(handText)
330 if m1:
331 info["type"] = "tour"
332 info["currency"] = "T$"
333 else:
334 info["type"] = "ring"
335 info["currency"] = "USD"
337 m2 = self.re_Split.search(handText)
338 if m2:
339 info["split"] = True
340 else:
341 info["split"] = False
343 m3 = self.re_Table.search(handText)
344 if m3 and "AOF" in m3.group("TABLE"):
345 info["category"] = "aof_omaha"
347 if info["limitType"] == "fl" and info["bb"] is not None:
348 if info["type"] == "ring":
349 try:
350 info["sb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][0]
351 info["bb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][1]
352 except KeyError:
353 info["sb"] = str((Decimal(self.clearMoneyString(mg["SB"])) / 2).quantize(Decimal("0.01")))
354 info["bb"] = str(Decimal(self.clearMoneyString(mg["SB"])).quantize(Decimal("0.01")))
355 # tmp = handText[0:200]
356 # log.error(_("KingsClubToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") % (mg['BB'], tmp))
357 # raise FpdbParseError
358 else:
359 info["sb"] = str((Decimal(self.clearMoneyString(mg["SB"])) / 2).quantize(Decimal("0.01")))
360 info["bb"] = str(Decimal(self.clearMoneyString(mg["SB"])).quantize(Decimal("0.01")))
362 return info
364 def readHandInfo(self, hand):
365 # First check if partial
366 if hand.handText.count("*** SUMMARY *") != 1:
367 raise FpdbHandPartial("Hand is not cleanly split into pre and post Summary")
369 info = {}
370 m = self.re_HandInfo.search(hand.handText, re.DOTALL)
371 m1 = self.re_DateTime.finditer(hand.handText, re.DOTALL)
372 m2 = self.re_GameInfo.search(hand.handText)
373 m3 = self.re_TourNo.search(hand.handText)
374 if m is None or m1 is None or m2 is None:
375 tmp = hand.handText[0:200]
376 log.error(("KingsClubToFpdb.readHandInfo: '%s'") % tmp)
377 raise FpdbParseError
379 info.update(m.groupdict())
380 info.update(m2.groupdict())
381 if m3 is not None:
382 info.update(m3.groupdict())
383 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other)
384 # 2008/08/17 - 01:14:43 (ET)
385 # 2008/09/07 06:23:14 ET
386 # 2021-01-02 15:39:12
387 datetimestr = "2000/01/01 00:00:00" # default used if time not found
388 for a in m1:
389 datetimestr = "%s/%s/%s %s:%s:%s" % (
390 a.group("Y"),
391 a.group("M"),
392 a.group("D"),
393 a.group("H"),
394 a.group("MIN"),
395 a.group("S"),
396 )
397 # tz = a.group('TZ') # just assume ET??
398 # print " tz = ", tz, " datetime =", datetimestr
399 hand.startTime = datetime.datetime.strptime(
400 datetimestr, "%Y/%m/%d %H:%M:%S"
401 ) # also timezone at end, e.g. " ET"
402 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
404 # log.debug("readHandInfo: %s" % info)
405 for key in info:
406 if key == "HID":
407 hand.handid = info[key]
408 if key == "TOURNO":
409 hand.tourNo = info[key]
410 if key == "TABLE":
411 if "TABLENO" in info:
412 hand.tablename = info["TABLENO"]
413 else:
414 hand.tablename = info[key]
415 if key == "BUTTON":
416 hand.buttonpos = info[key]
417 if self.re_Cancelled.search(hand.handText):
418 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid)
420 def readButton(self, hand):
421 m = self.re_Button.search(hand.handText)
422 if m:
423 hand.buttonpos = int(m.group("BUTTON"))
424 else:
425 log.info("readButton: " + ("not found"))
427 def readPlayerStacks(self, hand):
428 pre, post = hand.handText.split("*** SUMMARY *")
429 m = self.re_PlayerInfo.finditer(pre)
430 for a in m:
431 hand.addPlayer(
432 int(a.group("SEAT")),
433 a.group("PNAME"),
434 str(Decimal(self.clearMoneyString(a.group("CASH"))) * 100)
435 if hand.tourNo is not None
436 else self.clearMoneyString(a.group("CASH")),
437 )
439 def markStreets(self, hand):
440 # There is no marker between deal and draw in KingsClubPkr A5 single draw
441 # this upsets the accounting, incorrectly sets handsPlayers.cardxx and
442 # in consequence the mucked-display is incorrect.
443 # Attempt to fix by inserting a DRAW marker into the hand text attribute
445 if hand.gametype["category"] == "a5_1draw":
446 # isolate the first discard/stand pat line (thanks Carl for the regex)
447 discard_split = re.split(r"(?:(.+(?: stands pat| discards| draws).+))", hand.handText, re.DOTALL)
448 if len(hand.handText) == len(discard_split[0]):
449 # handText was not split, no DRAW street occurred
450 pass
451 else:
452 # DRAW street found, reassemble, with DRAW marker added
453 discard_split[0] += "*** 1ST DRAW ***\r\n"
454 hand.handText = ""
455 for i in discard_split:
456 hand.handText += i
458 # PREFLOP = ** Dealing down cards **
459 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
460 if hand.gametype["split"]:
461 m = re.search(
462 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* BOARD 1 - FLOP \*\*\*)|.+)"
463 r"(\*\*\* BOARD 1 - FLOP \*\*\* (?P<FLOP1>\[(\S\S ?)?\S\S \S\S\].+(?=\*\*\* BOARD 2 - FLOP \*\*\*)|.+))?"
464 r"(\*\*\* BOARD 2 - FLOP \*\*\* (?P<FLOP2>\[(\S\S ?)?\S\S \S\S\].+(?=\*\*\* BOARD 1 - TURN \*\*\*)|.+))?"
465 r"(\*\*\* BOARD 1 - TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN1>\[\S\S\].+(?=\*\*\* BOARD 2 - TURN \*\*\*)|.+))?"
466 r"(\*\*\* BOARD 2 - TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN2>\[\S\S\].+(?=\*\*\* BOARD 1 - RIVER \*\*\*)|.+))?"
467 r"(\*\*\* BOARD 1 - RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER1>\[\S\S\].+?(?=\*\*\* BOARD 2 - RIVER \*\*\*)|.+))?"
468 r"(\*\*\* BOARD 2 - RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER2>\[\S\S\].+))?",
469 hand.handText,
470 re.DOTALL,
471 )
472 elif hand.gametype["category"] == "drawmaha":
473 m = re.search(
474 r"(?P<DEAL>.+(?=\*\*\* FLOP \*\*\*)|.+)"
475 r"(\*\*\* FLOP \*\*\*(?P<DRAWONE> (\[\S\S\] )?\[(\S\S ?)?\S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
476 r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<DRAWTWO>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
477 r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<DRAWTHREE>\[\S\S\].+))?",
478 hand.handText,
479 re.DOTALL,
480 )
481 elif hand.gametype["base"] in ("hold"):
482 arr = hand.handText.split("*** HOLE CARDS ***")
483 if len(arr) > 1:
484 pre, post = arr
485 else:
486 post = arr[0]
487 m = re.search(
488 r"(?P<PREFLOP>(.+(?P<FLOPET>\[\S\S\]))?.+(?=\*\*\* FLOP \*\*\*)|.+)"
489 r"(\*\*\* FLOP \*\*\*(?P<FLOP> (\[\S\S\] )?\[(\S\S ?)?\S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
490 r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
491 r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?",
492 post,
493 re.DOTALL,
494 )
496 elif hand.gametype["base"] in ("stud"):
497 arr = hand.handText.split("*** 3RD STREET ***")
498 if self.re_BringIn.search(arr[0]):
499 m = re.search(
500 r"(?P<THIRD>.+(?=\*\*\* 3RD STREET \*\*\*)|.+)"
501 r"(\*\*\* 3RD STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 4TH STREET \*\*\*)|.+))?"
502 r"(\*\*\* 4TH STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 5TH STREET \*\*\*)|.+))?"
503 r"(\*\*\* 5TH STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 6TH STREET \*\*\*)|.+))?"
504 r"(\*\*\* 6TH STREET \*\*\*(?P<SEVENTH>.+))?",
505 hand.handText,
506 re.DOTALL,
507 )
508 else:
509 m = re.search(
510 r"(?P<ANTES>.+(?=\*\*\* 3RD STREET \*\*\*)|.+)"
511 r"(\*\*\* 3RD STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4TH STREET \*\*\*)|.+))?"
512 r"(\*\*\* 4TH STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5TH STREET \*\*\*)|.+))?"
513 r"(\*\*\* 5TH STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6TH STREET \*\*\*)|.+))?"
514 r"(\*\*\* 6TH STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* 7TH STREET \*\*\*)|.+))?"
515 r"(\*\*\* 7TH STREET \*\*\*(?P<SEVENTH>.+))?",
516 hand.handText,
517 re.DOTALL,
518 )
519 elif hand.gametype["base"] in ("draw"):
520 if hand.gametype["category"] in ("27_1draw", "fivedraw"):
521 m = re.search(
522 r"(?P<PREDEAL>.+(?=\*\*\* 1ST BETTING ROUND \*\*\*)|.+)"
523 r"(\*\*\* 1ST BETTING ROUND \*\*\*(?P<DEAL>.+(?=\*\*\* 1ST DRAW \*\*\*)|.+))?"
524 r"(\*\*\* 1ST DRAW \*\*\*(?P<DRAWONE>.+))?",
525 hand.handText,
526 re.DOTALL,
527 )
528 elif hand.gametype["category"] == "a5_1draw":
529 m = re.search(
530 r"(?P<DEAL>.+(?=\*\*\* 1ST DRAW \*\*\*)|.+)" r"(\*\*\* 1ST DRAW \*\*\*(?P<DRAWONE>.+))?",
531 hand.handText,
532 re.DOTALL,
533 )
534 else:
535 m = re.search(
536 r"(?P<PREDEAL>.+(?=\*\*\* 1ST BETTING ROUND \*\*\*)|.+)"
537 r"(\*\*\* 1ST BETTING ROUND \*\*\*(?P<DEAL>.+(?=\*\*\* 1ST DRAW \*\*\*)|.+))?"
538 r"(\*\*\* 1ST DRAW \*\*\*(?P<DRAWONE>.+(?=\*\*\* 2ND DRAW \*\*\*)|.+))?"
539 r"(\*\*\* 2ND DRAW \*\*\*(?P<DRAWTWO>.+(?=\*\*\* 3RD DRAW \*\*\*)|.+))?"
540 r"(\*\*\* 3RD DRAW \*\*\*(?P<DRAWTHREE>.+))?",
541 hand.handText,
542 re.DOTALL,
543 )
544 hand.addStreets(m)
545 if hand.gametype["base"] in ("hold") and not hand.gametype["split"]:
546 m1 = re.search(
547 r"(\*\*\* BOARD 1 - RIVER \*\*\* \[(?P<FLOP1>\S\S \S\S \S\S) (?P<TURN1>\S\S)] (?P<RIVER1>\[\S\S\].+(?=\*\*\* BOARD 2 - RIVER \*\*\*)|.+))"
548 r"(\*\*\* BOARD 2 - RIVER \*\*\* \[(?P<FLOP2>(\S\S|\-) (\S\S|\-) (\S\S|\-)) (?P<TURN2>(\S\S|\-))] (?P<RIVER2>\[\S\S\].+))",
549 post,
550 re.DOTALL,
551 )
552 if m1:
553 if hand.streets.get("FLOP") is None:
554 hand.streets.update({"FLOP1": m1.group("FLOP1"), "FLOP2": m1.group("FLOP2")})
555 if hand.streets.get("TURN") is None:
556 hand.streets.update({"TURN1": m1.group("TURN1"), "TURN2": m1.group("TURN2")})
557 hand.streets.update({"RIVER1": m1.group("RIVER1"), "RIVER2": m1.group("RIVER2")})
558 else:
559 m2 = re.search(
560 r"(\*\*\* RIVER \*\*\* \[(?P<FLOP>\S\S \S\S \S\S) (?P<TURN>\S\S)] (?P<RIVER>\[\S\S\].+(?=\*\*\* SUMMARY \*\s?\*\*)|.+))",
561 post,
562 re.DOTALL,
563 )
564 if m2:
565 if hand.streets.get("FLOP") is None:
566 hand.streets.update({"FLOP": m2.group("FLOP")})
567 if hand.streets.get("TURN") is None:
568 hand.streets.update({"TURN": m2.group("TURN")})
569 hand.streets.update({"RIVER": m2.group("RIVER")})
571 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
572 if (
573 street != "FLOPET" or hand.streets.get("FLOP") is None
574 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
575 if street in ("FLOP1", "TURN1", "FLOP2", "TURN2") and not hand.gametype["split"]:
576 hand.setCommunityCards(street, hand.streets[street].split(" "))
577 else:
578 m = self.re_Board.search(hand.streets[street])
579 if m:
580 hand.setCommunityCards(street, m.group("CARDS").split(" "))
581 elif street in ("FLOP", "TURN"):
582 hand.setCommunityCards(street, hand.streets[street].split(" "))
583 if street in ("FLOP1", "TURN1", "RIVER1", "FLOP2", "TURN2", "RIVER2"):
584 hand.runItTimes = 2
586 def readSTP(self, hand):
587 log.debug(("read Splash the Pot"))
588 m = self.re_STP.search(hand.handText)
589 if m:
590 hand.addSTP(m.group("AMOUNT"))
592 def readAntes(self, hand):
593 log.debug("reading antes")
594 m = self.re_Antes.finditer(hand.handText)
595 pnames = set([])
596 for player in m:
597 # ~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
598 if player.group("PNAME") in pnames and hand.gametype["category"] == "6_holdem":
599 hand.addBlind(
600 player.group("PNAME"),
601 "button blind",
602 str(Decimal(self.clearMoneyString(player.group("ANTE"))) * 100)
603 if hand.tourNo is not None
604 else self.clearMoneyString(player.group("ANTE")),
605 )
606 else:
607 hand.addAnte(
608 player.group("PNAME"),
609 str(Decimal(self.clearMoneyString(player.group("ANTE"))) * 100)
610 if hand.tourNo is not None
611 else self.clearMoneyString(player.group("ANTE")),
612 )
613 pnames.add(player.group("PNAME"))
615 def readBringIn(self, hand):
616 m = self.re_BringIn.search(hand.handText, re.DOTALL)
617 if m:
618 # ~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
619 hand.addBringIn(
620 m.group("PNAME"),
621 str(Decimal(self.clearMoneyString(m.group("BRINGIN"))) * 100)
622 if hand.tourNo is not None
623 else self.clearMoneyString(m.group("BRINGIN")),
624 )
626 def readBlinds(self, hand):
627 for a in self.re_PostSB.finditer(hand.handText):
628 hand.addBlind(
629 a.group("PNAME"),
630 "small blind",
631 str(Decimal(self.clearMoneyString(a.group("SB"))) * 100)
632 if hand.tourNo is not None
633 else self.clearMoneyString(a.group("SB")),
634 )
635 for a in self.re_PostBB.finditer(hand.handText):
636 hand.addBlind(
637 a.group("PNAME"),
638 "big blind",
639 str(Decimal(self.clearMoneyString(a.group("BB"))) * 100)
640 if hand.tourNo is not None
641 else self.clearMoneyString(a.group("BB")),
642 )
643 for a in self.re_PostBoth.finditer(hand.handText):
644 hand.addBlind(
645 a.group("PNAME"),
646 "big blind",
647 str(Decimal(self.clearMoneyString(a.group("SBBB"))) * 100)
648 if hand.tourNo is not None
649 else self.clearMoneyString(a.group("SBBB")),
650 )
651 for a in self.re_PostStraddle.finditer(hand.handText):
652 hand.addBlind(
653 a.group("PNAME"),
654 "straddle",
655 str(Decimal(self.clearMoneyString(a.group("STRADDLE"))) * 100)
656 if hand.tourNo is not None
657 else self.clearMoneyString(a.group("STRADDLE")),
658 )
660 def readHoleCards(self, hand):
661 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
662 # we need to grab hero's cards
663 for street in ("PREFLOP", "DEAL"):
664 if street in hand.streets.keys():
665 m = self.re_HeroCards.finditer(hand.streets[street])
666 for found in m:
667 # if m == None:
668 # hand.involved = False
669 # else:
670 newcards = [x for x in found.group("NEWCARDS").split(" ") if x != "X"]
671 if len(newcards) > 0:
672 hand.hero = found.group("PNAME")
673 _street = "FLOP" if hand.gametype["category"] == "aof_omaha" else street
674 hand.addHoleCards(_street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
676 for street, text in list(hand.streets.items()):
677 if not text or street in ("PREFLOP", "DEAL"):
678 continue # already done these
679 m = self.re_HeroCards.finditer(hand.streets[street])
680 for found in m:
681 player = found.group("PNAME")
682 if found.group("NEWCARDS") is None:
683 newcards = []
684 else:
685 newcards = [x for x in found.group("NEWCARDS").split(" ") if x != "X"]
686 if found.group("OLDCARDS") is None:
687 oldcards = []
688 else:
689 oldcards = [x for x in found.group("OLDCARDS").split(" ") if x != "X"]
691 if street == "THIRD" and len(newcards) == 3: # hero in stud game
692 hand.hero = player
693 hand.dealt.add(player) # need this for stud??
694 hand.addHoleCards(
695 street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False
696 )
697 else:
698 hand.addHoleCards(
699 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
700 )
702 def readAction(self, hand, street):
703 if hand.gametype["split"] and street in hand.communityStreets:
704 s = street + "2"
705 else:
706 s = street
707 if not hand.streets[s]:
708 return
709 m = self.re_Action.finditer(hand.streets[s])
710 for action in m:
711 action.groupdict()
712 if (
713 action.group("ATYPE") in (" discards", " stands pat", " draws")
714 and hand.gametype["category"] == "drawmaha"
715 ):
716 street = "DRAWTWO"
717 # log.error("DEBUG: %s acts: %s" % (street, acts))
718 if action.group("ATYPE") == " folds":
719 hand.addFold(street, action.group("PNAME"))
720 elif action.group("ATYPE") == " checks":
721 hand.addCheck(street, action.group("PNAME"))
722 elif action.group("ATYPE") == " calls":
723 hand.addCallTo(
724 street,
725 action.group("PNAME"),
726 str(Decimal(self.clearMoneyString(action.group("BET"))) * 100)
727 if hand.tourNo is not None
728 else self.clearMoneyString(action.group("BET")),
729 )
730 elif action.group("ATYPE") == " raises":
731 if action.group("BETTO") is not None:
732 hand.addRaiseTo(
733 street,
734 action.group("PNAME"),
735 str(Decimal(self.clearMoneyString(action.group("BETTO"))) * 100)
736 if hand.tourNo is not None
737 else self.clearMoneyString(action.group("BETTO")),
738 )
739 elif action.group("BET") is not None:
740 hand.addCallandRaise(
741 street,
742 action.group("PNAME"),
743 str(Decimal(self.clearMoneyString(action.group("BET"))) * 100)
744 if hand.tourNo is not None
745 else self.clearMoneyString(action.group("BET")),
746 )
747 elif action.group("ATYPE") == " bets":
748 if street in ("PREFLOP", "THIRD", "DEAL"):
749 hand.addRaiseTo(
750 street,
751 action.group("PNAME"),
752 str(Decimal(self.clearMoneyString(action.group("BET"))) * 100)
753 if hand.tourNo is not None
754 else self.clearMoneyString(action.group("BET")),
755 )
756 else:
757 hand.addBet(
758 street,
759 action.group("PNAME"),
760 str(Decimal(self.clearMoneyString(action.group("BET"))) * 100)
761 if hand.tourNo is not None
762 else self.clearMoneyString(action.group("BET")),
763 )
764 elif action.group("ATYPE") == " discards":
765 hand.addDiscard(
766 street, action.group("PNAME"), len(action.group("CARDS").split(" ")), action.group("CARDS")
767 )
768 if action.group("DRAWS1") is not None:
769 player = action.group("PNAME")
770 newcards = [x for x in action.group("DRAWS1").split(" ") if x != "X"]
771 discards = action.group("CARDS").split(" ")
772 laststreet = hand.allStreets[hand.allStreets.index(street) - 1]
773 oldcards = [x for x in hand.join_holecards(player, True, laststreet) if x not in discards]
774 hand.addHoleCards(
775 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
776 )
777 elif action.group("ATYPE") == " draws":
778 hand.addDiscard(street, action.group("PNAME"), self.clearMoneyString(action.group("BET")))
779 elif action.group("ATYPE") == " stands pat":
780 hand.addStandsPat(street, action.group("PNAME"), action.group("CARDS"))
781 else:
782 log.debug(
783 ("DEBUG:")
784 + " "
785 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE"))
786 )
788 def readShowdownActions(self, hand):
789 pass
791 def readCollectPot(self, hand):
792 if (hand.gametype["category"] == "27_1draw" and hand.gametype["limitType"] == "nl") or hand.gametype[
793 "base"
794 ] == "stud":
795 hand.adjustCollected = False
796 else:
797 hand.adjustCollected = True
798 for m in self.re_CollectPot.finditer(hand.handText):
799 pot = (
800 str(Decimal(self.clearMoneyString(m.group("POT"))) * 100)
801 if hand.tourNo is not None
802 else self.clearMoneyString(m.group("POT"))
803 )
804 hand.addCollectPot(player=m.group("PNAME"), pot=pot)
805 for m in self.re_Rake.finditer(hand.handText):
806 if hand.rakes.get("rake"):
807 hand.rakes["rake"] += Decimal(self.clearMoneyString(m.group("RAKE")))
808 else:
809 hand.rakes["rake"] = Decimal(self.clearMoneyString(m.group("RAKE")))
811 def readShownCards(self, hand):
812 runIt = False
813 for shows in self.re_ShowdownAction.finditer(hand.handText):
814 player = shows.group("PNAME").replace("Run 1: ", "")
815 if "Run 2: " in shows.group("PNAME"):
816 runIt = True
817 else:
818 cards = [x for x in shows.group("CARDS").split(" ") if x != "X"]
819 hand.addShownCards(cards, player, shown=True, mucked=False)
820 if runIt:
821 hand.streetList += ["DRAWTWO"]
822 hand.allStreets += ["DRAWTWO"]
823 hand.holeStreets += ["DRAWTWO"]
824 hand.actionStreets += ["DRAWTWO"]
825 hand.streets["DRAWTWO"] = ""
826 hand.actions["DRAWTWO"] = []
827 hand.holecards["DRAWTWO"] = {}
828 for shows in self.re_ShowdownAction.finditer(hand.handText):
829 if "Run 2: " in shows.group("PNAME"):
830 cards = [x for x in shows.group("CARDS").split(" ") if x != "X"]
831 hand.addShownCards(cards, shows.group("PNAME").replace("Run 2: ", ""), shown=True, mucked=False)