If you only have games of a single limit type (fixed, pot, or no limit), but of more...
[fpdb-dooglus.git] / pyfpdb / BetfairToFpdb.py
blobd30b3b8e92ef591ab59c9635559fa1727efe1741
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright 2008-2010, Carl Gherardi
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.
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.
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 ########################################################################
21 import L10n
22 _ = L10n.get_translation()
24 import sys
25 import logging
26 from HandHistoryConverter import *
28 # Betfair HH format
30 class Betfair(HandHistoryConverter):
32 sitename = 'Betfair'
33 filetype = "text"
34 codepage = "cp1252"
35 siteId = 7 # Needs to match id entry in Sites database
37 # Static regexes
38 re_GameInfo = re.compile("^(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAME>(Texas Hold\'em|Omaha Hi|Omaha|Razz))", re.MULTILINE)
39 re_SplitHands = re.compile(r'\n\n+')
40 re_HandInfo = re.compile("\*\*\*\*\* Betfair Poker Hand History for Game (?P<HID>[0-9]+) \*\*\*\*\*\n(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>(Texas Hold\'em|Omaha|Razz)) - (?P<DATETIME>[a-zA-Z]+, [a-zA-Z]+ \d+, \d\d:\d\d:\d\d GMT \d\d\d\d)\nTable (?P<TABLE>[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P<BUTTON>[0-9]+)", re.MULTILINE)
41 re_Button = re.compile(ur"^Seat (?P<BUTTON>\d+) is the button", re.MULTILINE)
42 re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)")
43 re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
46 def compilePlayerRegexs(self, hand):
47 players = set([player[1] for player in hand.players])
48 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
49 # we need to recompile the player regexs.
50 self.compiledPlayers = players
51 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
52 logging.debug("player_re: " + player_re)
53 self.re_PostSB = re.compile("^%s posts small blind \[\$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
54 self.re_PostBB = re.compile("^%s posts big blind \[\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
55 self.re_Antes = re.compile("^%s antes asdf sadf sadf" % player_re, re.MULTILINE)
56 self.re_BringIn = re.compile("^%s antes asdf sadf sadf" % player_re, re.MULTILINE)
57 self.re_PostBoth = re.compile("^%s posts small \& big blinds \[\$?(?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
58 self.re_HeroCards = re.compile("^Dealt to %s \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
59 self.re_Action = re.compile("^%s (?P<ATYPE>bets|checks|raises to|raises|calls|folds)(\s\[\$(?P<BET>[.\d]+)\])?" % player_re, re.MULTILINE)
60 self.re_ShowdownAction = re.compile("^%s shows \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
61 self.re_CollectPot = re.compile("^%s wins \$(?P<POT>[.\d]+) (.*?\[ (?P<CARDS>.*?) \])?" % player_re, re.MULTILINE)
62 self.re_SitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
63 self.re_ShownCards = re.compile(r"%s (?P<SEAT>[0-9]+) (?P<CARDS>adsfasdf)" % player_re, re.MULTILINE)
65 def readSupportedGames(self):
66 return [["ring", "hold", "nl"],
67 ["ring", "hold", "pl"]
70 def determineGameType(self, handText):
71 info = {'type':'ring'}
73 m = self.re_GameInfo.search(handText)
74 if not m:
75 tmp = handText[0:100]
76 log.error(_("determineGameType: Unable to recognise gametype from: '%s'") % tmp)
77 log.error(_("determineGameType: Raising FpdbParseError"))
78 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp)
80 mg = m.groupdict()
82 # translations from captured groups to our info strings
83 limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' }
84 games = { # base, category
85 "Texas Hold'em" : ('hold','holdem'),
86 'Omaha Hi' : ('hold','omahahi'),
87 'Omaha' : ('hold','omahahi'),
88 'Razz' : ('stud','razz'),
89 '7 Card Stud' : ('stud','studhi')
91 currencies = { u' €':'EUR', '$':'USD', '':'T$' }
92 if 'LIMIT' in mg:
93 info['limitType'] = limits[mg['LIMIT']]
94 if 'GAME' in mg:
95 (info['base'], info['category']) = games[mg['GAME']]
96 if 'SB' in mg:
97 info['sb'] = mg['SB']
98 if 'BB' in mg:
99 info['bb'] = mg['BB']
100 if 'CURRENCY' in mg:
101 info['currency'] = currencies[mg['CURRENCY']]
103 return info
105 def readHandInfo(self, hand):
106 m = self.re_HandInfo.search(hand.handText)
107 if(m == None):
108 log.error(_("Didn't match re_HandInfo"))
109 raise FpdbParseError(_("No match in readHandInfo."))
110 logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE')))
111 hand.handid = m.group('HID')
112 hand.tablename = m.group('TABLE')
113 hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), "%A, %B %d, %H:%M:%S GMT %Y")
114 #hand.buttonpos = int(m.group('BUTTON'))
116 def readPlayerStacks(self, hand):
117 m = self.re_PlayerInfo.finditer(hand.handText)
118 for a in m:
119 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
121 #Shouldn't really dip into the Hand object, but i've no idea how to tell the length of iter m
122 if len(hand.players) < 2:
123 logging.info(_("readPlayerStacks: Less than 2 players found in a hand"))
125 def markStreets(self, hand):
126 m = re.search(r"\*\* Dealing down cards \*\*(?P<PREFLOP>.+(?=\*\* Dealing Flop \*\*)|.+)"
127 r"(\*\* Dealing Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?"
128 r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?"
129 r"(\*\* Dealing River \*\*(?P<RIVER> \[ \S\S \].+))?", hand.handText,re.DOTALL)
131 hand.addStreets(m)
134 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
135 if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
136 m = self.re_Board.search(hand.streets[street])
137 hand.setCommunityCards(street, m.group('CARDS').split(', '))
139 def readBlinds(self, hand):
140 try:
141 m = self.re_PostSB.search(hand.handText)
142 hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
143 except: # no small blind
144 hand.addBlind(None, None, None)
145 for a in self.re_PostBB.finditer(hand.handText):
146 hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
147 for a in self.re_PostBoth.finditer(hand.handText):
148 hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
150 def readAntes(self, hand):
151 logging.debug("reading antes")
152 m = self.re_Antes.finditer(hand.handText)
153 for player in m:
154 logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
155 hand.addAnte(player.group('PNAME'), player.group('ANTE'))
157 def readBringIn(self, hand):
158 m = self.re_BringIn.search(hand.handText,re.DOTALL)
159 if m:
160 logging.debug(_("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))))
161 hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
162 else:
163 logging.warning(_("No bringin found"))
165 def readButton(self, hand):
166 hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
168 def readHeroCards(self, hand):
169 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
170 # we need to grab hero's cards
171 for street in ('PREFLOP', 'DEAL'):
172 if street in hand.streets.keys():
173 m = self.re_HeroCards.finditer(hand.streets[street])
174 for found in m:
175 hand.hero = found.group('PNAME')
176 newcards = [c.strip() for c in found.group('CARDS').split(',')]
177 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
179 def readStudPlayerCards(self, hand, street):
180 # balh blah blah
181 pass
183 def readAction(self, hand, street):
184 m = self.re_Action.finditer(hand.streets[street])
185 for action in m:
186 if action.group('ATYPE') == 'raises to':
187 hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
188 # elif action.group('ATYPE') == ' completes it to':
189 # hand.addComplete( street, action.group('PNAME'), action.group('BET') )
190 elif action.group('ATYPE') == 'calls':
191 hand.addCall( street, action.group('PNAME'), action.group('BET') )
192 elif action.group('ATYPE') == 'bets':
193 hand.addBet( street, action.group('PNAME'), action.group('BET') )
194 elif action.group('ATYPE') == 'folds':
195 hand.addFold( street, action.group('PNAME'))
196 elif action.group('ATYPE') == 'checks':
197 hand.addCheck( street, action.group('PNAME'))
198 else:
199 sys.stderr.write( _("DEBUG: unimplemented readAction: '%s' '%s'") %(action.group('PNAME'),action.group('ATYPE'),))
202 def readShowdownActions(self, hand):
203 for shows in self.re_ShowdownAction.finditer(hand.handText):
204 cards = shows.group('CARDS')
205 cards = cards.split(', ')
206 hand.addShownCards(cards, shows.group('PNAME'))
208 def readCollectPot(self,hand):
209 for m in self.re_CollectPot.finditer(hand.handText):
210 hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
212 def readShownCards(self,hand):
213 for m in self.re_ShownCards.finditer(hand.handText):
214 if m.group('CARDS') is not None:
215 cards = m.group('CARDS')
216 cards = cards.split(', ')
217 hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
220 if __name__ == "__main__":
221 parser = OptionParser()
222 parser.add_option("-i", "--input", dest="ipath", help=_("parse input hand history"), default="regression-test-files/betfair/befair.02.04.txt")
223 parser.add_option("-o", "--output", dest="opath", help=_("output translation to"), default="-")
224 parser.add_option("-f", "--follow", dest="follow", help=_("follow (tail -f) the input"), action="store_true", default=False)
225 parser.add_option("-q", "--quiet",
226 action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
227 parser.add_option("-v", "--verbose",
228 action="store_const", const=logging.INFO, dest="verbosity")
229 parser.add_option("--vv",
230 action="store_const", const=logging.DEBUG, dest="verbosity")
232 (options, args) = parser.parse_args()
234 LOG_FILENAME = './logging.out'
235 logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
237 e = Betfair(in_path = options.ipath, out_path = options.opath, follow = options.follow)