2 # -*- coding: utf-8 -*-
4 # Copyright 2008-2010, Carl Gherardi
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 ########################################################################
22 _
= L10n
.get_translation()
26 from HandHistoryConverter
import *
30 class Betfair(HandHistoryConverter
):
35 siteId
= 7 # Needs to match id entry in Sites database
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
)
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
)
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$' }
93 info
['limitType'] = limits
[mg
['LIMIT']]
95 (info
['base'], info
['category']) = games
[mg
['GAME']]
101 info
['currency'] = currencies
[mg
['CURRENCY']]
105 def readHandInfo(self
, hand
):
106 m
= self
.re_HandInfo
.search(hand
.handText
)
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
)
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
)
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
):
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
)
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
)
160 logging
.debug(_("Player bringing in: %s for %s" %(m
.group('PNAME'), m
.group('BRINGIN'))))
161 hand
.addBringIn(m
.group('PNAME'), m
.group('BRINGIN'))
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
])
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
):
183 def readAction(self
, hand
, street
):
184 m
= self
.re_Action
.finditer(hand
.streets
[street
])
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'))
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
)