Coverage for BetOnlineToFpdb.py: 0%
375 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-14 11:07 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-14 11:07 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2008-2011, 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
35log = logging.getLogger("parser")
36# BetOnline HH Format
39class BetOnline(HandHistoryConverter):
40 # Class Variables
42 sitename = "BetOnline"
43 skin = "BetOnline"
44 filetype = "text"
45 codepage = ("utf8", "cp1252")
46 siteId = 19 # Needs to match id entry in Sites database
47 sym = {"USD": "\$", "CAD": "\$", "T$": "", "EUR": "€", "GBP": "\xa3", "play": ""} # ADD Euro, Sterling, etc HERE
48 substitutions = {
49 "LS": "\$|€|", # legal currency symbols - Euro(cp1252, utf-8)
50 "PLYR": r"(?P<PNAME>.+?)",
51 "NUM": ".,\d",
52 }
54 # translations from captured groups to fpdb info strings
55 Lim_Blinds = {
56 "0.04": ("0.01", "0.02"),
57 "0.08": ("0.02", "0.04"),
58 "0.10": ("0.02", "0.05"),
59 "0.20": ("0.05", "0.10"),
60 "0.40": ("0.10", "0.20"),
61 "0.50": ("0.10", "0.25"),
62 "1.00": ("0.25", "0.50"),
63 "1": ("0.25", "0.50"),
64 "2.00": ("0.50", "1.00"),
65 "2": ("0.50", "1.00"),
66 "4.00": ("1.00", "2.00"),
67 "4": ("1.00", "2.00"),
68 "6.00": ("1.00", "3.00"),
69 "6": ("1.00", "3.00"),
70 "8.00": ("2.00", "4.00"),
71 "8": ("2.00", "4.00"),
72 "10.00": ("2.00", "5.00"),
73 "10": ("2.00", "5.00"),
74 "20.00": ("5.00", "10.00"),
75 "20": ("5.00", "10.00"),
76 "30.00": ("10.00", "15.00"),
77 "30": ("10.00", "15.00"),
78 "40.00": ("10.00", "20.00"),
79 "40": ("10.00", "20.00"),
80 "60.00": ("15.00", "30.00"),
81 "60": ("15.00", "30.00"),
82 "80.00": ("20.00", "40.00"),
83 "80": ("20.00", "40.00"),
84 "100.00": ("25.00", "50.00"),
85 "100": ("25.00", "50.00"),
86 "200.00": ("50.00", "100.00"),
87 "200": ("50.00", "100.00"),
88 "400.00": ("100.00", "200.00"),
89 "400": ("100.00", "200.00"),
90 "800.00": ("200.00", "400.00"),
91 "800": ("200.00", "400.00"),
92 "1000.00": ("250.00", "500.00"),
93 "1000": ("250.00", "500.00"),
94 }
96 limits = {"No Limit": "nl", "Pot Limit": "pl", "Limit": "fl", "LIMIT": "fl"}
97 games = { # base, category
98 "Hold'em": ("hold", "holdem"),
99 "Omaha": ("hold", "omahahi"),
100 "Omaha Hi/Lo": ("hold", "omahahilo"),
101 "Razz": ("stud", "razz"),
102 "7 Card Stud": ("stud", "studhi"),
103 "7 Card Stud Hi/Lo": ("stud", "studhilo"),
104 "Badugi": ("draw", "badugi"),
105 "Triple Draw 2-7 Lowball": ("draw", "27_3draw"),
106 "Single Draw 2-7 Lowball": ("draw", "27_1draw"),
107 "5 Card Draw": ("draw", "fivedraw"),
108 }
109 mixes = {
110 "HORSE": "horse",
111 "8-Game": "8game",
112 "HOSE": "hose",
113 "Mixed PLH/PLO": "plh_plo",
114 "Mixed Omaha H/L": "plo_lo",
115 "Mixed Hold'em": "mholdem",
116 "Triple Stud": "3stud",
117 } # Legal mixed games
118 currencies = {"€": "EUR", "$": "USD", "": "T$"}
120 skins = {
121 "BetOnline Poker": "BetOnline",
122 "PayNoRake": "PayNoRake",
123 "ActionPoker.com": "ActionPoker",
124 "Gear Poker": "GearPoker",
125 "SportsBetting.ag Poker": "SportsBetting.ag",
126 "Tiger Gaming": "Tiger Gaming",
127 } # Legal mixed games
129 # Static regexes
130 re_GameInfo = re.compile(
131 """
132 (?P<SKIN>BetOnline\sPoker|PayNoRake|ActionPoker\.com|Gear\sPoker|SportsBetting\.ag\sPoker|Tiger\sGaming)\sGame\s\#(?P<HID>[0-9]+):\s+
133 (\{.*\}\s+)?(Tournament\s\# # open paren of tournament info
134 (?P<TOURNO>\d+):\s?
135 # here's how I plan to use LS
136 (?P<BUYIN>(?P<BIAMT>(%(LS)s)[%(NUM)s]+)?\+?(?P<BIRAKE>(%(LS)s)[%(NUM)s]+)?\+?(?P<BOUNTY>(%(LS)s)[%(NUM)s]+)?\s?|Freeroll|)\s+)?
137 # close paren of tournament info
138 (?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|Single\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s
139 (?P<LIMIT>No\sLimit|Limit|LIMIT|Pot\sLimit)?,?\s?
140 (
141 \(? # open paren of the stakes
142 (?P<CURRENCY>%(LS)s|)?
143 (?P<SB>[%(NUM)s]+)/(%(LS)s)?
144 (?P<BB>[%(NUM)s]+)
145 \)? # close paren of the stakes
146 )?
147 \s?-\s
148 (?P<DATETIME>.*$)
149 """
150 % substitutions,
151 re.MULTILINE | re.VERBOSE,
152 )
154 re_PlayerInfo = re.compile(
155 """
156 ^Seat\s(?P<SEAT>[0-9]+):\s
157 (?P<PNAME>.*)\s
158 \((%(LS)s)?(?P<CASH>[%(NUM)s]+)\sin\s[cC]hips\)"""
159 % substitutions,
160 re.MULTILINE | re.VERBOSE,
161 )
163 re_HandInfo1 = re.compile(
164 """
165 ^Table\s\'(?P<TABLE>[\/,\.\-\ &%\$\#a-zA-Z\d\'\(\)]+)\'\s
166 ((?P<MAX>\d+)-max\s)?
167 (?P<MONEY>\((Play\sMoney|Real\sMoney)\)\s)?
168 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
169 re.MULTILINE | re.VERBOSE,
170 )
172 re_HandInfo2 = re.compile(
173 """
174 ^Table\s(?P<TABLE>[\/,\.\-\ &%\$\#a-zA-Z\d\']+)\s
175 (?P<MONEY>\((Play\sMoney|Real\sMoney)\)\s)?
176 (Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
177 re.MULTILINE | re.VERBOSE,
178 )
180 re_Identify = re.compile(
181 "(BetOnline\sPoker|PayNoRake|ActionPoker\.com|Gear\sPoker|SportsBetting\.ag\sPoker|Tiger\sGaming)\sGame\s\#\d+"
182 )
183 re_SplitHands = re.compile("\n\n\n+")
184 re_TailSplitHands = re.compile("(\n\n\n+)")
185 re_Button = re.compile("Seat #(?P<BUTTON>\d+) is the button", re.MULTILINE)
186 re_Board1 = re.compile(r"Board \[(?P<FLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<TURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\]")
187 re_Board2 = re.compile(r"\[(?P<CARDS>.+)\]")
188 re_Hole = re.compile(r"\*\*\*\sHOLE\sCARDS\s\*\*\*")
190 re_DateTime1 = re.compile(
191 """(?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]+))?\s(?P<TZ>.*$)""",
192 re.MULTILINE,
193 )
194 re_DateTime2 = re.compile(
195 """(?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]+)""",
196 re.MULTILINE,
197 )
199 re_PostSB = re.compile(r"^%(PLYR)s: [Pp]osts small blind (%(LS)s)?(?P<SB>[%(NUM)s]+)" % substitutions, re.MULTILINE)
200 re_PostBB = re.compile(
201 r"^%(PLYR)s: ([Pp]osts big blind|[Pp]osts? [Nn]ow)( (%(LS)s)?(?P<BB>[%(NUM)s]+))?" % substitutions, re.MULTILINE
202 )
203 re_Antes = re.compile(r"^%(PLYR)s: ante processed (%(LS)s)?(?P<ANTE>[%(NUM)s]+)" % substitutions, re.MULTILINE)
204 re_BringIn = re.compile(
205 r"^%(PLYR)s: brings[- ]in( low|) for (%(LS)s)?(?P<BRINGIN>[%(NUM)s]+)" % substitutions, re.MULTILINE
206 )
207 re_PostBoth = re.compile(r"^%(PLYR)s: [Pp]ost dead (%(LS)s)?(?P<SBBB>[%(NUM)s]+)" % substitutions, re.MULTILINE)
208 re_HeroCards = re.compile(
209 r"^Dealt [Tt]o %(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE
210 )
211 re_Action = re.compile(
212 r"""
213 ^%(PLYR)s:?(?P<ATYPE>\shas\sleft\sthe\stable|\s[Bb]ets|\s[Cc]hecks|\s[Rr]aises|\s[Cc]alls|\s[Ff]olds|\s[Dd]iscards|\s[Ss]tands\spat|\sReraises)
214 (\s(%(LS)s)?(?P<BET>[%(NUM)s]+))?(\sto\s(%(LS)s)?(?P<BETTO>[%(NUM)s]+))? # the number discarded goes in <BET>
215 \s*(and\sis\s[Aa]ll.[Ii]n)?
216 (\son|\scards?)?
217 (\s\[(?P<CARDS>.+?)\])?\.?\s*$"""
218 % substitutions,
219 re.MULTILINE | re.VERBOSE,
220 )
221 re_ShowdownAction = re.compile(r"^%s: shows (?P<CARDS>.*)" % substitutions["PLYR"], re.MULTILINE)
222 re_sitsOut = re.compile("^%s sits out" % substitutions["PLYR"], re.MULTILINE)
223 re_JoinsTable = re.compile("^.+ joins the table at seat #\d+", re.MULTILINE)
224 re_TotalPot = re.compile(r"^Total pot (?P<POT>[%(NUM)s]+)( \| Rake (?P<RAKE>[%(NUM)s]+))?", re.MULTILINE)
225 re_ShownCards = re.compile(
226 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(.+?\) )?(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and won \([%(NUM)s]+\))?"
227 % substitutions,
228 re.MULTILINE,
229 )
230 re_CollectPot = re.compile(
231 r"Seat (?P<SEAT>[0-9]+): %(PLYR)s (\(.+?\) )?(collected|showed \[.*\] and won) \((%(LS)s)?(?P<POT>[%(NUM)s]+)\)"
232 % substitutions,
233 re.MULTILINE,
234 )
236 def compilePlayerRegexs(self, hand):
237 pass
239 def readSupportedGames(self):
240 return [
241 ["ring", "hold", "nl"],
242 ["ring", "hold", "pl"],
243 ["ring", "hold", "fl"],
244 # ["ring", "stud", "fl"],
245 # ["ring", "draw", "fl"],
246 # ["ring", "draw", "pl"],
247 # ["ring", "draw", "nl"],
248 ["tour", "hold", "nl"],
249 ["tour", "hold", "pl"],
250 ["tour", "hold", "fl"],
251 # ["tour", "stud", "fl"],
252 # ["tour", "draw", "fl"],
253 # ["tour", "draw", "pl"],
254 # ["tour", "draw", "nl"],
255 ]
257 def determineGameType(self, handText):
258 info = {}
259 m = self.re_GameInfo.search(handText)
260 if not m:
261 # BetOnline starts writing the hh the moment you sit down.
262 # Test if the hh contains the join line, and throw a partial if so.
263 m2 = self.re_JoinsTable.search(handText)
264 if not m2:
265 tmp = handText[0:200]
266 log.error(("BetOnlineToFpdb.determineGameType: '%s'") % tmp)
267 raise FpdbParseError
268 else:
269 raise FpdbHandPartial(
270 "BetOnlineToFpdb.determineGameType: " + ("Partial hand history: 'Player joining table'")
271 )
273 mg = m.groupdict()
274 if mg["LIMIT"]:
275 info["limitType"] = self.limits[mg["LIMIT"]]
276 if info["limitType"] == "pl":
277 m = self.re_HeroCards.search(handText)
278 if m and len(m.group("NEWCARDS").split(" ")) == 4:
279 (info["base"], info["category"]) = self.games["Omaha"]
280 else:
281 info["limitType"] = self.limits["No Limit"]
282 if "SKIN" in mg:
283 self.skin = self.skins[mg["SKIN"]]
284 if "GAME" in mg and not info.get("base"):
285 (info["base"], info["category"]) = self.games[mg["GAME"]]
286 if "SB" in mg:
287 info["sb"] = self.clearMoneyString(mg["SB"])
288 if "BB" in mg:
289 info["bb"] = self.clearMoneyString(mg["BB"])
290 if "CURRENCY" in mg and mg["CURRENCY"] is not None:
291 info["currency"] = self.currencies[mg["CURRENCY"]]
292 else:
293 info["currency"] = "USD"
294 if "MIXED" in mg:
295 if mg["MIXED"] is not None:
296 info["mix"] = self.mixes[mg["MIXED"]]
298 if "TOURNO" in mg and mg["TOURNO"] is None:
299 info["type"] = "ring"
300 else:
301 info["type"] = "tour"
303 if info["limitType"] == "fl" and info["bb"] is not None:
304 if info["type"] == "ring":
305 try:
306 info["sb"] = self.Lim_Blinds[info["BB"]][0]
307 info["bb"] = self.Lim_Blinds[info["BB"]][1]
308 except KeyError:
309 tmp = handText[0:200]
310 log.error(
311 ("BetOnlineToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'")
312 % (mg["BB"], tmp)
313 )
314 raise FpdbParseError
315 else:
316 info["sb"] = str((old_div(Decimal(info["SB"]), 2)).quantize(Decimal("0.01")))
317 info["bb"] = str(Decimal(info["SB"]).quantize(Decimal("0.01")))
319 return info
321 def readHandInfo(self, hand):
322 info = {}
323 if self.skin in ("ActionPoker", "GearPoker"):
324 m = self.re_HandInfo2.search(hand.handText, re.DOTALL)
325 else:
326 m = self.re_HandInfo1.search(hand.handText, re.DOTALL)
327 m2 = self.re_GameInfo.search(hand.handText)
328 if m is None or m2 is None:
329 tmp = hand.handText[0:200]
330 log.error(("BetOnlineToFpdb.readHandInfo: '%s'") % tmp)
331 raise FpdbParseError
333 info.update(m.groupdict())
334 info.update(m2.groupdict())
336 # print 'DEBUG:', info
337 for key in info:
338 if key == "DATETIME":
339 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other)
340 # 2008/08/17 - 01:14:43 (ET)
341 # 2008/09/07 06:23:14 ET
343 datetimestr, time_zone = "2000/01/01 00:00:00", "ET" # default used if time not found
344 if self.skin not in ("ActionPoker", "GearPoker"):
345 m1 = self.re_DateTime1.finditer(info[key])
346 for a in m1:
347 seconds = "00"
348 if a.group("S"):
349 seconds = a.group("S")
350 datetimestr = "%s/%s/%s %s:%s:%s" % (
351 a.group("Y"),
352 a.group("M"),
353 a.group("D"),
354 a.group("H"),
355 a.group("MIN"),
356 seconds,
357 )
358 tz = a.group("TZ") # just assume ET??
359 if tz == "GMT Standard Time":
360 time_zone = "GMT"
361 elif tz in ("Pacific Daylight Time", "Pacific Standard Time"):
362 time_zone = "PT"
363 else:
364 time_zone = "ET"
365 else:
366 m2 = self.re_DateTime2.finditer(info[key])
367 for a in m2:
368 datetimestr = "%s/%s/%s %s:%s:%s" % (
369 a.group("Y"),
370 a.group("M"),
371 a.group("D"),
372 a.group("H"),
373 a.group("MIN"),
374 a.group("S"),
375 )
376 time_zone = "ET"
377 # print " tz = ", tz, " datetime =", datetimestr
378 hand.startTime = datetime.datetime.strptime(
379 datetimestr, "%Y/%m/%d %H:%M:%S"
380 ) # also timezone at end, e.g. " ET"
381 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, time_zone, "UTC")
382 if key == "HID":
383 hand.handid = info[key]
384 if key == "MONEY":
385 if info[key] == "(Play Money) ":
386 hand.gametype["currency"] = "play"
387 if key == "TOURNO":
388 hand.tourNo = info[key]
389 if key == "BUYIN":
390 if hand.tourNo is not None:
391 # print "DEBUG: info['BUYIN']: %s" % info['BUYIN']
392 # print "DEBUG: info['BIAMT']: %s" % info['BIAMT']
393 # print "DEBUG: info['BIRAKE']: %s" % info['BIRAKE']
394 # print "DEBUG: info['BOUNTY']: %s" % info['BOUNTY']
395 if not info[key] or info[key] == "Freeroll":
396 hand.buyin = 0
397 hand.fee = 0
398 hand.buyinCurrency = "FREE"
399 else:
400 if info[key].find("$") != -1:
401 hand.buyinCurrency = "USD"
402 elif info[key].find("€") != -1:
403 hand.buyinCurrency = "EUR"
404 elif re.match("^[0-9+]*$", info[key]):
405 hand.buyinCurrency = "play"
406 else:
407 # FIXME: handle other currencies, play money
408 raise FpdbParseError(
409 ("BetOnlineToFpdb.readHandInfo: Failed to detect currency.")
410 + " "
411 + ("Hand ID")
412 + ": %s: '%s'" % (hand.handid, info[key])
413 )
415 info["BIAMT"] = info["BIAMT"].strip("$€")
416 if info["BOUNTY"] is not None:
417 # There is a bounty, Which means we need to switch BOUNTY and BIRAKE values
418 tmp = info["BOUNTY"]
419 info["BOUNTY"] = info["BIRAKE"]
420 info["BIRAKE"] = tmp
421 info["BOUNTY"] = info["BOUNTY"].strip("$€") # Strip here where it isn't 'None'
422 hand.koBounty = int(100 * Decimal(info["BOUNTY"]))
423 hand.isKO = True
424 else:
425 hand.isKO = False
427 info["BIRAKE"] = info["BIRAKE"].strip("$€")
429 hand.buyin = int(100 * Decimal(info["BIAMT"]))
430 hand.fee = int(100 * Decimal(info["BIRAKE"]))
431 if key == "LEVEL":
432 hand.level = info[key]
434 if key == "TABLE":
435 if hand.tourNo is not None:
436 hand.tablename = re.split("-", info[key])[1]
437 else:
438 hand.tablename = info[key]
439 if key == "BUTTON":
440 hand.buttonpos = info[key]
441 if key == "MAX" and info[key] is not None:
442 hand.maxseats = int(info[key])
443 if not self.re_Board1.search(hand.handText) and self.skin not in ("ActionPoker", "GearPoker"):
444 raise FpdbHandPartial("readHandInfo: " + ("Partial hand history") + ": '%s'" % hand.handid)
446 def readButton(self, hand):
447 m = self.re_Button.search(hand.handText)
448 if m:
449 hand.buttonpos = int(m.group("BUTTON"))
450 else:
451 log.info("readButton: " + ("not found"))
453 def readPlayerStacks(self, hand):
454 m = self.re_PlayerInfo.finditer(hand.handText)
455 for a in m:
456 pname = self.unknownPlayer(hand, a.group("PNAME"))
457 hand.addPlayer(int(a.group("SEAT")), pname, self.clearMoneyString(a.group("CASH")))
459 def markStreets(self, hand):
460 # There is no marker between deal and draw in Stars single draw games
461 # this upsets the accounting, incorrectly sets handsPlayers.cardxx and
462 # in consequence the mucked-display is incorrect.
463 # Attempt to fix by inserting a DRAW marker into the hand text attribute
465 if hand.gametype["category"] in ("27_1draw", "fivedraw"):
466 # isolate the first discard/stand pat line (thanks Carl for the regex)
467 discard_split = re.split(r"(?:(.+(?: stands pat|: discards).+))", hand.handText, re.DOTALL)
468 if len(hand.handText) == len(discard_split[0]):
469 # handText was not split, no DRAW street occurred
470 pass
471 else:
472 # DRAW street found, reassemble, with DRAW marker added
473 discard_split[0] += "*** DRAW ***\r\n"
474 hand.handText = ""
475 for i in discard_split:
476 hand.handText += i
478 # PREFLOP = ** Dealing down cards **
479 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
480 if hand.gametype["base"] in ("hold"):
481 m = re.search(
482 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
483 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=\*\*\* TURN \*\*\*)|.+))?"
484 r"(\*\*\* TURN \*\*\* \[\S\S\S? \S\S\S? \S\S\S?](?P<TURN>\[\S\S\S?\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
485 r"(\*\*\* RIVER \*\*\* \[\S\S\S? \S\S\S? \S\S\S? \S\S\S?](?P<RIVER>\[\S\S\S?\].+))?",
486 hand.handText,
487 re.DOTALL,
488 )
489 m2 = self.re_Board1.search(hand.handText)
490 if m and m2:
491 if m2.group("FLOP") and not m.group("FLOP"):
492 m = re.search(
493 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=Board )|.+)"
494 r"(Board \[(?P<FLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<TURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\])?",
495 hand.handText,
496 re.DOTALL,
497 )
498 elif m2.group("TURN") and not m.group("TURN"):
499 m = re.search(
500 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
501 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=Board )|.+))?"
502 r"(Board \[(?P<BFLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<TURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\])?",
503 hand.handText,
504 re.DOTALL,
505 )
506 elif m2.group("RIVER") and not m.group("RIVER"):
507 m = re.search(
508 r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
509 r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S\S? \S\S\S? \S\S\S?\].+(?=\*\*\* TURN \*\*\*)|.+))?"
510 r"(\*\*\* TURN \*\*\* \[\S\S\S? \S\S\S? \S\S\S?](?P<TURN>\[\S\S\S?\].+(?=Board )|.+))?"
511 r"(Board \[(?P<BFLOP>\S\S\S? \S\S\S? \S\S\S?)?\s?(?P<BTURN>\S\S\S?)?\s?(?P<RIVER>\S\S\S?)?\])?",
512 hand.handText,
513 re.DOTALL,
514 )
515 elif hand.gametype["base"] in ("stud"):
516 m = re.search(
517 r"(?P<ANTES>.+(?=\*\*\* 3rd STREET \*\*\*)|.+)"
518 r"(\*\*\* 3rd STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4th STREET \*\*\*)|.+))?"
519 r"(\*\*\* 4th STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5th STREET \*\*\*)|.+))?"
520 r"(\*\*\* 5th STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6th STREET \*\*\*)|.+))?"
521 r"(\*\*\* 6th STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* RIVER \*\*\*)|.+))?"
522 r"(\*\*\* RIVER \*\*\*(?P<SEVENTH>.+))?",
523 hand.handText,
524 re.DOTALL,
525 )
526 elif hand.gametype["base"] in ("draw"):
527 if hand.gametype["category"] in ("27_1draw", "fivedraw"):
528 m = re.search(
529 r"(?P<PREDEAL>.+(?=\*\*\* DEALING HANDS \*\*\*)|.+)"
530 r"(\*\*\* DEALING HANDS \*\*\*(?P<DEAL>.+(?=\*\*\* DRAW \*\*\*)|.+))?"
531 r"(\*\*\* DRAW \*\*\*(?P<DRAWONE>.+))?",
532 hand.handText,
533 re.DOTALL,
534 )
535 else:
536 m = re.search(
537 r"(?P<PREDEAL>.+(?=\*\*\* DEALING HANDS \*\*\*)|.+)"
538 r"(\*\*\* DEALING HANDS \*\*\*(?P<DEAL>.+(?=\*\*\* FIRST DRAW \*\*\*)|.+))?"
539 r"(\*\*\* FIRST DRAW \*\*\*(?P<DRAWONE>.+(?=\*\*\* SECOND DRAW \*\*\*)|.+))?"
540 r"(\*\*\* SECOND DRAW \*\*\*(?P<DRAWTWO>.+(?=\*\*\* THIRD DRAW \*\*\*)|.+))?"
541 r"(\*\*\* THIRD DRAW \*\*\*(?P<DRAWTHREE>.+))?",
542 hand.handText,
543 re.DOTALL,
544 )
545 hand.addStreets(m)
546 # if m3 and m2:
547 # if m2.group('RIVER') and not m3.group('RIVER'):
548 # print hand.streets
550 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
551 if street in (
552 "FLOP",
553 "TURN",
554 "RIVER",
555 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
556 if self.skin not in ("ActionPoker", "GearPoker"):
557 m = self.re_Board1.search(hand.handText)
558 if m and m.group(street):
559 cards = m.group(street).split(" ")
560 cards = [c.replace("10", "T") for c in cards]
561 hand.setCommunityCards(street, cards)
562 else:
563 m = self.re_Board2.search(hand.streets[street])
564 cards = m.group("CARDS").split(" ")
565 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards]
566 hand.setCommunityCards(street, cards)
568 def readAntes(self, hand):
569 m = self.re_Antes.finditer(hand.handText)
570 for player in m:
571 if player.group("ANTE") != "0.00":
572 pname = self.unknownPlayer(hand, player.group("PNAME"))
573 hand.addAnte(pname, self.clearMoneyString(player.group("ANTE")))
575 def readBringIn(self, hand):
576 m = self.re_BringIn.search(hand.handText, re.DOTALL)
577 if m:
578 # ~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
579 hand.addBringIn(m.group("PNAME"), self.clearMoneyString(m.group("BRINGIN")))
581 def readBlinds(self, hand):
582 liveBlind = True
583 for a in self.re_PostSB.finditer(hand.handText):
584 pname = self.unknownPlayer(hand, a.group("PNAME"))
585 sb = self.clearMoneyString(a.group("SB"))
586 if liveBlind:
587 hand.addBlind(pname, "small blind", sb)
588 liveBlind = False
589 else:
590 # Post dead blinds as ante
591 hand.addBlind(pname, "secondsb", sb)
592 if not hand.gametype["sb"] and self.skin in ("ActionPoker", "GearPoker"):
593 hand.gametype["sb"] = sb
594 for a in self.re_PostBB.finditer(hand.handText):
595 pname = self.unknownPlayer(hand, a.group("PNAME"))
596 if a.group("BB") is not None:
597 bb = self.clearMoneyString(a.group("BB"))
598 elif hand.gametype["bb"]:
599 bb = hand.gametype["bb"]
600 else:
601 raise FpdbHandPartial("BetOnlineToFpdb.readBlinds: " + ("Partial hand history: 'No blind info'"))
602 hand.addBlind(pname, "big blind", bb)
603 if not hand.gametype["bb"] and self.skin in ("ActionPoker", "GearPoker"):
604 hand.gametype["bb"] = bb
605 for a in self.re_PostBoth.finditer(hand.handText):
606 if a.group("SBBB") != "0.00":
607 pname = self.unknownPlayer(hand, a.group("PNAME"))
608 sbbb = self.clearMoneyString(a.group("SBBB"))
609 amount = str(Decimal(sbbb) + old_div(Decimal(sbbb), 2))
610 hand.addBlind(pname, "both", amount)
611 else:
612 pname = self.unknownPlayer(hand, a.group("PNAME"))
613 hand.addBlind(pname, "secondsb", hand.gametype["sb"])
614 self.fixBlinds(hand)
616 def fixBlinds(self, hand):
617 # FIXME
618 # The following should only trigger when a small blind is missing in ActionPoker hands, or the sb/bb is ALL_IN
619 if self.skin in ("ActionPoker", "GearPoker"):
620 if hand.gametype["sb"] is None and hand.gametype["bb"] is not None:
621 BB = str(Decimal(hand.gametype["bb"]) * 2)
622 if self.Lim_Blinds.get(BB) is not None:
623 hand.gametype["sb"] = self.Lim_Blinds.get(BB)[0]
624 elif hand.gametype["bb"] is None and hand.gametype["sb"] is not None:
625 for k, v in list(self.Lim_Blinds.items()):
626 if hand.gametype["sb"] == v[0]:
627 hand.gametype["bb"] = v[1]
628 if hand.gametype["sb"] is None or hand.gametype["bb"] is None:
629 log.error(("BetOnline.fixBlinds: Failed to fix blinds") + " Hand ID: %s" % (hand.handid,))
630 raise FpdbParseError
631 hand.sb = hand.gametype["sb"]
632 hand.bb = hand.gametype["bb"]
634 def unknownPlayer(self, hand, pname):
635 if pname == "Unknown player" or not pname:
636 if not pname:
637 pname = "Dead"
638 if pname not in (p[1] for p in hand.players):
639 hand.addPlayer(-1, pname, "0")
640 return pname
642 def readHoleCards(self, hand):
643 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
644 # we need to grab hero's cards
645 for street in ("PREFLOP", "DEAL"):
646 if street in list(hand.streets.keys()):
647 m = self.re_HeroCards.finditer(hand.streets[street])
648 for found in m:
649 # if m == None:
650 # hand.involved = False
651 # else:
652 hand.hero = found.group("PNAME")
653 newcards = found.group("NEWCARDS").split(" ")
654 newcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in newcards]
655 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
657 for street, text in list(hand.streets.items()):
658 if not text or street in ("PREFLOP", "DEAL"):
659 continue # already done these
660 m = self.re_HeroCards.finditer(hand.streets[street])
661 for found in m:
662 player = found.group("PNAME")
663 if found.group("NEWCARDS") is None:
664 newcards = []
665 else:
666 newcards = found.group("NEWCARDS").split(" ")
667 newcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in newcards]
668 if found.group("OLDCARDS") is None:
669 oldcards = []
670 else:
671 oldcards = found.group("OLDCARDS").split(" ")
672 oldcards = [c[:-1].replace("10", "T") + c[-1].lower() for c in oldcards]
673 if street == "THIRD" and len(newcards) == 3: # hero in stud game
674 hand.hero = player
675 hand.dealt.add(player) # need this for stud??
676 hand.addHoleCards(
677 street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False
678 )
679 else:
680 hand.addHoleCards(
681 street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False
682 )
684 def readAction(self, hand, street):
685 if street == "PREFLOP":
686 m0 = self.re_Action.finditer(self.re_Hole.split(hand.handText)[0])
687 for action in m0:
688 pname = self.unknownPlayer(hand, action.group("PNAME"))
689 if action.group("ATYPE") == " has left the table":
690 if pname in (p[1] for p in hand.players):
691 hand.addFold(street, pname)
692 m = self.re_Action.finditer(hand.streets[street])
693 for action in m:
694 # acts = action.groupdict()
695 # print "DEBUG: street: %s acts: %s" % (street, acts)
696 pname = self.unknownPlayer(hand, action.group("PNAME"))
697 if action.group("ATYPE") in (" folds", " Folds", " has left the table"):
698 if pname in (p[1] for p in hand.players):
699 hand.addFold(street, pname)
700 elif action.group("ATYPE") in (" checks", " Checks"):
701 hand.addCheck(street, pname)
702 elif action.group("ATYPE") in (" calls", " Calls"):
703 hand.addCall(street, pname, self.clearMoneyString(action.group("BET")))
704 elif action.group("ATYPE") in (" raises", " Raises", " Reraises"):
705 hand.addCallandRaise(street, pname, self.clearMoneyString(action.group("BET")))
706 elif action.group("ATYPE") in (" bets", " Bets"):
707 hand.addBet(street, pname, self.clearMoneyString(action.group("BET")))
708 elif action.group("ATYPE") == " discards":
709 hand.addDiscard(street, pname, action.group("BET"), action.group("CARDS"))
710 elif action.group("ATYPE") == " stands pat":
711 hand.addStandsPat(street, pname, action.group("CARDS"))
712 else:
713 log.debug(
714 ("DEBUG:")
715 + " "
716 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE"))
717 )
719 def readShowdownActions(self, hand):
720 # TODO: pick up mucks also??
721 for shows in self.re_ShowdownAction.finditer(hand.handText):
722 cards = shows.group("CARDS").split(" ")
723 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards]
724 hand.addShownCards(cards, shows.group("PNAME"))
726 def readCollectPot(self, hand):
727 hand.adjustCollected = True
728 for m in self.re_CollectPot.finditer(hand.handText):
729 hand.addCollectPot(player=m.group("PNAME"), pot=m.group("POT"))
730 for m in self.re_TotalPot.finditer(hand.handText):
731 if hand.rakes.get("pot"):
732 hand.rakes["pot"] += Decimal(self.clearMoneyString(m.group("POT")))
733 else:
734 hand.rakes["pot"] = Decimal(self.clearMoneyString(m.group("POT")))
736 def readShownCards(self, hand):
737 for m in self.re_ShownCards.finditer(hand.handText):
738 if m.group("CARDS") is not None:
739 pname = self.unknownPlayer(hand, m.group("PNAME"))
740 cards = m.group("CARDS")
741 cards = cards.split(" ") # needs to be a list, not a set--stud needs the order
742 cards = [c[:-1].replace("10", "T") + c[-1].lower() for c in cards if len(c) > 0]
743 (shown, mucked) = (False, False)
744 if m.group("SHOWED") == "showed":
745 shown = True
746 elif m.group("SHOWED") == "mucked":
747 mucked = True
748 if hand.gametype["category"] == "holdem" and len(cards) > 2:
749 continue
750 # print "DEBUG: hand.addShownCards(%s, %s, %s, %s)" %(cards, m.group('PNAME'), shown, mucked)
751 hand.addShownCards(cards=cards, player=pname, shown=shown, mucked=mucked, string=None)
753 @staticmethod
754 def getTableTitleRe(type, table_name=None, tournament=None, table_number=None):
755 """Returns string to search in windows titles"""
756 if type == "tour":
757 return r"\(" + re.escape(str(tournament)) + r"\-" + re.escape(str(table_number)) + r"\)"
758 else:
759 return re.escape(table_name)