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