#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2008-2010, Carl Gherardi # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## import sys import datetime from HandHistoryConverter import * import locale lang=locale.getdefaultlocale()[0][0:2] if lang=="en": def _(string): return string else: import gettext try: trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) trans.install() except IOError: def _(string): return string # Win2day HH Format class Win2day(HandHistoryConverter): sitename = "Win2day" filetype = "text" codepage = "cp1252" siteID = 4 # Static regexes # #'^$' re_GameInfo = re.compile('^', re.MULTILINE) re_SplitHands = re.compile('') re_HandInfo = re.compile("^Table \'(?P[- a-zA-Z]+)\'(?P.+?$)?", re.MULTILINE) re_Button = re.compile('\n\n\n re_PlayerInfo = re.compile('^', re.MULTILINE) re_Card = re.compile('^', re.MULTILINE) re_BoardLast = re.compile('^', re.MULTILINE) def compilePlayerRegexs(self, hand): players = set([player[1] for player in hand.players]) if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' # we need to recompile the player regexs. self.compiledPlayers = players player_re = "(?P" + "|".join(map(re.escape, players)) + ")" logging.debug("player_re: " + player_re) # self.re_PostSB = re.compile(r'^' % player_re, re.MULTILINE) self.re_PostBB = re.compile(r'^' % player_re, re.MULTILINE) self.re_Antes = re.compile(r"^%s: posts the ante \$?(?P[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r'^' % player_re, re.MULTILINE) #r'\n\n' self.re_HeroCards = re.compile(r'\n(?P\n)' % player_re, re.MULTILINE) #'^' self.re_Action = re.compile(r'^' % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r'\n(?P\n)' % player_re, re.MULTILINE) # # self.re_CollectPot = re.compile(r'' % player_re, re.MULTILINE) self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE) self.re_ShownCards = re.compile("^Seat (?P[0-9]+): %s \(.*\) showed \[(?P.*)\].*" % player_re, re.MULTILINE) def readSupportedGames(self): return [["ring", "hold", "nl"], ["ring", "hold", "pl"], ["ring", "hold", "fl"], ["ring", "stud", "fl"], ["ring", "draw", "fl"], ["ring", "omaha", "pl"] ] def determineGameType(self, handText): info = {'type':'ring'} m = self.re_GameInfo.search(handText) if not m: print "determineGameType:", handText return None mg = m.groupdict() # translations from captured groups to our info strings #limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' } limits = { 'NL':'nl', 'PL':'pl'} games = { # base, category "GAME_THM" : ('hold','holdem'), # 'Omaha' : ('hold','omahahi'), #'Omaha Hi/Lo' : ('hold','omahahilo'), # 'Razz' : ('stud','razz'), #'7 Card Stud' : ('stud','studhi'), # 'Badugi' : ('draw','badugi') } if 'LIMIT' in mg: info['limitType'] = limits[mg['LIMIT']] if 'GAME' in mg: (info['base'], info['category']) = games[mg['GAME']] if 'SB' in mg: info['sb'] = mg['SB'] if 'BB' in mg: info['bb'] = mg['BB'] if 'CURRENCY' in mg: info['currency'] = mg['CURRENCY'] # NB: SB, BB must be interpreted as blinds or bets depending on limit type. return info def readHandInfo(self, hand): info = {} m = self.re_HandInfo.search(hand.handText,re.DOTALL) if m: info.update(m.groupdict()) # TODO: Be less lazy and parse maxseats from the HandInfo regex if m.group('TABLEATTRIBUTES'): m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES')) hand.maxseats = int(m2.group(1)) m = self.re_GameInfo.search(hand.handText) if m: info.update(m.groupdict()) m = self.re_Button.search(hand.handText) if m: info.update(m.groupdict()) # TODO : I rather like the idea of just having this dict as hand.info logging.debug("readHandInfo: %s" % info) for key in info: if key == 'DATETIME': # Win2day uses UTC timestamp hand.startTime = datetime.datetime.fromtimestamp(int(info[key])) if key == 'HID': hand.handid = info[key] if key == 'TABLE': hand.tablename = info[key] if key == 'BUTTON': hand.buttonpos = info[key] def readButton(self, hand): m = self.re_Button.search(hand.handText) if m: for player in hand.players: if player[1] == m.group('BUTTON'): hand.buttonpos = player[0] break else: logging.info(_('readButton: not found')) def readPlayerStacks(self, hand): logging.debug("readPlayerStacks") m = self.re_PlayerInfo.finditer(hand.handText) players = [] for a in m: hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) def markStreets(self, hand): # PREFLOP = ** Dealing down cards ** # This re fails if, say, river is missing; then we don't get the ** that starts the river. if hand.gametype['base'] in ("hold"): #m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P.+(?=\*\*\* FLOP \*\*\*)|.+)" # r"(\*\*\* FLOP \*\*\*(?P \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?" # r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?" # r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P\[\S\S\].+))?", hand.handText,re.DOTALL) m = re.search('(?P.+(?=.+(?=.+(?=.+(?= 38): retCard += 's' elif(card > 25): retCard += 'h' elif(card > 12): retCard += 'c' else: retCard += 'd' return(retCard) def readDrawCards(self, hand, street): logging.debug("readDrawCards") m = self.re_HeroCards.finditer(hand.streets[street]) if m == None: hand.involved = False else: for player in m: hand.hero = player.group('PNAME') # Only really need to do this once newcards = player.group('NEWCARDS') oldcards = player.group('OLDCARDS') if newcards == None: newcards = set() else: newcards = set(newcards.split(' ')) if oldcards == None: oldcards = set() else: oldcards = set(oldcards.split(' ')) hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street) def readStudPlayerCards(self, hand, street): # See comments of reference implementation in FullTiltToFpdb.py logging.debug("readStudPlayerCards") m = self.re_HeroCards.finditer(hand.streets[street]) for player in m: #~ logging.debug(player.groupdict()) (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS')) if oldcards: oldcards = [c.strip() for c in oldcards.split(' ')] if newcards: newcards = [c.strip() for c in newcards.split(' ')] if street=='ANTES': return elif street=='THIRD': # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS' # hero: [xx][o] # others: [o] hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards) elif street in ('FOURTH', 'FIFTH', 'SIXTH'): # 4th: # hero: [xxo] [o] # others: [o] [o] # 5th: # hero: [xxoo] [o] # others: [oo] [o] # 6th: # hero: [xxooo] [o] # others: [ooo] [o] hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards) # we may additionally want to check the earlier streets tally with what we have but lets trust it for now. elif street=='SEVENTH' and newcards: # hero: [xxoooo] [x] # others: not reported. hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards) def readAction(self, hand, street): m = self.re_Action.finditer(hand.streets[street]) for action in m: if action.group('ATYPE') == 'ACTION_RAISE': hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == 'ACTION_CALL': hand.addCall( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == 'ACTION_ALLIN': hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == 'ACTION_BET': hand.addBet( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == 'ACTION_FOLD': hand.addFold( street, action.group('PNAME')) elif action.group('ATYPE') == 'ACTION_CHECK': hand.addCheck( street, action.group('PNAME')) elif action.group('ATYPE') == 'ACTION_DISCARD': hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED')) elif action.group('ATYPE') == 'ACTION_STAND': hand.addStandsPat( street, action.group('PNAME')) else: print _("DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),)) def readShowdownActions(self, hand): for shows in self.re_ShowdownAction.finditer(hand.handText): showdownCards = set([]) for card in self.re_Card.finditer(shows.group('CARDS')): #print "DEBUG:", card, card.group('CARD'), self.convertWin2dayCards(card.group('CARD')) showdownCards.add(self.convertWin2dayCards(card.group('CARD'))) hand.addShownCards(showdownCards, shows.group('PNAME')) def readCollectPot(self,hand): for m in self.re_CollectPot.finditer(hand.handText): potcoll = Decimal(m.group('POT')) if potcoll > 0: hand.addCollectPot(player=m.group('PNAME'),pot=potcoll) def readShownCards(self,hand): for m in self.re_ShownCards.finditer(hand.handText): if m.group('CARDS') is not None: cards = m.group('CARDS') cards = set(cards.split(' ')) hand.addShownCards(cards=cards, player=m.group('PNAME')) if __name__ == "__main__": parser = OptionParser() parser.add_option("-i", "--input", dest="ipath", help=_("parse input hand history"), default="-") parser.add_option("-o", "--output", dest="opath", help=_("output translation to"), default="-") parser.add_option("-f", "--follow", dest="follow", help=_("follow (tail -f) the input"), action="store_true", default=False) parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO) parser.add_option("-v", "--verbose", action="store_const", const=logging.INFO, dest="verbosity") parser.add_option("--vv", action="store_const", const=logging.DEBUG, dest="verbosity") (options, args) = parser.parse_args() LOG_FILENAME = './logging.out' logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity) e = Win2day(in_path = options.ipath, out_path = options.opath, follow = options.follow)