Coverage for GGPokerToFpdb.py: 0%
395 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# GGpoker HH Format
33log = logging.getLogger("parser")
36class GGPoker(HandHistoryConverter):
37 # Class Variables
39 sitename = "GGPoker"
40 filetype = "text"
41 codepage = ("utf8", "cp1252")
42 siteId = 27 # Needs to match id entry in Sites database
43 sym = {
44 "USD": "\$",
45 "T$": "",
46 "play": "",
47 } # ADD Euro, Sterling, etc HERE
48 substitutions = {
49 "LEGAL_ISO": "USD|CNY", # legal ISO currency codes
50 "LS": "\$|\¥|", # legal currency symbols - Euro(cp1252, utf-8)
51 "PLYR": r"\s?(?P<PNAME>.+?)",
52 "CUR": "(\$|\¥|)",
53 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button blind\) |\(button\) \(small blind\) |\(small blind/button\) |\(button\) \(big blind\) )?",
54 }
56 # translations from captured groups to fpdb info strings
57 Lim_Blinds = {
58 "0.04": ("0.01", "0.02"),
59 "0.08": ("0.02", "0.04"),
60 "0.10": ("0.02", "0.05"),
61 "0.20": ("0.05", "0.10"),
62 "0.40": ("0.10", "0.20"),
63 "0.50": ("0.10", "0.25"),
64 "1.00": ("0.25", "0.50"),
65 "1": ("0.25", "0.50"),
66 "2.00": ("0.50", "1.00"),
67 "2": ("0.50", "1.00"),
68 "4.00": ("1.00", "2.00"),
69 "4": ("1.00", "2.00"),
70 "6.00": ("1.00", "3.00"),
71 "6": ("1.00", "3.00"),
72 "8.00": ("2.00", "4.00"),
73 "8": ("2.00", "4.00"),
74 "10.00": ("2.00", "5.00"),
75 "10": ("2.00", "5.00"),
76 "16.00": ("4.00", "8.00"),
77 "16": ("4.00", "8.00"),
78 "20.00": ("5.00", "10.00"),
79 "20": ("5.00", "10.00"),
80 "30.00": ("10.00", "15.00"),
81 "30": ("10.00", "15.00"),
82 "40.00": ("10.00", "20.00"),
83 "40": ("10.00", "20.00"),
84 "50.00": ("10.00", "25.00"),
85 "50": ("10.00", "25.00"),
86 "60.00": ("15.00", "30.00"),
87 "60": ("15.00", "30.00"),
88 "80.00": ("20.00", "40.00"),
89 "80": ("20.00", "40.00"),
90 "100.00": ("25.00", "50.00"),
91 "100": ("25.00", "50.00"),
92 "150.00": ("50.00", "75.00"),
93 "150": ("50.00", "75.00"),
94 "200.00": ("50.00", "100.00"),
95 "200": ("50.00", "100.00"),
96 "400.00": ("100.00", "200.00"),
97 "400": ("100.00", "200.00"),
98 "500.00": ("100.00", "250.00"),
99 "500": ("100.00", "250.00"),
100 "600.00": ("150.00", "300.00"),
101 "600": ("150.00", "300.00"),
102 "800.00": ("200.00", "400.00"),
103 "800": ("200.00", "400.00"),
104 "1000.00": ("250.00", "500.00"),
105 "1000": ("250.00", "500.00"),
106 "2000.00": ("500.00", "1000.00"),
107 "2000": ("500.00", "1000.00"),
108 "4000.00": ("1000.00", "2000.00"),
109 "4000": ("1000.00", "2000.00"),
110 "10000.00": ("2500.00", "5000.00"),
111 "10000": ("2500.00", "5000.00"),
112 "20000.00": ("5000.00", "10000.00"),
113 "20000": ("5000.00", "10000.00"),
114 "40000.00": ("10000.00", "20000.00"),
115 "40000": ("10000.00", "20000.00"),
116 }
118 limits = {"No Limit": "nl", "Pot Limit": "pl", "Fixed Limit": "fl", "Limit": "fl", "(NL postflop)": "pn"}
119 games = { # base, category
120 "Hold'em": ("hold", "holdem"),
121 "ShortDeck": ("hold", "6_holdem"),
122 "Omaha": ("hold", "omahahi"),
123 "Omaha Hi/Lo": ("hold", "omahahilo"),
124 "PLO": ("hold", "omahahi"),
125 "PLO-5": ("hold", "5_omahahi"),
126 "PLO-6": ("hold", "6_omahahi"),
127 }
128 # mixes = {
129 # 'HORSE': 'horse',
130 # '8-Game': '8game',
131 # '8-GAME': '8game',
132 # 'HOSE': 'hose',
133 # 'Mixed PLH/PLO': 'plh_plo',
134 # 'Mixed NLH/PLO': 'nlh_plo',
135 # 'Mixed Omaha H/L': 'plo_lo',
136 # 'Mixed Hold\'em': 'mholdem',
137 # 'Mixed Omaha': 'momaha',
138 # 'Triple Stud': '3stud'
139 # } # Legal mixed games
140 currencies = {"$": "USD", "": "T$", "¥": "CNY"}
142 # Poker Hand #TM316262814: Tournament #9364957, WSOP #77: $5,000 No Limit Hold'em Main Event [Flight W], $25M GTD Hold'em No Limit - Level10 (1,000/2,000) - 2020/08/30 15:49:08
143 # Poker Hand #TM247771977: Tournament #5259595, Daily Special $250 Hold'em No Limit - Level2 (60/120) - 2020/06/02 19:17:33
144 # Static regexes
145 re_GameInfo = re.compile(
146 """
147 Poker\sHand\s\#[A-Z]{0,2}(?P<HID>[0-9]+):\s+
148 (\{.*\}\s+)?((?P<TOUR>((Zoom|Rush)\s)?(Tournament))\s\# # open paren of tournament info
149 (?P<TOURNO>\d+),\s
150 # here's how I plan to use LS
151 (?P<TOURNAME>.+?)\s
152 )?
153 # close paren of tournament info
154 (?P<GAME>Hold\'em|Hold\'em|ShortDeck|Omaha|PLO|Omaha\sHi/Lo|PLO\-(5|6))\s
155 (?P<LIMIT>No\sLimit|Fixed\sLimit|Limit|Pot\sLimit|\(NL\spostflop\))?,?\s*
156 (-\s)?
157 (?P<SHOOTOUT>Match.*,\s)?
158 (Level(?P<LEVEL>[IVXLC\d]+)\s?)?
159 \(? # open paren of the stakes
160 (?P<CURRENCY>%(LS)s|)?
161 (ante\s\d+,\s)?
162 ((?P<SB>[,.0-9]+)/(%(LS)s)?(?P<BB>[,.0-9]+)|(?P<BUB>[,.0-9]+))
163 (?P<CAP>\s-\s[%(LS)s]?(?P<CAPAMT>[,.0-9]+)\sCap\s-\s)? # Optional Cap part
164 \s?(?P<ISO>%(LEGAL_ISO)s)?
165 \) # close paren of the stakes
166 (?P<BLAH2>\s\[AAMS\sID:\s[A-Z0-9]+\])? # AAMS ID: in .it HH's
167 \s-\s
168 (?P<DATETIME>.*$)
169 """
170 % substitutions,
171 re.MULTILINE | re.VERBOSE,
172 )
174 re_PlayerInfo = re.compile(
175 """
176 ^\s?Seat\s(?P<SEAT>[0-9]+):\s
177 (?P<PNAME>.*)\s
178 \((%(LS)s)?(?P<CASH>[,.0-9]+)\sin\schips
179 (,\s(%(LS)s)?(?P<BOUNTY>[,.0-9]+)\sbounty)?
180 \)
181 (?P<SITOUT>\sis\ssitting\sout)?"""
182 % substitutions,
183 re.MULTILINE | re.VERBOSE,
184 )
186 re_HandInfo = re.compile(
187 """
188 ^\s?Table\s(ID\s)?\'(?P<TABLE>.+?)\'\s
189 ((?P<MAX>\d+)-max\s)?
190 (?P<PLAY>\(Play\sMoney\)\s)?
191 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
192 re.MULTILINE | re.VERBOSE,
193 )
195 re_Identify = re.compile("Poker\sHand\s\#[A-Z]{0,2}?\d+:")
196 re_SplitHands = re.compile("(?:\s?\n){2,}")
197 re_TailSplitHands = re.compile("(\n\n\n+)")
198 re_Button = re.compile("Seat #(?P<BUTTON>\d+) is the button", re.MULTILINE)
199 re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
200 re_Board2 = re.compile(r"\[(?P<C1>\S\S)\] \[(\S\S)?(?P<C2>\S\S) (?P<C3>\S\S)\]")
201 re_Board3 = re.compile(r"Board\s\[(?P<FLOP>\S\S \S\S \S\S) (?P<TURN>\S\S) (?P<RIVER>\S\S)\]")
202 re_DateTime1 = re.compile(
203 """(?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]+)""",
204 re.MULTILINE,
205 )
206 re_DateTime2 = re.compile(
207 """(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+)""", re.MULTILINE
208 )
209 # revised re including timezone (not currently used):
210 # 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)
212 # These used to be compiled per player, but regression tests say
213 # we don't have to, and it makes life faster.
214 re_PostSB = re.compile(r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[,.0-9]+)" % substitutions, re.MULTILINE)
215 re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[,.0-9]+)" % substitutions, re.MULTILINE)
216 re_PostBUB = re.compile(r"^%(PLYR)s: posts button blind %(CUR)s(?P<BUB>[,.0-9]+)" % substitutions, re.MULTILINE)
217 re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P<ANTE>[,.0-9]+)" % substitutions, re.MULTILINE)
218 re_BringIn = re.compile(
219 r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P<BRINGIN>[,.0-9]+)" % substitutions, re.MULTILINE
220 )
221 re_PostMissed = re.compile(r"^%(PLYR)s: posts missed blind %(CUR)s(?P<SBBB>[,.0-9]+)" % substitutions, re.MULTILINE)
222 re_PostStraddle = re.compile(r"^%(PLYR)s: straddle %(CUR)s(?P<STRADDLE>[,.0-9]+)" % substitutions, re.MULTILINE)
223 re_Action = re.compile(
224 r"""
225 ^%(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat|\sChooses\sto\sEV\sCashout|\sReceives\sCashout)
226 (\s%(CUR)s(?P<BET>[,.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[,.\d]+))? # the number discarded goes in <BET>
227 \s*(and\sis\sall.in)?
228 (and\shas\sreached\sthe\s[%(CUR)s\d\.,]+\scap)?
229 (\son|\scards?)?
230 (\s\(disconnect\))?
231 (\s\[(?P<CARDS>.+?)\])?\s*$"""
232 % substitutions,
233 re.MULTILINE | re.VERBOSE,
234 )
235 re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % substitutions["PLYR"], re.MULTILINE)
236 re_sitsOut = re.compile("^%s sits out" % substitutions["PLYR"], re.MULTILINE)
237 # 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)
238 re_CollectPot = re.compile(
239 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(collected|showed \[.*\] and (won|collected)) \(?%(CUR)s(?P<POT>[,.\d]+)\)?(, mucked| with.*|)"
240 % substitutions,
241 re.MULTILINE,
242 )
243 # Vinsand88 cashed out the hand for $2.19 | Cash Out Fee $0.02
244 re_CollectPot2 = re.compile(r"^%(PLYR)s collected %(CUR)s(?P<POT>[,.\d]+)" % substitutions, re.MULTILINE)
245 re_CollectPot3 = re.compile(
246 r"^%(PLYR)s: Receives Cashout \(%(CUR)s(?P<POT>[,.\d]+)\)" % substitutions, re.MULTILINE
247 )
248 re_CollectPot4 = re.compile(
249 r"^%(PLYR)s: Pays Cashout Risk \(%(CUR)s(?P<POT>[,.\d]+)\)" % substitutions, re.MULTILINE
250 )
251 re_CashedOut = re.compile(r"(Chooses\sto\sEV\sCashout|Receives\sCashout)")
252 re_WinningRankOne = re.compile(
253 "^%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[,\.0-9]+) - congratulations!$" % substitutions,
254 re.MULTILINE,
255 )
256 re_WinningRankOther = re.compile(
257 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place and received %(CUR)s(?P<AMT>[,.0-9]+)\.$"
258 % substitutions,
259 re.MULTILINE,
260 )
261 re_RankOther = re.compile(
262 "^%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place$" % substitutions, re.MULTILINE
263 )
264 re_Cancelled = re.compile("Hand\scancelled", re.MULTILINE)
265 re_Uncalled = re.compile("Uncalled bet \(%(CUR)s(?P<BET>[,.\d]+)\) returned to" % substitutions, re.MULTILINE)
266 # APTEM-89 wins the $0.27 bounty for eliminating Hero
267 # ChazDazzle wins the 22000 bounty for eliminating berkovich609
268 # JKuzja, vecenta split the $50 bounty for eliminating ODYSSES
269 re_Bounty = re.compile(
270 "^%(PLYR)s (?P<SPLIT>split|wins) the %(CUR)s(?P<AMT>[,\.0-9]+) bounty for eliminating (?P<ELIMINATED>.+?)$"
271 % substitutions,
272 re.MULTILINE,
273 )
274 # Amsterdam71 wins $19.90 for eliminating MuKoJla and their own bounty increases by $19.89 to $155.32
275 # Amsterdam71 wins $4.60 for splitting the elimination of Frimble11 and their own bounty increases by $4.59 to $41.32
276 # Amsterdam71 wins the tournament and receives $230.36 - congratulations!
277 re_Progressive = re.compile(
278 """
279 ^%(PLYR)s\swins\s%(CUR)s(?P<AMT>[,\.0-9]+)\s
280 for\s(splitting\sthe\selimination\sof|eliminating)\s(?P<ELIMINATED>.+?)\s
281 and\stheir\sown\sbounty\sincreases\sby\s%(CUR)s(?P<INCREASE>[\.0-9]+)\sto\s%(CUR)s(?P<ENDAMT>[\.0-9]+)$"""
282 % substitutions,
283 re.MULTILINE | re.VERBOSE,
284 )
285 re_Rake = re.compile(
286 """
287 Total\spot\s%(CUR)s(?P<POT>[,\.0-9]+)(.+?)?\s\|\sRake\s%(CUR)s(?P<RAKE>[,\.0-9]+)"""
288 % substitutions,
289 re.MULTILINE | re.VERBOSE,
290 )
292 re_STP = re.compile(
293 """
294 Cash\sDrop\sto\sPot\s:\stotal\s%(CUR)s(?P<AMOUNT>[,\.0-9]+)"""
295 % substitutions,
296 re.MULTILINE | re.VERBOSE,
297 )
299 def compilePlayerRegexs(self, hand):
300 players = set([player[1] for player in hand.players])
301 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
302 self.compiledPlayers = players
303 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
304 subst = {
305 "PLYR": player_re,
306 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) |\(button\) \(big blind\) )?",
307 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|)",
308 }
309 self.re_HeroCards = re.compile(
310 r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE
311 )
312 self.re_ShownCards = re.compile(
313 "^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>.*))?)?$"
314 % subst,
315 re.MULTILINE,
316 )
318 def readSupportedGames(self):
319 return [
320 ["ring", "hold", "nl"],
321 ["ring", "hold", "pl"],
322 ["ring", "hold", "fl"],
323 ["ring", "hold", "pn"],
324 ["ring", "stud", "fl"],
325 ["ring", "draw", "fl"],
326 ["ring", "draw", "pl"],
327 ["ring", "draw", "nl"],
328 ["tour", "hold", "nl"],
329 ["tour", "hold", "pl"],
330 ["tour", "hold", "fl"],
331 ["tour", "hold", "pn"],
332 ["tour", "stud", "fl"],
333 ["tour", "draw", "fl"],
334 ["tour", "draw", "pl"],
335 ["tour", "draw", "nl"],
336 ]
338 def determineGameType(self, handText):
339 info = {}
340 m = self.re_GameInfo.search(handText)
341 if not m:
342 tmp = handText[0:200]
343 log.error(("GGPokerToFpdb.determineGameType: '%s'") % tmp)
344 raise FpdbParseError
346 mg = m.groupdict()
347 if "LIMIT" in mg and mg["LIMIT"] is not None:
348 info["limitType"] = self.limits[mg["LIMIT"]]
349 else:
350 info["limitType"] = "pl"
351 if "GAME" in mg:
352 (info["base"], info["category"]) = self.games[mg["GAME"]]
353 if "SB" in mg and mg["SB"] is not None:
354 info["sb"] = self.clearMoneyString(mg["SB"])
355 if "BB" in mg and mg["BB"] is not None:
356 info["bb"] = self.clearMoneyString(mg["BB"])
357 if "BUB" in mg and mg["BUB"] is not None:
358 info["sb"] = "0"
359 info["bb"] = self.clearMoneyString(mg["BUB"])
360 if "CURRENCY" in mg:
361 info["currency"] = self.currencies[mg["CURRENCY"]]
362 if "CAP" in mg and mg["CAP"] is not None:
363 info["buyinType"] = "cap"
364 else:
365 info["buyinType"] = "regular"
366 if "SPLIT" in mg and mg["SPLIT"] == "Split":
367 info["split"] = True
368 else:
369 info["split"] = False
370 if "TOURNO" in mg and mg["TOURNO"] is None:
371 info["type"] = "ring"
372 else:
373 info["type"] = "tour"
374 if "ZOOM" in mg["TOUR"]:
375 info["fast"] = True
377 if info.get("currency") in ("T$", None) and info["type"] == "ring":
378 info["currency"] = "play"
380 if info["limitType"] == "fl" and info["bb"] is not None:
381 if info["type"] == "ring":
382 try:
383 info["sb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][0]
384 info["bb"] = self.Lim_Blinds[self.clearMoneyString(mg["BB"])][1]
385 except KeyError:
386 tmp = handText[0:200]
387 log.error(
388 ("GGPokerToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") % (mg["BB"], tmp)
389 )
390 raise FpdbParseError
391 else:
392 info["sb"] = str((Decimal(self.clearMoneyString(mg["SB"])) / 2).quantize(Decimal("0.01")))
393 info["bb"] = str(Decimal(self.clearMoneyString(mg["SB"])).quantize(Decimal("0.01")))
395 return info
397 def readHandInfo(self, hand):
398 # First check if partial
399 if hand.handText.count("*** SUMMARY ***") != 1:
400 raise FpdbHandPartial(("Hand is not cleanly split into pre and post Summary"))
402 info = {}
403 m = self.re_HandInfo.search(hand.handText, re.DOTALL)
404 m2 = self.re_GameInfo.search(hand.handText)
405 if m is None or m2 is None:
406 tmp = hand.handText[0:200]
407 log.error(("GGPokerToFpdb.readHandInfo: '%s'") % tmp)
408 raise FpdbParseError
410 info.update(m.groupdict())
411 info.update(m2.groupdict())
413 # log.debug("readHandInfo: %s" % info)
414 for key in info:
415 if key == "DATETIME":
416 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other)
417 # 2008/08/17 - 01:14:43 (ET)
418 # 2008/09/07 06:23:14 ET
419 datetimestr = "2000/01/01 00:00:00" # default used if time not found
420 m1 = self.re_DateTime1.finditer(info[key])
421 for a in m1:
422 datetimestr = "%s/%s/%s %s:%s:%s" % (
423 a.group("Y"),
424 a.group("M"),
425 a.group("D"),
426 a.group("H"),
427 a.group("MIN"),
428 a.group("S"),
429 )
430 # tz = a.group('TZ') # just assume ET??
431 # print " tz = ", tz, " datetime =", datetimestr
432 hand.startTime = datetime.datetime.strptime(
433 datetimestr, "%Y/%m/%d %H:%M:%S"
434 ) # also timezone at end, e.g. " ET"
435 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
437 if key == "HID":
438 hand.handid = info[key]
439 if key == "TOURNO":
440 hand.tourNo = info[key]
441 if key == "BUYIN":
442 if hand.tourNo is not None:
443 # print "DEBUG: info['BUYIN']: %s" % info['BUYIN']
444 # print "DEBUG: info['BIAMT']: %s" % info['BIAMT']
445 # print "DEBUG: info['BIRAKE']: %s" % info['BIRAKE']
446 # print "DEBUG: info['BOUNTY']: %s" % info['BOUNTY']
447 if info[key].strip() == "Freeroll":
448 hand.buyin = 0
449 hand.fee = 0
450 hand.buyinCurrency = "FREE"
451 elif info[key].strip() == "":
452 hand.buyin = 0
453 hand.fee = 0
454 hand.buyinCurrency = "NA"
455 else:
456 if info[key].find("$") != -1:
457 hand.buyinCurrency = "USD"
458 elif info[key].find("£") != -1:
459 hand.buyinCurrency = "GBP"
460 elif info[key].find("€") != -1:
461 hand.buyinCurrency = "EUR"
462 elif info[key].find("₹") != -1:
463 hand.buyinCurrency = "INR"
464 elif info[key].find("¥") != -1:
465 hand.buyinCurrency = "CNY"
466 elif info[key].find("FPP") != -1:
467 hand.buyinCurrency = "PSFP"
468 elif info[key].find("SC") != -1:
469 hand.buyinCurrency = "PSFP"
470 elif re.match("^[0-9+]*$", info[key].strip()):
471 hand.buyinCurrency = "play"
472 else:
473 # FIXME: handle other currencies, play money
474 log.error(
475 ("GGPokerToFpdb.readHandInfo: Failed to detect currency.")
476 + " Hand ID: %s: '%s'" % (hand.handid, info[key])
477 )
478 raise FpdbParseError
480 info["BIAMT"] = info["BIAMT"].strip("$€£FPPSC₹")
482 if hand.buyinCurrency != "PSFP":
483 if info["BOUNTY"] is not None:
484 # There is a bounty, Which means we need to switch BOUNTY and BIRAKE values
485 tmp = info["BOUNTY"]
486 info["BOUNTY"] = info["BIRAKE"]
487 info["BIRAKE"] = tmp
488 info["BOUNTY"] = info["BOUNTY"].strip("$€£₹") # Strip here where it isn't 'None'
489 hand.koBounty = int(100 * Decimal(info["BOUNTY"]))
490 hand.isKO = True
491 else:
492 hand.isKO = False
494 info["BIRAKE"] = info["BIRAKE"].strip("$€£₹")
496 hand.buyin = int(100 * Decimal(info["BIAMT"])) + hand.koBounty
497 hand.fee = int(100 * Decimal(info["BIRAKE"]))
498 else:
499 hand.buyin = int(100 * Decimal(info["BIAMT"]))
500 hand.fee = 0
501 if key == "LEVEL":
502 hand.level = info[key]
503 if key == "SHOOTOUT" and info[key] is not None:
504 hand.isShootout = True
505 if key == "TABLE":
506 tablesplit = re.split(" ", info[key])
507 if hand.tourNo is not None and len(tablesplit) > 1:
508 hand.tablename = tablesplit[1]
509 else:
510 hand.tablename = info[key]
511 if key == "TOURNAME":
512 hand.tourneyName = info[key]
513 if key == "BUTTON":
514 hand.buttonpos = info[key]
515 if key == "MAX" and info[key] is not None:
516 hand.maxseats = int(info[key])
518 if "Zoom" in self.in_path or "Rush" in self.in_path:
519 (hand.gametype["fast"], hand.isFast) = (True, True)
521 if self.re_Cancelled.search(hand.handText):
522 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid)
524 def readButton(self, hand):
525 m = self.re_Button.search(hand.handText)
526 if m:
527 hand.buttonpos = int(m.group("BUTTON"))
528 else:
529 log.info("readButton: " + ("not found"))
531 def readPlayerStacks(self, hand):
532 pre, post = hand.handText.split("*** SUMMARY ***")
533 m = self.re_PlayerInfo.finditer(pre)
534 for a in m:
535 hand.addPlayer(
536 int(a.group("SEAT")),
537 a.group("PNAME"),
538 self.clearMoneyString(a.group("CASH")),
539 None,
540 a.group("SITOUT"),
541 self.clearMoneyString(a.group("BOUNTY")),
542 )
544 def markStreets(self, hand):
545 # There is no marker between deal and draw in Stars single draw games
546 # this upsets the accounting, incorrectly sets handsPlayers.cardxx and
547 # in consequence the mucked-display is incorrect.
548 # Attempt to fix by inserting a DRAW marker into the hand text attribute
550 if hand.gametype["category"] in ("27_1draw", "fivedraw"):
551 # isolate the first discard/stand pat line (thanks Carl for the regex)
552 discard_split = re.split(r"(?:(.+(?: stands pat|: discards).+))", hand.handText, re.DOTALL)
553 if len(hand.handText) == len(discard_split[0]):
554 # handText was not split, no DRAW street occurred
555 pass
556 else:
557 # DRAW street found, reassemble, with DRAW marker added
558 discard_split[0] += "*** DRAW ***\r\n"
559 hand.handText = ""
560 for i in discard_split:
561 hand.handText += i
563 # PREFLOP = ** Dealing down cards **
564 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
565 if hand.gametype["base"] in ("hold"):
566 m = re.search(
567 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>(.+(?P<FLOPET>\[\S\S\]))?.+(?=\*\*\* (FIRST\s)?FLOP \*\*\*)|.+)"
568 r"(\*\*\* FLOP \*\*\*(?P<FLOP> ?(\[\S\S\] )?\[(\S\S ?)?\S\S \S\S\].+(?=\*\*\* (FIRST\s)?TURN \*\*\*)|.+))?"
569 r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* (FIRST\s)?RIVER \*\*\*)|.+))?"
570 r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?"
571 r"(\*\*\* FIRST FLOP \*\*\*(?P<FLOP1> ?(\[\S\S\] )?\[\S\S \S\S \S\S\].+(?=\*\*\* FIRST TURN \*\*\*)|.+))?"
572 r"(\*\*\* FIRST TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN1>\[\S\S\].+(?=\*\*\* FIRST RIVER \*\*\*)|.+))?"
573 r"(\*\*\* FIRST RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER1>\[\S\S\].+?(?=\*\*\* SECOND (FLOP|TURN|RIVER) \*\*\*)|.+))?"
574 r"(\*\*\* SECOND FLOP \*\*\*(?P<FLOP2> ?(\[\S\S\] )?\[\S\S \S\S \S\S\].+(?=\*\*\* SECOND TURN \*\*\*)|.+))?"
575 r"(\*\*\* SECOND TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN2>\[\S\S\].+(?=\*\*\* SECOND RIVER \*\*\*)|.+))?"
576 r"(\*\*\* SECOND RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER2>\[\S\S\].+?(?=\*\*\* THIRD (FLOP|TURN|RIVER) \*\*\*)|.+))?"
577 r"(\*\*\* THIRD FLOP \*\*\*(?P<FLOP3> ?(\[\S\S\] )?\[\S\S \S\S \S\S\].+(?=\*\*\* THIRD TURN \*\*\*)|.+))?"
578 r"(\*\*\* THIRD TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN3>\[\S\S\].+(?=\*\*\* THIRD RIVER \*\*\*)|.+))?"
579 r"(\*\*\* THIRD RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER3>\[\S\S\].+))?",
580 hand.handText,
581 re.DOTALL,
582 )
583 hand.addStreets(m)
584 if "AFO" in hand.tablename:
585 m1 = self.re_Board3.search(hand.handText)
586 if m1:
587 hand.streets.update(
588 {
589 "FLOP": "[%s] %s" % (m1.group("FLOP"), hand.streets["FLOP"]),
590 "TURN": "[%s]" % m1.group("TURN"),
591 "RIVER": "[%s]" % m1.group("RIVER"),
592 }
593 )
595 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
596 if (
597 street != "FLOPET" or hand.streets.get("FLOP") is None
598 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
599 m = self.re_Board.search(hand.streets[street])
600 hand.setCommunityCards(street, m.group("CARDS").split(" "))
601 if street in ("FLOP3", "TURN3", "RIVER3"):
602 hand.runItTimes = 3
603 elif street in ("FLOP2", "TURN2", "RIVER2"):
604 hand.runItTimes = 2
606 def readSTP(self, hand):
607 m = self.re_STP.search(hand.handText)
608 if m:
609 hand.addSTP(m.group("AMOUNT"))
611 def readAntes(self, hand):
612 log.debug(("reading antes"))
613 m = self.re_Antes.finditer(hand.handText)
614 for player in m:
615 # ~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
616 hand.addAnte(player.group("PNAME"), self.clearMoneyString(player.group("ANTE")))
618 def readBringIn(self, hand):
619 m = self.re_BringIn.search(hand.handText, re.DOTALL)
620 if m:
621 # ~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
622 hand.addBringIn(m.group("PNAME"), self.clearMoneyString(m.group("BRINGIN")))
624 def readBlinds(self, hand):
625 liveBlind, straddles = True, {}
626 for a in self.re_PostSB.finditer(hand.handText):
627 if liveBlind:
628 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB")))
629 liveBlind = False
630 else:
631 names = [p[1] for p in hand.players]
632 if "Big Blind" in names or "Small Blind" in names or "Dealer" in names:
633 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB")))
634 else:
635 # Post dead blinds as ante
636 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SB")))
637 for a in self.re_PostMissed.finditer(hand.handText):
638 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SBBB")))
639 for a in self.re_PostStraddle.finditer(hand.handText):
640 if straddles.get(a.group("PNAME")) is None:
641 straddles[a.group("PNAME")] = self.clearMoneyString(a.group("STRADDLE"))
642 elif Decimal(straddles[a.group("PNAME")]) < Decimal(self.clearMoneyString(a.group("STRADDLE"))):
643 straddles[a.group("PNAME")] = self.clearMoneyString(a.group("STRADDLE"))
644 for p, amount in list(straddles.items()):
645 hand.addBlind(p, "straddle", amount)
646 for a in self.re_PostBB.finditer(hand.handText):
647 if straddles.get(a.group("PNAME")) is None:
648 hand.addBlind(a.group("PNAME"), "big blind", self.clearMoneyString(a.group("BB")))
649 for a in self.re_PostBUB.finditer(hand.handText):
650 hand.addBlind(a.group("PNAME"), "button blind", self.clearMoneyString(a.group("BUB")))
652 def readHoleCards(self, hand):
653 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
654 # we need to grab hero's cards
655 for street in ("PREFLOP", "DEAL"):
656 if street in hand.streets.keys():
657 m = self.re_HeroCards.finditer(hand.streets[street])
658 for found in m:
659 # if m == None:
660 # hand.involved = False
661 # else:
662 hand.hero = found.group("PNAME")
663 if "cards" not in found.group("NEWCARDS"):
664 newcards = found.group("NEWCARDS").split(" ")
665 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
667 for street, text in list(hand.streets.items()):
668 if not text or street in ("PREFLOP", "DEAL"):
669 continue # already done these
670 m = self.re_HeroCards.finditer(hand.streets[street])
671 for found in m:
672 player = found.group("PNAME")
673 if found.group("NEWCARDS") is None:
674 newcards = []
675 else:
676 newcards = found.group("NEWCARDS").split(" ")
677 if found.group("OLDCARDS") is None:
678 oldcards = []
679 else:
680 oldcards = found.group("OLDCARDS").split(" ")
682 if street == "THIRD" and len(newcards) == 3: # hero in stud game
683 hand.hero = player
684 hand.dealt.add(player) # need this for stud??
685 hand.addHoleCards(
686 street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False
687 )
688 else:
689 hand.addHoleCards(
690 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
691 )
693 def readAction(self, hand, street):
694 if hand.gametype["split"] and street in hand.communityStreets:
695 s = street + "2"
696 else:
697 s = street
698 if not hand.streets[s]:
699 return
700 m = self.re_Action.finditer(hand.streets[s])
701 for action in m:
702 # acts = action.groupdict()
703 # log.error("DEBUG: %s acts: %s" % (street, acts))
704 if action.group("ATYPE") == " folds":
705 hand.addFold(street, action.group("PNAME"))
706 elif action.group("ATYPE") == " checks":
707 hand.addCheck(street, action.group("PNAME"))
708 elif action.group("ATYPE") == " Chooses to EV Cashout" or action.group("ATYPE") == " Receives Cashout":
709 hand.addCashout(street, action.group("PNAME"))
710 elif action.group("ATYPE") == " calls":
711 hand.addCall(street, action.group("PNAME"), self.clearMoneyString(action.group("BET")))
712 elif action.group("ATYPE") == " raises":
713 if action.group("BETTO") is not None:
714 hand.addRaiseTo(street, action.group("PNAME"), self.clearMoneyString(action.group("BETTO")))
715 elif action.group("BET") is not None:
716 hand.addCallandRaise(street, action.group("PNAME"), self.clearMoneyString(action.group("BET")))
717 elif action.group("ATYPE") == " bets":
718 hand.addBet(street, action.group("PNAME"), self.clearMoneyString(action.group("BET")))
719 elif action.group("ATYPE") == " discards":
720 hand.addDiscard(street, action.group("PNAME"), action.group("BET"), action.group("CARDS"))
721 elif action.group("ATYPE") == " stands pat":
722 hand.addStandsPat(street, action.group("PNAME"), action.group("CARDS"))
723 else:
724 log.debug(
725 ("DEBUG:")
726 + " "
727 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE"))
728 )
730 def readShowdownActions(self, hand):
731 # TODO: pick up mucks also??
732 for shows in self.re_ShowdownAction.finditer(hand.handText):
733 cards = shows.group("CARDS").split(" ")
734 hand.addShownCards(cards, shows.group("PNAME"))
736 def readTourneyResults(self, hand):
737 """Reads knockout bounties and add them to the koCounts dict"""
738 if self.re_Bounty.search(hand.handText) is None:
739 koAmounts = {}
740 winner = None
741 # %(PLYR)s wins %(CUR)s(?P<AMT>[\.0-9]+) for eliminating (?P<ELIMINATED>.+?) and their own bounty increases by %(CUR)s(?P<INCREASE>[\.0-9]+) to %(CUR)s(?P<ENDAMT>[\.0-9]+)
742 # re_WinningRankOne = re.compile(u"^%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[\.0-9]+) - congratulations!$" % substitutions, re.MULTILINE)
743 for a in self.re_Progressive.finditer(hand.handText):
744 if a.group("PNAME") not in koAmounts:
745 koAmounts[a.group("PNAME")] = 0
746 koAmounts[a.group("PNAME")] += 100 * Decimal(a.group("AMT"))
747 hand.endBounty[a.group("PNAME")] = 100 * Decimal(a.group("ENDAMT"))
748 hand.isProgressive = True
750 m = self.re_WinningRankOne.search(hand.handText)
751 if m:
752 winner = m.group("PNAME")
754 if hand.koBounty > 0:
755 for pname, amount in koAmounts.iteritems():
756 if pname == winner:
757 # end = amount + hand.endBounty[pname]
758 hand.koCounts[pname] = (amount + hand.endBounty[pname]) / Decimal(hand.koBounty)
759 else:
760 # end = 0
761 hand.koCounts[pname] = amount / Decimal(hand.koBounty)
762 else:
763 for a in self.re_Bounty.finditer(hand.handText):
764 if a.group("SPLIT") == "split":
765 pnames = a.group("PNAME").split(", ")
766 for pname in pnames:
767 if pname not in hand.koCounts:
768 hand.koCounts[pname] = 0
769 hand.koCounts[pname] += 1 / Decimal(len(pnames))
770 else:
771 if a.group("PNAME") not in hand.koCounts:
772 hand.koCounts[a.group("PNAME")] = 0
773 hand.koCounts[a.group("PNAME")] += 1
775 def readCollectPot(self, hand):
776 # Bovada walks are calculated incorrectly in converted PokerStars hands
777 # acts, bovadaUncalled_v1, bovadaUncalled_v2, blindsantes, adjustment = (
778 # hand.actions.get("PREFLOP"),
779 # False,
780 # False,
781 # 0,
782 # 0,
783 # )
784 # names = [p[1] for p in hand.players]
785 i = 0
786 pre, post = hand.handText.split("*** SUMMARY ***")
787 hand.cashedOut = self.re_CashedOut.search(pre) is not None
788 if hand.runItTimes == 0 and hand.cashedOut is False:
789 for m in self.re_CollectPot.finditer(post):
790 pot = self.clearMoneyString(m.group("POT"))
791 hand.addCollectPot(player=m.group("PNAME"), pot=pot)
792 i += 1
793 if i == 0:
794 for m in self.re_CollectPot2.finditer(pre):
795 pot = self.clearMoneyString(m.group("POT"))
796 hand.addCollectPot(player=m.group("PNAME"), pot=pot)
797 if hand.cashedOut:
798 for m in self.re_CollectPot3.finditer(pre):
799 pot = self.clearMoneyString(m.group("POT"))
800 hand.addCollectPot(player=m.group("PNAME"), pot=pot)
801 for m in self.re_CollectPot4.finditer(pre):
802 pot = "-" + self.clearMoneyString(m.group("POT"))
803 hand.addCollectPot(player=m.group("PNAME"), pot=pot)
805 def readShownCards(self, hand):
806 for m in self.re_ShownCards.finditer(hand.handText):
807 if m.group("CARDS") is not None:
808 cards = m.group("CARDS")
809 cards = cards.split(" ") # needs to be a list, not a set--stud needs the order
810 string = m.group("STRING")
811 if m.group("STRING2"):
812 string += "|" + m.group("STRING2")
814 (shown, mucked) = (False, False)
815 if m.group("SHOWED") == "showed":
816 shown = True
817 elif m.group("SHOWED") == "mucked":
818 mucked = True
820 # print "DEBUG: hand.addShownCards(%s, %s, %s, %s)" %(cards, m.group('PNAME'), shown, mucked)
821 hand.addShownCards(cards=cards, player=m.group("PNAME"), shown=shown, mucked=mucked, string=string)
823 @staticmethod
824 def getTableTitleRe(type, table_name=None, tournament=None, table_number=None):
825 "Returns string to search in windows titles"
826 regex = re.escape(str(table_name))
827 if type == "tour":
828 regex = re.escape(str(tournament)) + ".* (Table|Tisch) " + re.escape(str(table_number))
829 log.info(
830 "Stars.getTableTitleRe: table_name='%s' tournament='%s' table_number='%s'"
831 % (table_name, tournament, table_number)
832 )
833 log.info("Stars.getTableTitleRe: returns: '%s'" % (regex))
834 return regex