From 5f425e0910077f6fe8e855251cebf1534a013a5f Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 17:59:52 +0800 Subject: [PATCH 01/14] OnGame: Fix determineGameType Assumes that its a cash game at the moment, and needs some love for non-limit holdem --- pyfpdb/OnGameToFpdb.py | 63 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index d9c069a7..5f8e1683 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -43,14 +43,44 @@ class OnGame(HandHistoryConverter): codepage = ("utf8", "cp1252") siteId = 5 # Needs to match id entry in Sites database + substitutions = { + 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes + 'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8) + } + + limits = { 'NO LIMIT':'nl', 'LIMIT':'fl'} + + games = { # base, category + "TEXAS_HOLDEM" : ('hold','holdem'), + # 'Omaha' : ('hold','omahahi'), + # 'Omaha Hi/Lo' : ('hold','omahahilo'), + # 'Razz' : ('stud','razz'), + # 'RAZZ' : ('stud','razz'), + # '7 Card Stud' : ('stud','studhi'), + # '7 Card Stud Hi/Lo' : ('stud','studhilo'), + # 'Badugi' : ('draw','badugi'), + # 'Triple Draw 2-7 Lowball' : ('draw','27_3draw'), + # '5 Card Draw' : ('draw','fivedraw') + } + #self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)') # Static regexes re_SplitHands = re.compile('\n\n\n+') - - #Texas Hold'em $.5-$1 NL (real money), hand #P4-76915775-797 - #Table Kuopio, 20 Sep 2008 11:59 PM - re_HandInfo = re.compile(r"Texas Hold'em \$?(?P[.0-9]+)-\$?(?P[.0-9]+) NL \(real money\), hand #(?P[-A-Z\d]+)\nTable\ (?P[\' \w]+), (?P\d\d \w+ \d\d\d\d \d\d:\d\d (AM|PM))") - # SB BB HID TABLE DAY MON YEAR HR12 MIN AMPM + + # ***** History for hand R5-75443872-57 ***** + # Start hand: Wed Aug 18 19:29:10 GMT+0100 2010 + # Table: someplace [75443872] (LIMIT TEXAS_HOLDEM 0.50/1, Real money) + re_HandInfo = re.compile(u""" + \*\*\*\*\*\sHistory\sfor\shand\s(?P[-A-Z\d]+).* + Start\shand:\s(?P.*) + Table:\s(?P
[\'\w]+)\s\[\d+\]\s\( + ( + (?PNo\sLimit|Limit|LIMIT|Pot\sLimit)\s + (?PTEXAS_HOLDEM|RAZZ)\s + (?P[.0-9]+)/ + (?P[.0-9]+) + )? + """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) # self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') @@ -80,8 +110,9 @@ class OnGame(HandHistoryConverter): pass def determineGameType(self, handText): - # Cheating with this regex, only support nlhe at the moment - gametype = ["ring", "hold", "nl"] + # Inspect the handText and return the gametype dict + # gametype dict is: {'limitType': xxx, 'base': xxx, 'category': xxx} + info = {} m = self.re_HandInfo.search(handText) if not m: @@ -90,10 +121,20 @@ class OnGame(HandHistoryConverter): log.error(_("determineGameType: Raising FpdbParseError")) raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp) - gametype = gametype + [m.group('SB')] - gametype = gametype + [m.group('BB')] - - return gametype + mg = m.groupdict() + + info['type'] = 'ring' + + if 'LIMIT' in mg: + info['limitType'] = self.limits[mg['LIMIT']] + if 'GAME' in mg: + (info['base'], info['category']) = self.games[mg['GAME']] + if 'SB' in mg: + info['sb'] = mg['SB'] + if 'BB' in mg: + info['bb'] = mg['BB'] + + return info def readHandInfo(self, hand): m = self.re_HandInfo.search(hand.string) From c77cf551048de0de949b28549f3c4dcedd1bf2bd Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 18:05:25 +0800 Subject: [PATCH 02/14] OnGame: Fix readSupprtedGames and currency --- pyfpdb/OnGameToFpdb.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index 5f8e1683..da7e299a 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -107,7 +107,10 @@ class OnGame(HandHistoryConverter): re_sitsOut = re.compile('(?P.*) sits out') def readSupportedGames(self): - pass + return [ + ["ring", "hold", "fl"], + ["ring", "hold", "nl"], + ] def determineGameType(self, handText): # Inspect the handText and return the gametype dict @@ -124,6 +127,7 @@ class OnGame(HandHistoryConverter): mg = m.groupdict() info['type'] = 'ring' + info['currency'] = 'USD' if 'LIMIT' in mg: info['limitType'] = self.limits[mg['LIMIT']] From 8ffb984d259c8373f183a0ac8a66f67a16139e46 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 18:52:00 +0800 Subject: [PATCH 03/14] HHC: Better doco for readHandInfo --- pyfpdb/HandHistoryConverter.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 34619c40..6308b755 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -326,15 +326,28 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. or None if we fail to get the info """ #TODO: which parts are optional/required? - # Read any of: - # HID HandID - # TABLE Table name - # SB small blind - # BB big blind - # GAMETYPE gametype - # YEAR MON DAY HR MIN SEC datetime - # BUTTON button seat number def readHandInfo(self, hand): abstract + """Read and set information about the hand being dealt, and set the correct + variables in the Hand object 'hand + + * hand.startTime - a datetime object + * hand.handid - The site identified for the hand - a string. + * hand.tablename + * hand.buttonpos + * hand.maxseats + * hand.mixed + + Tournament fields: + + * hand.tourNo - The site identified tournament id as appropriate - a string. + * hand.buyin + * hand.fee + * hand.buyinCurrency + * hand.koBounty + * hand.isKO + * hand.level + """ + #TODO: which parts are optional/required? # Needs to return a list of lists in the format # [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]] From 803f0fcaf8a97221a0c99cc27a4fa0eef08611c7 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 19:48:37 +0800 Subject: [PATCH 04/14] HHC: Documentation on readHandInfo() --- pyfpdb/HandHistoryConverter.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 6308b755..39ff3bf7 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -349,9 +349,22 @@ or None if we fail to get the info """ """ #TODO: which parts are optional/required? - # Needs to return a list of lists in the format - # [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]] def readPlayerStacks(self, hand): abstract + """This function is for identifying players at the table, and to pass the + information on to 'hand' via Hand.addPlayer(seat, name, chips) + + At the time of writing the reference function in the PS converter is: + log.debug("readPlayerStacks") + m = self.re_PlayerInfo.finditer(hand.handText) + for a in m: + hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) + + Which is pretty simple because the hand history format is consistent. Other hh formats aren't so nice. + + This is the appropriate place to identify players that are sitting out and ignore them + + *** NOTE: You may find this is a more appropriate place to set hand.maxseats *** + """ def compilePlayerRegexs(self): abstract """Compile dynamic regexes -- these explicitly match known player names and must be updated if a new player joins""" From d04e5e1a23369df7396ed21bef85b499449c179c Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 20:09:44 +0800 Subject: [PATCH 05/14] HHC: doco for compilePlayerRegexes --- pyfpdb/HandHistoryConverter.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 39ff3bf7..a521f248 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -367,7 +367,25 @@ or None if we fail to get the info """ """ def compilePlayerRegexs(self): abstract - """Compile dynamic regexes -- these explicitly match known player names and must be updated if a new player joins""" + """Compile dynamic regexes -- compile player dependent regexes. + + Depending on the ambiguity of lines you may need to match, and the complexity of + player names - we found that we needed to recompile some regexes for player actions so that they actually contained the player names. + + eg. + We need to match the ante line: + antes $1.00 + + But is actually named + + YesI antes $4000 - A perfectly legal playername + + Giving: + + YesI antes $4000 antes $1.00 + + Which without care in your regexes most people would match 'YesI' and not 'YesI antes $4000' + """ # Needs to return a MatchObject with group names identifying the streets into the Hand object # so groups are called by street names 'PREFLOP', 'FLOP', 'STREET2' etc From 7c5f4645f26435c4f8dea5ca27c929676507ea59 Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 20:10:52 +0800 Subject: [PATCH 06/14] OnGame: More updates, primarily to readHandInfo --- pyfpdb/OnGameToFpdb.py | 78 ++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index da7e299a..39ddb906 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -81,11 +81,21 @@ class OnGame(HandHistoryConverter): (?P[.0-9]+) )? """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) + + # Wed Aug 18 19:45:30 GMT+0100 2010 + re_DateTime = re.compile(""" + [a-zA-Z]{3}\s + (?P[a-zA-Z]{3})\s + (?P[0-9]{2})\s + (?P[0-9]+):(?P[0-9]+):(?P[0-9]+)\sGMT + (?P[-+]\d+)\s + (?P[0-9]{4}) + """, re.MULTILINE|re.VERBOSE) # self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') #Seat 1: .Lucchess ($4.17 in chips) - re_PlayerInfo = re.compile(u'Seat (?P[0-9]+): (?P.*) \((\$(?P[.0-9]+) in chips)\)') + re_PlayerInfo = re.compile(u'Seat (?P[0-9]+): (?P.*) \((?P[.0-9]+) \)') #ANTES/BLINDS #helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). @@ -106,6 +116,9 @@ class OnGame(HandHistoryConverter): re_CollectPot = re.compile('(?P.*), bets.+, collects \$(?P\d*\.?\d*), net.* ') re_sitsOut = re.compile('(?P.*) sits out') + def compilePlayerRegexs(self, hand): + pass + def readSupportedGames(self): return [ ["ring", "hold", "fl"], @@ -141,30 +154,37 @@ class OnGame(HandHistoryConverter): return info def readHandInfo(self, hand): - m = self.re_HandInfo.search(hand.string) - hand.handid = m.group('HID') - hand.tablename = m.group('TABLE') - #hand.buttonpos = self.rexx.button_re.search(hand.string).group('BUTTONPNAME') -# These work, but the info is already in the Hand class - should be used for tourneys though. -# m.group('SB') -# m.group('BB') -# m.group('GAMETYPE') + info = {} + m = self.re_HandInfo.search(hand.handText) -# Believe Everleaf time is GMT/UTC, no transation necessary -# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET] -# or : 2008/11/07 12:38:49 ET -# Not getting it in my HH files yet, so using -# 2008/11/10 3:58:52 ET -#TODO: Do conversion from GMT to ET -#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this) - - hand.startTime = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p") - #hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), - #int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) + if m: + info.update(m.groupdict()) + + log.debug("readHandInfo: %s" % info) + for key in info: + if key == 'DATETIME': + #'Wed Aug 18 19:45:30 GMT+0100 2010 + # %a %b %d %H:%M:%S %z %Y + #hand.startTime = time.strptime(m.group('DATETIME'), "%a %b %d %H:%M:%S GMT%z %Y") + # Stupid library doesn't seem to support %z (http://docs.python.org/library/time.html?highlight=strptime#time.strptime) + # So we need to re-interpret te string to be useful + m1 = self.re_DateTime.finditer(info[key]) + for a in m1: + datetimestr = "%s %s %s %s:%s:%s" % (a.group('M'),a.group('D'), a.group('Y'), a.group('H'),a.group('MIN'),a.group('S')) + hand.startTime = time.strptime(datetimestr, "%b %d %Y %H:%M:%S") + # TODO: Manually adjust time against OFFSET + if key == 'HID': + hand.handid = info[key] + if key == 'TABLE': + hand.tablename = info[key] + + # TODO: These + hand.buttonpos = 1 + hand.maxseats = 10 + hand.mixed = None def readPlayerStacks(self, hand): - m = self.re_PlayerInf.finditer(hand.string) - players = [] + m = self.re_PlayerInfo.finditer(hand.handText) for a in m: hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) @@ -176,7 +196,7 @@ class OnGame(HandHistoryConverter): m = re.search(r"PRE-FLOP(?P.+(?=FLOP)|.+(?=SHOWDOWN))" r"(FLOP (?P\[board cards .+ \].+(?=TURN)|.+(?=SHOWDOWN)))?" r"(TURN (?P\[board cards .+ \].+(?=RIVER)|.+(?=SHOWDOWN)))?" - r"(RIVER (?P\[board cards .+ \].+(?=SHOWDOWN)))?", hand.string,re.DOTALL) + r"(RIVER (?P\[board cards .+ \].+(?=SHOWDOWN)))?", hand.handText, re.DOTALL) hand.addStreets(m) @@ -189,17 +209,17 @@ class OnGame(HandHistoryConverter): def readBlinds(self, hand): try: - m = self.re_PostSB.search(hand.string) + m = self.re_PostSB.search(hand.handText) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) except: # no small blind hand.addBlind(None, None, None) - for a in self.re_PostBB.finditer(hand.string): + for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) - for a in self.re_PostBoth.finditer(hand.string): + for a in self.re_PostBoth.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) def readHeroCards(self, hand): - m = self.re_HeroCards.search(hand.string) + m = self.re_HeroCards.search(hand.handText) if(m == None): #Not involved in hand hand.involved = False @@ -230,13 +250,13 @@ class OnGame(HandHistoryConverter): # TODO: Everleaf does not record uncalled bets. def readShowdownActions(self, hand): - for shows in self.re_ShowdownAction.finditer(hand.string): + for shows in self.re_ShowdownAction.finditer(hand.handText): cards = shows.group('CARDS') cards = set(cards.split(',')) hand.addShownCards(cards, shows.group('PNAME')) def readCollectPot(self,hand): - for m in self.re_CollectPot.finditer(hand.string): + for m in self.re_CollectPot.finditer(hand.handText): hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) def readShownCards(self,hand): From 5d2e7cb32010891f621c31bb5a0c7e1f567f177d Mon Sep 17 00:00:00 2001 From: Worros Date: Fri, 20 Aug 2010 20:26:53 +0800 Subject: [PATCH 07/14] Betfair: Fix for Betfair 2.0 The Betfair poker site has changed hands/software and now has a completely different hand history coverter. Starting the process of making it work --- pyfpdb/BetfairToFpdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyfpdb/BetfairToFpdb.py b/pyfpdb/BetfairToFpdb.py index 07ee2612..62db0311 100755 --- a/pyfpdb/BetfairToFpdb.py +++ b/pyfpdb/BetfairToFpdb.py @@ -44,8 +44,9 @@ class Betfair(HandHistoryConverter): siteId = 7 # Needs to match id entry in Sites database # Static regexes + #re_SplitHands = re.compile(r'\n\n+') # Betfair 1.0 version re_GameInfo = re.compile("^(?PNL|PL|) (?P\$|)?(?P[.0-9]+)/\$?(?P[.0-9]+) (?P(Texas Hold\'em|Omaha Hi|Razz))", re.MULTILINE) - re_SplitHands = re.compile(r'\n\n+') + re_SplitHands = re.compile(r'End of hand .{2}-\d{7,9}-\d+ \*\*\*\*\*\n') re_HandInfo = re.compile("\*\*\*\*\* Betfair Poker Hand History for Game (?P[0-9]+) \*\*\*\*\*\n(?PNL|PL|) (?P\$|)?(?P[.0-9]+)/\$?(?P[.0-9]+) (?P(Texas Hold\'em|Omaha Hi|Razz)) - (?P[a-zA-Z]+, [a-zA-Z]+ \d+, \d\d:\d\d:\d\d GMT \d\d\d\d)\nTable (?P
[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P