Coverage for PokerTrackerToFpdb.py: 0%
437 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-2012, Chaz Littlejohn
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########################################################################
21from __future__ import division
23from past.utils import old_div
24# import L10n
25# _ = L10n.get_translation()
27# TODO: straighten out discards for draw games
29from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial
30from decimal import Decimal
31import re
32import logging
33import datetime
36import MergeStructures
39# PokerTracker HH Format
40log = logging.getLogger("parser")
43class PokerTracker(HandHistoryConverter):
44 # Class Variables
45 Structures = None
46 filetype = "text"
47 codepage = ("utf8", "cp1252")
48 sym = {"USD": "\$", "CAD": "\$", "T$": "", "EUR": "€", "GBP": "\£", "play": ""} # ADD Euro, Sterling, etc HERE
49 substitutions = {
50 "LEGAL_ISO": "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
51 "LS": "\$|€|\£|", # legal currency symbols - Euro(cp1252, utf-8)
52 "PLYR": r"(?P<PNAME>.+?)",
53 "NUM": ".,\d",
54 "CUR": "(\$|€||\£|)",
55 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button\) \(small blind\) |\(button\) \(big blind\) )?",
56 }
58 # translations from captured groups to fpdb info strings
59 Lim_Blinds = {
60 "0.04": ("0.01", "0.02"),
61 "0.08": ("0.02", "0.04"),
62 "0.10": ("0.02", "0.05"),
63 "0.20": ("0.05", "0.10"),
64 "0.40": ("0.10", "0.20"),
65 "0.50": ("0.10", "0.25"),
66 "1.00": ("0.25", "0.50"),
67 "1": ("0.25", "0.50"),
68 "2.00": ("0.50", "1.00"),
69 "2": ("0.50", "1.00"),
70 "4.00": ("1.00", "2.00"),
71 "4": ("1.00", "2.00"),
72 "6.00": ("1.00", "3.00"),
73 "6": ("1.00", "3.00"),
74 "8.00": ("2.00", "4.00"),
75 "8": ("2.00", "4.00"),
76 "10.00": ("2.00", "5.00"),
77 "10": ("2.00", "5.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 "60.00": ("15.00", "30.00"),
85 "60": ("15.00", "30.00"),
86 "80.00": ("20.00", "40.00"),
87 "80": ("20.00", "40.00"),
88 "100.00": ("25.00", "50.00"),
89 "100": ("25.00", "50.00"),
90 "150.00": ("50.00", "75.00"),
91 "150": ("50.00", "75.00"),
92 "200.00": ("50.00", "100.00"),
93 "200": ("50.00", "100.00"),
94 "400.00": ("100.00", "200.00"),
95 "400": ("100.00", "200.00"),
96 "800.00": ("200.00", "400.00"),
97 "800": ("200.00", "400.00"),
98 "1000.00": ("250.00", "500.00"),
99 "1000": ("250.00", "500.00"),
100 "2000.00": ("500.00", "1000.00"),
101 "2000": ("500.00", "1000.00"),
102 }
104 limits = {"NL": "nl", "No Limit": "nl", "Pot Limit": "pl", "PL": "pl", "FL": "fl", "Limit": "fl", "LIMIT": "fl"}
105 games = { # base, category
106 "Hold'em": ("hold", "holdem"),
107 "Texas Hold'em": ("hold", "holdem"),
108 "Holdem": ("hold", "holdem"),
109 "Omaha": ("hold", "omahahi"),
110 "Omaha Hi": ("hold", "omahahi"),
111 "Omaha Hi/Lo": ("hold", "omahahilo"),
112 }
113 sites = {
114 "EverestPoker Game #": ("Everest", 16),
115 "GAME #": ("iPoker", 14),
116 "MERGE_GAME #": ("Merge", 12),
117 "Merge Game #": ("Merge", 12),
118 "** Game ID ": ("Microgaming", 20),
119 "** Hand # ": ("Microgaming", 20),
120 }
121 currencies = {"€": "EUR", "$": "USD", "": "T$", "£": "GBP"}
123 re_Site = re.compile(
124 "(?P<SITE>EverestPoker\sGame\s\#|GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#|\*{2}\s(Game\sID|Hand\s\#)\s)\d+"
125 )
126 # Static regexes
127 re_GameInfo1 = re.compile(
128 """
129 (?P<SITE>GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#)(?P<HID>[0-9\-]+)(\sVersion:[\d\.]+\s(?P<UNCALLED>Uncalled:Y))?(:?\s+|\s\|\s)
130 (?P<GAME>Holdem|Texas\sHold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\s\s?
131 ((?P<LIMIT>PL|NL|FL|No\sLimit|Limit|LIMIT|Pot\sLimit)\s\s?)?
132 (?P<TOUR>Tournament)?
133 (\(? # open paren of the stakes
134 (?P<CURRENCY>%(LS)s|)?
135 (?P<SB>[%(NUM)s]+)/(%(LS)s)?
136 (?P<BB>[%(NUM)s]+)
137 (?P<BLAH>\s-\s[%(LS)s\d\.]+\sCap\s-\s)? # Optional Cap part
138 \s?(?P<ISO>%(LEGAL_ISO)s)?
139 \)?
140 )?(\s|\s\|\s) # close paren of the stakes
141 (?P<DATETIME>.*$)
142 """
143 % substitutions,
144 re.MULTILINE | re.VERBOSE,
145 )
147 re_GameInfo2 = re.compile(
148 """
149 EverestPoker\sGame\s\#(?P<HID>[0-9]+):\s+
150 (?P<TOUR>Tourney\sID:\s(?P<TOURNO>\d+),\s)?
151 Table\s(?P<TABLE>.+)?
152 \s-\s
153 ((?P<CURRENCY>%(LS)s|)?
154 (?P<SB>[%(NUM)s]+)/(%(LS)s)?
155 (?P<BB>[%(NUM)s]+))?
156 \s-\s
157 (?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\s
158 (?P<GAME>Hold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\s
159 (-\s)?
160 (?P<DATETIME>.*$)
161 """
162 % substitutions,
163 re.MULTILINE | re.VERBOSE,
164 )
166 re_GameInfo3 = re.compile(
167 """
168 (?P<HID>[0-9]+)(\sVersion:\d)?\sstarting\s\-\s(?P<DATETIME>.*$)\s
169 \*\*(?P<TOUR>.+(?P<SPEED>(Turbo|Hyper))?\((?P<TOURNO>\d+)\):Table)?\s(?P<TABLE>.+)\s
170 \[((Multi|Single)\sTable\s)?(?P<GAME>Hold\'em|Omaha|Omaha\sHi|Omaha\sHi/Lo)\]\s
171 \((?P<SB>[%(NUM)s]+)\|(?P<BB>[%(NUM)s]+)\s(?P<LIMIT>NL|FL|PL)\s\-\s(MTT|SNG|STT|(?P<CURRENCY>%(LS)s|)\s?Cash\sGame)(\sseats:(?P<MAX>\d+))?.*\)\s
172 (?P<PLAY>Real|Play)\sMoney
173 """
174 % substitutions,
175 re.MULTILINE | re.VERBOSE,
176 )
178 re_PlayerInfo1 = re.compile(
179 """
180 ^Seat\s(?P<SEAT>[0-9]+):\s
181 (?P<PNAME>.*)\s
182 \((?P<CURRENCY>%(LS)s)?(?P<CASH>[%(NUM)s]+)(\sin\schips)?\)
183 (?P<BUTTON>\sDEALER)?"""
184 % substitutions,
185 re.MULTILINE | re.VERBOSE,
186 )
188 re_PlayerInfo2 = re.compile(
189 """
190 ^(\-\s)?(?P<PNAME>.*)\s
191 sitting\sin\sseat\s(?P<SEAT>[0-9]+)\swith\s
192 (%(LS)s)?(?P<CASH>[%(NUM)s]+)
193 (?P<BUTTON>\s?\[Dealer\])?"""
194 % substitutions,
195 re.MULTILINE | re.VERBOSE,
196 )
198 re_HandInfo_Tour = re.compile(
199 """
200 ^Table\s(?P<TABLE>.*),\s(?P<TOURNO>\d+)(,\s\d+)?\s
201 (?P<TOUR>\(Tournament:\s(.+)?\sBuy\-In:\s(?P<BUYIN>(?P<BIAMT>[%(LS)s\d\.]+)\s?\+?\s?(?P<BIRAKE>[%(LS)s\d\.]+))\))
202 """
203 % substitutions,
204 re.MULTILINE | re.VERBOSE,
205 )
207 re_HandInfo_Cash = re.compile(
208 """
209 ^Table\s(?P<TABLE>[^,]+?)(,\sSeats\s(?P<MAX>\d+))?$"""
210 % substitutions,
211 re.MULTILINE | re.VERBOSE,
212 )
214 re_Identify = re.compile(
215 "(EverestPoker\sGame\s\#|GAME\s\#|MERGE_GAME\s\#|Merge\sGame\s\#|\*{2}\s(Game\sID|Hand\s\#)\s)\d+"
216 )
217 re_SplitHands = re.compile("\n\n\n+?")
218 re_TailSplitHands = re.compile("(\n\n\n+)")
219 re_Button = re.compile("The button is in seat #(?P<BUTTON>\d+)", re.MULTILINE)
220 re_Board1 = re.compile(r"\[(?P<CARDS>.+)\]")
221 re_Board2 = re.compile(r":\s(?P<CARDS>.+)\n")
222 re_DateTime1 = re.compile(
223 """(?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]+)""",
224 re.MULTILINE,
225 )
226 re_DateTime2 = re.compile(
227 """(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})\/(?P<Y>[0-9]{4})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)""",
228 re.MULTILINE,
229 )
230 re_DateTime3 = re.compile(
231 """(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)[\- ]+(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})""",
232 re.MULTILINE,
233 )
234 # revised re including timezone (not currently used):
235 # 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)
237 # These used to be compiled per player, but regression tests say
238 # we don't have to, and it makes life faster.
239 re_PostSB = re.compile(
240 r"^%(PLYR)s:? ((posts|posted) the small blind( of)?|(Post )?SB) (\- )?%(CUR)s(?P<SB>[%(NUM)s]+)"
241 % substitutions,
242 re.MULTILINE,
243 )
244 re_PostBB = re.compile(
245 r"^%(PLYR)s:? ((posts|posted) the big blind( of)?|posts the dead blind of|(Post )?BB) (\- )?%(CUR)s(?P<BB>[%(NUM)s]+)"
246 % substitutions,
247 re.MULTILINE,
248 )
249 re_Antes = re.compile(
250 r"^%(PLYR)s:? ((posts|posted) (the )?ante( of)?|(Post )?Ante) (\- )?%(CUR)s(?P<ANTE>[%(NUM)s]+)"
251 % substitutions,
252 re.MULTILINE,
253 )
254 re_PostBoth1 = re.compile(
255 r"^%(PLYR)s:? (posts|Post|(Post )?DB) %(CUR)s(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE
256 )
257 re_PostBoth2 = re.compile(
258 r"^%(PLYR)s:? posted to play \- %(CUR)s(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE
259 )
260 re_HeroCards1 = re.compile(
261 r"^Dealt to %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE
262 )
263 re_HeroCards2 = re.compile(
264 r"rd(s)? to %(PLYR)s: (?P<OLDCARDS>NONE)?(?P<NEWCARDS>.+)\n" % substitutions, re.MULTILINE
265 )
266 re_Action1 = re.compile(
267 r"""^%(PLYR)s:?(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sBet|\sCheck|\sRaise(\sto)?|\sCall|\sFold|\sAllin)(?P<RAISETO>\s\(NF\))?(\sto)?(\s%(CUR)s(?P<BET>[%(NUM)s]+))?\s*(and\sis\sall.in)?(and\shas\sreached\sthe\s\[%(CUR)s\d\.,]+\scap)?(\son|\scards?)?(\s\[(?P<CARDS>.+?)\])?\s*$"""
268 % substitutions,
269 re.MULTILINE | re.VERBOSE,
270 )
271 re_Action2 = re.compile(
272 r"""
273 ^%(PLYR)s(?P<ATYPE>\sbet|\schecked|\sraised(\sto)?|\scalled|\sfolded|\swent\sall\-in)
274 (\s(\-\s)?%(CUR)s(?P<BET>[%(NUM)s]+))?\s*$"""
275 % substitutions,
276 re.MULTILINE | re.VERBOSE,
277 )
278 re_ShownCards1 = re.compile(
279 "^%(PLYR)s:? (?P<SHOWED>shows|Shows|mucked) \[(?P<CARDS>.*)\]" % substitutions, re.MULTILINE
280 )
281 re_ShownCards2 = re.compile("^%(PLYR)s (?P<SHOWED>shows|mucks): (?P<CARDS>.+)\n" % substitutions, re.MULTILINE)
282 re_CollectPot1 = re.compile(r"^%(PLYR)s:? (collects|wins) %(CUR)s(?P<POT>[%(NUM)s]+)" % substitutions, re.MULTILINE)
283 re_CollectPot2 = re.compile(r"^%(PLYR)s wins %(CUR)s(?P<POT>[%(NUM)s]+)" % substitutions, re.MULTILINE)
284 re_Cancelled = re.compile("Hand\scancelled", re.MULTILINE)
285 re_Tournament = re.compile("\(Tournament:")
286 re_Hole = re.compile(r"\*\*\sDealing\scard")
287 re_Currency = re.compile(r"\s\-\s(?P<CURRENCY>%(CUR)s)[%(NUM)s]+\s(Max|Min)" % substitutions)
288 re_Max = re.compile(r"(\s(?P<MAX>(HU|\d+\sSeat))\s)")
289 re_FastFold = re.compile(r"^%(PLYR)s\sQuick\sFolded" % substitutions, re.MULTILINE)
291 def compilePlayerRegexs(self, hand):
292 pass
294 def readSupportedGames(self):
295 return [
296 ["ring", "hold", "nl"],
297 ["ring", "hold", "pl"],
298 ["ring", "hold", "fl"],
299 ["tour", "hold", "nl"],
300 ["tour", "hold", "pl"],
301 ["tour", "hold", "fl"],
302 ]
304 def determineGameType(self, handText):
305 m = self.re_Site.search(handText)
306 if not m:
307 tmp = handText[0:200]
308 log.error(("PokerTrackerToFpdb.determineGameType: '%s'") % tmp)
309 raise FpdbParseError
311 self.sitename = self.sites[m.group("SITE")][0]
312 self.siteId = self.sites[m.group("SITE")][1] # Needs to match id entry in Sites database
314 info = {}
315 if self.sitename in ("iPoker", "Merge"):
316 m = self.re_GameInfo1.search(handText)
317 elif self.sitename == "Everest":
318 m = self.re_GameInfo2.search(handText)
319 elif self.sitename == "Microgaming":
320 m = self.re_GameInfo3.search(handText)
321 if not m:
322 tmp = handText[0:200]
323 log.error(("PokerTrackerToFpdb.determineGameType: '%s'") % tmp)
324 raise FpdbParseError
326 mg = m.groupdict()
327 # print 'DEBUG determineGameType', '%r' % mg
328 if "LIMIT" in mg and mg["LIMIT"] is not None:
329 info["limitType"] = self.limits[mg["LIMIT"]]
330 if "GAME" in mg:
331 (info["base"], info["category"]) = self.games[mg["GAME"]]
332 if mg["LIMIT"] is None:
333 if info["category"] == "omahahi":
334 info["limitType"] = "pl"
335 elif info["category"] == "holdem":
336 info["limitType"] = "nl"
337 if "SB" in mg:
338 info["sb"] = self.clearMoneyString(mg["SB"])
339 if "BB" in mg:
340 info["bb"] = self.clearMoneyString(mg["BB"])
341 if "CURRENCY" in mg and mg["CURRENCY"] is not None:
342 if self.sitename == "Microgaming" and not mg["CURRENCY"]:
343 m1 = self.re_Currency.search(mg["TABLE"])
344 if m1:
345 mg["CURRENCY"] = m1.group("CURRENCY")
346 if self.sitename == "iPoker" and not mg["CURRENCY"]:
347 m1 = self.re_PlayerInfo1.search(handText)
348 if m1:
349 mg["CURRENCY"] = m1.group("CURRENCY")
350 info["currency"] = self.currencies[mg["CURRENCY"]]
351 if "MIXED" in mg:
352 if mg["MIXED"] is not None:
353 info["mix"] = self.mixes[mg["MIXED"]]
355 if "TOUR" in mg and mg["TOUR"] is not None:
356 info["type"] = "tour"
357 info["currency"] = "T$"
358 else:
359 info["type"] = "ring"
361 if info["limitType"] == "fl" and info["bb"] is not None:
362 if info["type"] == "ring":
363 try:
364 bb = self.clearMoneyString(mg["BB"])
365 info["sb"] = self.Lim_Blinds[bb][0]
366 info["bb"] = self.Lim_Blinds[bb][1]
367 except KeyError:
368 tmp = handText[0:200]
369 log.error(
370 ("PokerTrackerToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'")
371 % (mg["BB"], tmp)
372 )
373 raise FpdbParseError
374 else:
375 sb = self.clearMoneyString(mg["SB"])
376 info["sb"] = str((old_div(Decimal(sb), 2)).quantize(Decimal("0.01")))
377 info["bb"] = str(Decimal(sb).quantize(Decimal("0.01")))
379 return info
381 def readHandInfo(self, hand):
382 info, m = {}, None
383 if self.sitename in ("iPoker", "Merge"):
384 m3 = self.re_Tournament.search(hand.handText, re.DOTALL)
385 if m3:
386 m = self.re_HandInfo_Tour.search(hand.handText, re.DOTALL)
387 else:
388 m = self.re_HandInfo_Cash.search(hand.handText, re.DOTALL)
389 m2 = self.re_GameInfo1.search(hand.handText)
390 elif self.sitename == "Everest":
391 m2 = self.re_GameInfo2.search(hand.handText)
392 elif self.sitename == "Microgaming":
393 m2 = self.re_GameInfo3.search(hand.handText)
394 if (m is None and self.sitename not in ("Everest", "Microgaming")) or m2 is None:
395 tmp = hand.handText[0:200]
396 log.error(("PokerTrackerToFpdb.readHandInfo: '%s'") % tmp)
397 raise FpdbParseError
399 if self.sitename not in ("Everest", "Microgaming"):
400 info.update(m.groupdict())
401 info.update(m2.groupdict())
403 if self.sitename != "Everest" and info.get("UNCALLED") is None:
404 hand.setUncalledBets(True)
406 # print 'readHandInfo', info
407 for key in info:
408 if key == "DATETIME":
409 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other)
410 # 2008/08/17 - 01:14:43 (ET)
411 # 2008/09/07 06:23:14 ET
412 if self.sitename in ("iPoker", "Microgaming"):
413 m1 = self.re_DateTime1.finditer(info[key])
414 elif self.sitename == "Merge":
415 m1 = self.re_DateTime2.finditer(info[key])
416 elif self.sitename == "Everest":
417 m1 = self.re_DateTime3.finditer(info[key])
418 datetimestr = "2000/01/01 00:00:00" # default used if time not found
419 for a in m1:
420 datetimestr = "%s/%s/%s %s:%s:%s" % (
421 a.group("Y"),
422 a.group("M"),
423 a.group("D"),
424 a.group("H"),
425 a.group("MIN"),
426 a.group("S"),
427 )
428 # tz = a.group('TZ') # just assume ET??
429 # print " tz = ", tz, " datetime =", datetimestr
430 hand.startTime = datetime.datetime.strptime(
431 datetimestr, "%Y/%m/%d %H:%M:%S"
432 ) # also timezone at end, e.g. " ET"
433 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
434 if key == "HID":
435 if self.sitename == "Merge":
436 hand.handid = info[key][:8] + info[key][9:]
437 else:
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 tourneyname = ""
444 if self.sitename == "Merge":
445 if self.Structures is None:
446 self.Structures = MergeStructures.MergeStructures()
447 tourneyname = re.split(",", m.group("TABLE"))[0].strip()
448 structure = self.Structures.lookupSnG(tourneyname, hand.startTime)
449 if structure is not None:
450 hand.buyin = int(100 * structure["buyIn"])
451 hand.fee = int(100 * structure["fee"])
452 hand.buyinCurrency = structure["currency"]
453 hand.maxseats = structure["seats"]
454 hand.isSng = True
455 else:
456 # print 'DEBUG', 'no match for tourney %s tourNo %s' % (tourneyname, hand.tourNo)
457 hand.buyin = 0
458 hand.fee = 0
459 hand.buyinCurrency = "NA"
460 hand.maxseats = None
461 if self.sitename != "Merge" or hand.buyin == 0:
462 if info[key] == "Freeroll" or "Free" in tourneyname:
463 hand.buyin = 0
464 hand.fee = 0
465 hand.buyinCurrency = "FREE"
466 else:
467 if info[key].find("$") != -1:
468 hand.buyinCurrency = "USD"
469 elif info[key].find("£") != -1:
470 hand.buyinCurrency = "GBP"
471 elif info[key].find("€") != -1:
472 hand.buyinCurrency = "EUR"
473 elif re.match("^[0-9+]*$", info[key]):
474 hand.buyinCurrency = "play"
475 else:
476 # FIXME: handle other currencies, play money
477 log.error(
478 ("PokerTrackerToFpdb.readHandInfo: Failed to detect currency.")
479 + " Hand ID: %s: '%s'" % (hand.handid, info[key])
480 )
481 raise FpdbParseError
483 info["BIAMT"] = info["BIAMT"].strip("$€£")
484 info["BIRAKE"] = info["BIRAKE"].strip("$€£")
486 hand.buyin = int(100 * Decimal(info["BIAMT"]))
487 hand.fee = int(100 * Decimal(info["BIRAKE"]))
488 if key == "TABLE":
489 if hand.gametype["type"] == "tour":
490 hand.tablename = "0"
491 elif hand.gametype["type"] == "tour" and self.sitename == "Microgaming":
492 hand.tablename = info[key]
493 else:
494 hand.tablename = re.split(",", info[key])[0]
495 hand.tablename = hand.tablename.strip()
496 if "Blaze" in hand.tablename:
497 hand.gametype["fast"] = True
498 if self.sitename == "Microgaming":
499 m3 = self.re_Max.search(hand.tablename)
500 if m3 and m3.group("MAX"):
501 if m3.group("MAX") == "HU":
502 hand.maxseats = 2
503 elif len(m3.group("MAX").split(" ")) == 2:
504 hand.maxseats = int(m3.group("MAX").split(" ")[0])
505 if key == "BUTTON":
506 hand.buttonpos = info[key]
507 if key == "MAX" and info[key] is not None:
508 seats = int(info[key])
509 if seats <= 10:
510 hand.maxseats = int(info[key])
512 if key == "PLAY" and info["PLAY"] is not None and info["PLAY"] == "Play":
513 # hand.currency = 'play' # overrides previously set value
514 hand.gametype["currency"] = "play"
516 if self.re_FastFold.search(hand.handText):
517 hand.fastFold = True
519 if self.re_Cancelled.search(hand.handText):
520 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid)
522 def readButton(self, hand):
523 m = self.re_Button.search(hand.handText)
524 if m:
525 hand.buttonpos = int(m.group("BUTTON"))
526 else:
527 log.info("readButton: " + ("not found"))
529 def readPlayerStacks(self, hand):
530 if self.sitename != "Microgaming":
531 m = self.re_PlayerInfo1.finditer(hand.handText)
532 else:
533 m = self.re_PlayerInfo2.finditer(hand.handText)
534 for a in m:
535 # print a.group('SEAT'), a.group('PNAME'), a.group('CASH')
536 hand.addPlayer(int(a.group("SEAT")), a.group("PNAME"), a.group("CASH"))
537 if a.group("BUTTON") is not None:
538 hand.buttonpos = int(a.group("SEAT"))
539 if len(hand.players) == 1:
540 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid)
542 def markStreets(self, hand):
543 # PREFLOP = ** Dealing down cards **
544 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
545 if self.sitename == "Microgaming":
546 m = re.search(
547 r"\*\* Dealing ca(?P<PREFLOP>.+(?=\*\* Dealing the flop)|.+)"
548 r"(\*\* Dealing the flop(?P<FLOP>:\s.+(?=\*\* Dealing the turn)|.+))?"
549 r"(\*\* Dealing the turn(?P<TURN>:\s.+(?=\*\* Dealing the river)|.+))?"
550 r"(\*\* Dealing the river(?P<RIVER>:\s.+))?",
551 hand.handText,
552 re.DOTALL,
553 )
554 else:
555 m = re.search(
556 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
557 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=\*\*\* TURN \*\*\*)|.+))?"
558 r"(\*\*\* TURN \*\*\* (?P<TURN>\[\S\S\S?\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
559 r"(\*\*\* RIVER \*\*\* (?P<RIVER>\[\S\S\S?\].+))?",
560 hand.handText,
561 re.DOTALL,
562 )
563 hand.addStreets(m)
565 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
566 if street in (
567 "FLOP",
568 "TURN",
569 "RIVER",
570 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
571 # print "DEBUG readCommunityCards:", street, hand.streets.group(street)
572 if self.sitename == "Microgaming":
573 m = self.re_Board2.search(hand.streets[street])
574 cards = [c.replace("10", "T").strip() for c in m.group("CARDS").replace(" of ", "").split(", ")]
575 else:
576 m = self.re_Board1.search(hand.streets[street])
577 if self.sitename == "iPoker":
578 cards = [c[1:].replace("10", "T") + c[0].lower() for c in m.group("CARDS").split(" ")]
579 else:
580 cards = [c.replace("10", "T").strip() for c in m.group("CARDS").split(" ")]
581 hand.setCommunityCards(street, cards)
583 def readAntes(self, hand):
584 log.debug(("reading antes"))
585 m = self.re_Antes.finditer(hand.handText)
586 for player in m:
587 # ~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
588 self.adjustMergeTourneyStack(hand, player.group("PNAME"), player.group("ANTE"))
589 hand.addAnte(player.group("PNAME"), player.group("ANTE"))
591 def readBlinds(self, hand):
592 liveBlind, bb, sb = True, None, None
593 for a in self.re_PostSB.finditer(hand.handText):
594 sb = self.clearMoneyString(a.group("SB"))
595 if liveBlind:
596 self.adjustMergeTourneyStack(hand, a.group("PNAME"), a.group("SB"))
597 hand.addBlind(a.group("PNAME"), "small blind", sb)
598 if not hand.gametype["sb"]:
599 hand.gametype["sb"] = sb
600 liveBlind = False
601 elif hand.gametype["type"] == "tour":
602 self.adjustMergeTourneyStack(hand, a.group("PNAME"), a.group("SB"))
603 if not hand.gametype["bb"]:
604 hand.gametype["bb"] = sb
605 hand.addBlind(a.group("PNAME"), "big blind", sb)
606 else:
607 # Post dead blinds as ante
608 self.adjustMergeTourneyStack(hand, a.group("PNAME"), a.group("SB"))
609 hand.addBlind(a.group("PNAME"), "secondsb", sb)
610 for a in self.re_PostBB.finditer(hand.handText):
611 bb = self.clearMoneyString(a.group("BB"))
612 self.adjustMergeTourneyStack(hand, a.group("PNAME"), a.group("BB"))
613 if not hand.gametype["bb"]:
614 hand.gametype["bb"] = bb
615 hand.addBlind(a.group("PNAME"), "big blind", bb)
616 else:
617 both = Decimal(hand.gametype["bb"]) + old_div(Decimal(hand.gametype["bb"]), 2)
618 if both == Decimal(a.group("BB")):
619 hand.addBlind(a.group("PNAME"), "both", bb)
620 else:
621 hand.addBlind(a.group("PNAME"), "big blind", bb)
623 if self.sitename == "Microgaming":
624 for a in self.re_PostBoth2.finditer(hand.handText):
625 if self.clearMoneyString(a.group("SBBB")) == hand.gametype["sb"]:
626 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SBBB")))
627 else:
628 bet = self.clearMoneyString(a.group("SBBB"))
629 amount = str(Decimal(bet) + old_div(Decimal(bet), 2))
630 hand.addBlind(a.group("PNAME"), "both", amount)
631 for a in self.re_Action2.finditer(self.re_Hole.split(hand.handText)[0]):
632 if a.group("ATYPE") == " went all-in":
633 amount = Decimal(self.clearMoneyString(a.group("BET")))
634 player = a.group("PNAME")
635 if bb is None:
636 hand.addBlind(player, "big blind", self.clearMoneyString(a.group("BET")))
637 self.allInBlind(hand, "PREFLOP", a, "big blind")
638 elif sb is None:
639 hand.addBlind(player, "small blind", self.clearMoneyString(a.group("BET")))
640 self.allInBlind(hand, "PREFLOP", a, "small blind")
641 else:
642 for a in self.re_PostBoth1.finditer(hand.handText):
643 self.adjustMergeTourneyStack(hand, a.group("PNAME"), a.group("SBBB"))
644 if Decimal(str(hand.sb)) == Decimal(self.clearMoneyString(a.group("SBBB"))):
645 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SBBB")))
646 else:
647 hand.addBlind(a.group("PNAME"), "both", self.clearMoneyString(a.group("SBBB")))
649 # FIXME
650 # The following should only trigger when a small blind is missing in a tournament, or the sb/bb is ALL_IN
651 # see http://sourceforge.net/apps/mantisbt/fpdb/view.php?id=115
652 if hand.gametype["type"] == "tour" and self.sitename in ("Merge", "iPoker"):
653 if hand.gametype["sb"] is None and hand.gametype["bb"] is None:
654 hand.gametype["sb"] = "1"
655 hand.gametype["bb"] = "2"
656 elif hand.gametype["sb"] is None:
657 hand.gametype["sb"] = str(old_div(int(Decimal(hand.gametype["bb"])), 2))
658 elif hand.gametype["bb"] is None:
659 hand.gametype["bb"] = str(int(Decimal(hand.gametype["sb"])) * 2)
660 if old_div(int(Decimal(hand.gametype["bb"])), 2) != int(Decimal(hand.gametype["sb"])):
661 if old_div(int(Decimal(hand.gametype["bb"])), 2) < int(Decimal(hand.gametype["sb"])):
662 hand.gametype["bb"] = str(int(Decimal(hand.gametype["sb"])) * 2)
663 else:
664 hand.gametype["sb"] = str(old_div(int(Decimal(hand.gametype["bb"])), 2))
666 def readHoleCards(self, hand):
667 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
668 # we need to grab hero's cards
669 if self.sitename != "Microgaming":
670 re_HeroCards = self.re_HeroCards1
671 else:
672 re_HeroCards = self.re_HeroCards2
673 for street in ("PREFLOP", "DEAL"):
674 if street in list(hand.streets.keys()):
675 m = re_HeroCards.finditer(hand.streets[street])
676 for found in m:
677 # if m == None:
678 # hand.involved = False
679 # else:
680 hand.hero = found.group("PNAME")
681 if self.sitename == "iPoker":
682 newcards = [c[1:].replace("10", "T") + c[0].lower() for c in found.group("NEWCARDS").split(" ")]
683 elif self.sitename == "Microgaming":
684 newcards = [
685 c.replace("10", "T").strip()
686 for c in found.group("NEWCARDS").replace(" of ", "").split(", ")
687 ]
688 else:
689 newcards = [c.replace("10", "T").strip() for c in found.group("NEWCARDS").split(" ")]
690 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
692 for street, text in list(hand.streets.items()):
693 if not text or street in ("PREFLOP", "DEAL"):
694 continue # already done these
695 m = re_HeroCards.finditer(hand.streets[street])
696 for found in m:
697 player = found.group("PNAME")
698 if found.group("NEWCARDS") is None:
699 newcards = []
700 else:
701 if self.sitename == "iPoker":
702 newcards = [c[1:].replace("10", "T") + c[0].lower() for c in found.group("NEWCARDS").split(" ")]
703 elif self.sitename == "Microgaming":
704 newcards = [
705 c.replace("10", "T").strip()
706 for c in found.group("NEWCARDS").replace(" of ", "").split(", ")
707 ]
708 else:
709 newcards = [c.replace("10", "T").strip() for c in found.group("NEWCARDS").split(" ")]
710 if found.group("OLDCARDS") is None:
711 oldcards = []
712 else:
713 if self.sitename == "iPoker":
714 oldcards = [c[1:].replace("10", "T") + c[0].lower() for c in found.group("OLDCARDS").split(" ")]
715 else:
716 oldcards = [c.replace("10", "T").strip() for c in found.group("OLDCARDS").split(" ")]
718 hand.addHoleCards(
719 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
720 )
722 def readAction(self, hand, street):
723 if self.sitename != "Microgaming":
724 m = self.re_Action1.finditer(hand.streets[street])
725 else:
726 m = self.re_Action2.finditer(hand.streets[street])
727 curr_pot = Decimal("0")
728 for action in m:
729 action.groupdict()
730 # print "DEBUG: acts: %s" %acts
731 if action.group("ATYPE") in (" folds", " Fold", " folded"):
732 hand.addFold(street, action.group("PNAME"))
733 elif action.group("ATYPE") in (" checks", " Check", " checked"):
734 hand.addCheck(street, action.group("PNAME"))
735 elif action.group("ATYPE") in (" calls", " Call", " called"):
736 hand.addCall(street, action.group("PNAME"), action.group("BET"))
737 elif action.group("ATYPE") in (" raises", " Raise", " raised", " raised to", " Raise to"):
738 amount = Decimal(self.clearMoneyString(action.group("BET")))
739 if self.sitename == "Merge":
740 hand.addRaiseTo(street, action.group("PNAME"), action.group("BET"))
741 elif self.sitename == "Microgaming" or action.group("ATYPE") == " Raise to":
742 hand.addCallandRaise(street, action.group("PNAME"), action.group("BET"))
743 else:
744 if curr_pot > amount:
745 hand.addCall(street, action.group("PNAME"), action.group("BET"))
746 # elif not action.group('RAISETO') and action.group('ATYPE')==' Raise':
747 # hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
748 else:
749 hand.addRaiseTo(street, action.group("PNAME"), action.group("BET"))
750 curr_pot = amount
751 elif action.group("ATYPE") in (" bets", " Bet", " bet"):
752 if self.sitename == "Microgaming" and street in ("PREFLOP", "THIRD", "DEAL"):
753 hand.addCallandRaise(street, action.group("PNAME"), action.group("BET"))
754 else:
755 hand.addBet(street, action.group("PNAME"), action.group("BET"))
756 curr_pot = Decimal(self.clearMoneyString(action.group("BET")))
757 elif action.group("ATYPE") in (" Allin", " went all-in"):
758 amount = Decimal(self.clearMoneyString(action.group("BET")))
759 hand.addAllIn(street, action.group("PNAME"), action.group("BET"))
760 if curr_pot > amount and curr_pot > Decimal("0") and self.sitename == "Microgaming":
761 hand.setUncalledBets(False)
762 curr_pot = amount
763 else:
764 log.debug(
765 ("DEBUG:")
766 + " "
767 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE"))
768 )
770 def allInBlind(self, hand, street, action, actiontype):
771 if street in ("PREFLOP", "DEAL"):
772 player = action.group("PNAME")
773 if hand.stacks[player] == 0:
774 hand.setUncalledBets(True)
776 def adjustMergeTourneyStack(self, hand, player, amount):
777 if self.sitename == "Merge":
778 amount = Decimal(self.clearMoneyString(amount))
779 if hand.gametype["type"] == "tour":
780 for p in hand.players:
781 if p[1] == player:
782 stack = Decimal(p[2])
783 stack += amount
784 p[2] = str(stack)
785 hand.stacks[player] += amount
787 def readCollectPot(self, hand):
788 if self.sitename == "Microgaming":
789 for m in self.re_CollectPot2.finditer(hand.handText):
790 hand.addCollectPot(player=m.group("PNAME"), pot=re.sub(",", "", m.group("POT")))
791 else:
792 for m in self.re_CollectPot1.finditer(hand.handText):
793 hand.addCollectPot(player=m.group("PNAME"), pot=re.sub(",", "", m.group("POT")))
795 def readShowdownActions(self, hand):
796 pass
798 def readShownCards(self, hand):
799 found = []
800 if self.sitename == "Microgaming":
801 re_ShownCards = self.re_ShownCards2
802 else:
803 re_ShownCards = self.re_ShownCards1
804 for m in re_ShownCards.finditer(hand.handText):
805 if m.group("CARDS") is not None and m.group("PNAME") not in found:
806 if self.sitename == "iPoker":
807 cards = [c[1:].replace("10", "T") + c[0].lower() for c in m.group("CARDS").split(" ")]
808 elif self.sitename == "Microgaming":
809 cards = [c.replace("10", "T").strip() for c in m.group("CARDS").replace(" of ", "").split(", ")]
810 else:
811 cards = [c.replace("10", "T").strip() for c in m.group("CARDS").split(" ")]
813 (shown, mucked) = (False, False)
814 if m.group("SHOWED") in ("shows", "Shows"):
815 shown = True
816 elif m.group("SHOWED") in ("mucked", "mucks"):
817 mucked = True
818 found.append(m.group("PNAME"))
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)