2 # -*- coding: utf-8 -*-
4 # Copyright 2008-2011, 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 ########################################################################
21 #Note that this filter also supports UltimateBet, they are both owned by the same company and form the Cereus Network
24 _
= L10n
.get_translation()
26 # TODO: I have no idea if AP has multi-currency options, i just copied the regex out of Everleaf converter for the currency symbols.. weeeeee - Eric
29 from HandHistoryConverter
import *
31 # Class for converting Absolute HH format.
33 class Absolute(HandHistoryConverter
):
43 re_SplitHands
= re
.compile(r
"\n\n+")
44 re_TailSplitHands
= re
.compile(r
"(\nn\n+)")
45 #Stage #1571362962: Holdem No Limit $0.02 - 2009-08-05 15:24:06 (ET)
46 #Table: TORONTO AVE (Real Money) Seat #6 is the dealer
47 #Seat 6 - FETS63 ($0.75 in chips)
48 #Board [10s 5d Kh Qh 8c]
50 re_GameInfo
= re
.compile( ur
"""
51 ^Stage\s+\#C?(?P<HID>[0-9]+):?\s+
52 (?:Tourney\ ID\ (?P<TRNY_ID>\d+)\s+)?
53 (?P<GAME>Holdem|Seven\ Card\ Hi\/L|HORSE)\s+
54 (?P<TRNY_TYPE>\(1\son\s1\)|Single\ Tournament|Multi\ Normal\ Tournament|)\s*
55 (?P<LIMIT>No\ Limit|Pot\ Limit|Normal|)\s?
57 (?P<SB>[.,0-9]+)/?(?:\$|\s€|)(?P<BB>[.,0-9]+)?
59 ((?P<TTYPE>(Turbo))\s+)?-\s+
60 ((?P<DATETIME>\d\d\d\d-\d\d-\d\d\ \d\d:\d\d:\d\d)(\.\d+)?)\s+
61 (?: \( (?P<TZ>[A-Z]+) \)\s+ )?
63 (Table:\ (?P<TABLE>.*?)\ \(Real\ Money\))?
64 """, re
.MULTILINE|re
.VERBOSE|re
.DOTALL
)
66 re_HorseGameInfo
= re
.compile(
67 ur
"^Game Type: (?P<LIMIT>Limit) (?P<GAME>Holdem)",
70 re_HandInfo
= re_GameInfo
72 # on HORSE STUD games, the table name isn't in the hand info!
73 re_RingInfoFromFilename
= re
.compile(ur
".*IHH([0-9]+) (?P<TABLE>.*) -")
74 re_TrnyInfoFromFilename
= re
.compile(
75 ur
"IHH\s?([0-9]+) (?P<TRNY_NAME>.*) "\
76 ur
"ID (?P<TRNY_ID>\d+)\s?(\((?P<TABLE>\d+)\))? .* "\
77 ur
"(?:\$|\s€|)(?P<BUYIN>[0-9.]+)\s*\+\s*(?:\$|\s€|)(?P<FEE>[0-9.]+)"
80 # TODO: that's not the right way to match for "dead" dealer is it?
81 re_Button
= re
.compile(ur
"Seat #(?P<BUTTON>[0-9]) is the ?[dead]* dealer$", re
.MULTILINE
)
83 re_PlayerInfo
= re
.compile(
84 ur
"^Seat (?P<SEAT>[0-9]) - (?P<PNAME>.*) "\
85 ur
"\((?:\$| €|)(?P<CASH>[0-9]*[.,0-9]+) in chips\)",
88 re_Board
= re
.compile(ur
"\[(?P<CARDS>[^\]]*)\]? *$", re
.MULTILINE
)
91 def compilePlayerRegexs(self
, hand
):
92 players
= set([player
[1] for player
in hand
.players
])
93 if not players
<= self
.compiledPlayers
: # x <= y means 'x is subset of y'
94 # we need to recompile the player regexs.
95 self
.compiledPlayers
= players
96 player_re
= "(?P<PNAME>" + "|".join(map(re
.escape
, players
)) + ")"
97 logging
.debug("player_re: "+ player_re
)
98 #(?P<CURRENCY>\$| €|)(?P<BB>[0-9]*[.0-9]+)
99 self
.re_PostSB
= re
.compile(ur
"^%s - Posts small blind (?:\$| €|)(?P<SB>[,.0-9]+)" % player_re
, re
.MULTILINE
)
100 self
.re_PostBB
= re
.compile(ur
"^%s - Posts big blind (?:\$| €|)(?P<BB>[.,0-9]+)" % player_re
, re
.MULTILINE
)
101 # TODO: Absolute posting when coming in new: %s - Posts $0.02 .. should that be a new Post line? where do we need to add support for that? *confused*
102 self
.re_PostBoth
= re
.compile(ur
"^%s - Posts dead (?:\$| €|)(?P<SBBB>[,.0-9]+)" % player_re
, re
.MULTILINE
)
103 self
.re_Action
= re
.compile(ur
"^%s - (?P<ATYPE>Bets |Raises |All-In |All-In\(Raise\) |Calls |Folds|Checks)?\$?(?P<BET>[,.0-9]+)?" % player_re
, re
.MULTILINE
)
104 self
.re_ShowdownAction
= re
.compile(ur
"^%s - Shows \[(?P<CARDS>.*)\]" % player_re
, re
.MULTILINE
)
105 self
.re_CollectPot
= re
.compile(ur
"^Seat [0-9]: %s(?: \(dealer\)|)(?: \(big blind\)| \(small blind\)|) (?:won|collected) Total \((?:\$| €|)(?P<POT>[,.0-9]+)\)" % player_re
, re
.MULTILINE
)
106 self
.re_Antes
= re
.compile(ur
"^%s - Ante \[(?:\$| €|)(?P<ANTE>[,.0-9]+)" % player_re
, re
.MULTILINE
)
107 #self.re_BringIn = re.compile(ur"^%s posts bring-in (?:\$| €|)(?P<BRINGIN>[.0-9]+)\." % player_re, re.MULTILINE)
108 self
.re_HeroCards
= re
.compile(ur
"^Dealt to %s \[(?P<CARDS>.*)\]" % player_re
, re
.MULTILINE
)
110 def readSupportedGames(self
):
111 return [["ring", "hold", "nl"],
112 ["ring", "hold", "pl"],
113 ["ring", "hold", "fl"],
114 ["ring", "studhi", "fl"],
115 ["ring", "omahahi", "pl"],
116 ["tour", "hold", "nl"],
119 def determineGameType(self
, handText
):
120 """return dict with keys/values:
121 'type' in ('ring', 'tour')
122 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl')
123 'base' in ('hold', 'stud', 'draw')
124 'category' in ('holdem', 'omahahi', omahahilo', 'razz',
125 'studhi', 'studhilo', 'fivedraw', '27_1draw',
126 '27_3draw', 'badugi')
127 'hilo' in ('h','l','s')
132 'currency' in ('USD', 'EUR', 'T$', <countrycode>)
134 or None if we fail to get the info """
135 info
= {'type':'ring'}
137 m
= self
.re_GameInfo
.search(handText
)
139 tmp
= handText
[0:100]
140 log
.error(_("Unable to recognise gametype from: '%s'") % tmp
)
141 log
.error("determineGameType: " + _("Raising FpdbParseError"))
142 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp
)
146 #print "DEBUG: mg: %s" % mg
148 # translations from captured groups to our info strings
149 limits
= { 'No Limit':'nl', 'Pot Limit':'pl', 'Normal':'fl', 'Limit':'fl'}
150 games
= { # base, category
151 "Holdem" : ('hold','holdem'),
152 'Omaha' : ('hold','omahahi'),
153 'Razz' : ('stud','razz'),
154 'Seven Card Hi/L' : ('stud','studhilo'),
155 '7 Card Stud' : ('stud','studhi')
157 currencies
= { u
' €':'EUR', '$':'USD', '':'T$' }
158 if 'GAME' in mg
and mg
['GAME'] == "HORSE": # if we're a HORSE game, the game type is on the next line
159 self
.HORSEHand
= True
160 m
= self
.re_HorseGameInfo
.search(handText
)
162 return None # it's a HORSE game and we don't understand the game type
164 #print "AP HORSE processing"
165 if 'GAME' not in temp
or 'LIMIT' not in temp
:
166 return None # sort of understood it but not really
168 mg
['GAME'] = temp
['GAME']
169 mg
['LIMIT'] = temp
['LIMIT']
171 (info
['base'], info
['category']) = games
[mg
['GAME']]
173 info
['limitType'] = limits
[mg
['LIMIT']]
175 info
['currency'] = currencies
[mg
['CURRENCY']]
176 if info
['currency'] == 'T$':
177 info
['type'] = 'tour'
179 mg
['SB'] = mg
['SB'].replace(',', '')
180 info
['sb'] = mg
['SB']
182 info
['bb'] = mg
['BB']
183 # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
184 if info
['bb'] is None:
185 mg
['SB'] = mg
['SB'].replace(',', '')
186 info
['bb'] = mg
['SB']
187 info
['sb'] = str(float(mg
['SB']) * 0.5) # TODO: AP does provide Small BET for Limit .. I think? at least 1-on-1 limit they do.. sigh
192 def readHandInfo(self
, hand
):
193 is_trny
= hand
.gametype
['type']=='tour'
195 m
= self
.re_HandInfo
.search(hand
.handText
)
196 fname_re
= self
.re_TrnyInfoFromFilename
if is_trny \
197 else self
.re_RingInfoFromFilename
198 fname_info
= fname_re
.search(self
.in_path
)
200 #print "DEBUG: fname_info.groupdict(): %s" %(fname_info.groupdict())
202 if m
is None or fname_info
is None:
204 tmp
= hand
.handText
[0:100]
205 logging
.error(_("No match in readHandInfo: '%s'") % tmp
)
206 raise FpdbParseError("Absolute: " + _("No match in readHandInfo: '%s'") % tmp
)
207 elif fname_info
is None:
208 logging
.error(_("File name didn't match re_*InfoFromFilename"))
209 logging
.error(_("File name: %s") % self
.in_path
)
210 raise FpdbParseError("Absolute: " + _("Didn't match re_*InfoFromFilename: '%s'") % self
.in_path
)
212 logging
.debug("HID %s, Table %s" % (m
.group('HID'), m
.group('TABLE')))
213 hand
.handid
= m
.group('HID')
215 hand
.tablename
= m
.group('TABLE')
217 hand
.tablename
= fname_info
.group('TABLE')
219 hand
.startTime
= datetime
.datetime
.strptime(m
.group('DATETIME'), "%Y-%m-%d %H:%M:%S")
222 hand
.fee
= fname_info
.group('FEE')
223 hand
.buyin
= fname_info
.group('BUYIN')
224 hand
.tourNo
= m
.group('TRNY_ID')
225 hand
.tourneyComment
= fname_info
.group('TRNY_NAME')
227 # assume 6-max unless we have proof it's a larger/smaller game,
228 #since absolute doesn't give seat max info
229 # TODO: (1-on-1) does have that info in the game type line
233 hand
.maxseats
= 8 # todo : unless it's heads up!!?
236 def readPlayerStacks(self
, hand
):
237 m
= self
.re_PlayerInfo
.finditer(hand
.handText
)
239 seatnum
= int(a
.group('SEAT'))
240 hand
.addPlayer(seatnum
, a
.group('PNAME'), a
.group('CASH'))
242 hand
.maxseats
= 9 # absolute does 2/4/6/9 games
243 # TODO: implement lookup list by table-name to determine maxes,
244 # then fall back to 6 default/10 here, if there's no entry in the list?
247 def markStreets(self
, hand
):
248 # PREFLOP = ** Dealing down cards **
249 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
250 #m = re.search('(\*\* Dealing down cards \*\*\n)(?P<PREFLOP>.*?\n\*\*)?( Dealing Flop \*\* \[ (?P<FLOP1>\S\S), (?P<FLOP2>\S\S), (?P<FLOP3>\S\S) \])?(?P<FLOP>.*?\*\*)?( Dealing Turn \*\* \[ (?P<TURN1>\S\S) \])?(?P<TURN>.*?\*\*)?( Dealing River \*\* \[ (?P<RIVER1>\S\S) \])?(?P<RIVER>.*)', hand.handText,re.DOTALL)
251 if hand
.gametype
['base'] == 'hold':
252 m
= re
.search(r
"\*\*\* POCKET CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
253 r
"(\*\*\* FLOP \*\*\*(?P<FLOP>.+(?=\*\*\* TURN \*\*\*)|.+))?"
254 r
"(\*\*\* TURN \*\*\*(?P<TURN>.+(?=\*\*\* RIVER \*\*\*)|.+))?"
255 r
"(\*\*\* RIVER \*\*\*(?P<RIVER>.+))?", hand
.handText
, re
.DOTALL
)
257 elif hand
.gametype
['base'] == 'stud': # TODO: Not implemented yet
258 m
= re
.search(r
"(?P<ANTES>.+(?=\*\* Dealing down cards \*\*)|.+)"
259 r
"(\*\* Dealing down cards \*\*(?P<THIRD>.+(?=\*\*\*\* dealing 4th street \*\*\*\*)|.+))?"
260 r
"(\*\*\*\* dealing 4th street \*\*\*\*(?P<FOURTH>.+(?=\*\*\*\* dealing 5th street \*\*\*\*)|.+))?"
261 r
"(\*\*\*\* dealing 5th street \*\*\*\*(?P<FIFTH>.+(?=\*\*\*\* dealing 6th street \*\*\*\*)|.+))?"
262 r
"(\*\*\*\* dealing 6th street \*\*\*\*(?P<SIXTH>.+(?=\*\*\*\* dealing river \*\*\*\*)|.+))?"
263 r
"(\*\*\*\* dealing river \*\*\*\*(?P<SEVENTH>.+))?", hand
.handText
,re
.DOTALL
)
266 def readCommunityCards(self
, hand
, street
):
267 # street has been matched by markStreets, so exists in this hand
268 # If this has been called, street is a street which gets dealt
269 # community cards by type hand but it might be worth checking somehow.
270 # if street in ('FLOP','TURN','RIVER'):
271 # a list of streets which get dealt community cards (i.e. all but PREFLOP)
272 logging
.debug("readCommunityCards (%s)" % street
)
273 m
= self
.re_Board
.search(hand
.streets
[street
])
274 cards
= m
.group('CARDS')
275 cards
= [validCard(card
) for card
in cards
.split(' ')]
276 hand
.setCommunityCards(street
=street
, cards
=cards
)
278 def readAntes(self
, hand
):
279 logging
.debug(_("reading antes"))
280 m
= self
.re_Antes
.finditer(hand
.handText
)
282 logging
.debug("hand.addAnte(%s,%s)" %(player
.group('PNAME'), player
.group('ANTE')))
283 hand
.addAnte(player
.group('PNAME'), player
.group('ANTE'))
285 def readBringIn(self
, hand
):
286 m
= self
.re_BringIn
.search(hand
.handText
,re
.DOTALL
)
288 logging
.debug(_("Player bringing in: %s for %s") % (m
.group('PNAME'), m
.group('BRINGIN')))
289 hand
.addBringIn(m
.group('PNAME'), m
.group('BRINGIN'))
291 logging
.warning(_("No bringin found."))
293 def readBlinds(self
, hand
):
294 m
= self
.re_PostSB
.search(hand
.handText
)
296 hand
.addBlind(m
.group('PNAME'), 'small blind', m
.group('SB'))
298 logging
.debug(_("No small blind"))
299 hand
.addBlind(None, None, None)
300 for a
in self
.re_PostBB
.finditer(hand
.handText
):
301 hand
.addBlind(a
.group('PNAME'), 'big blind', a
.group('BB'))
302 for a
in self
.re_PostBoth
.finditer(hand
.handText
):
303 hand
.addBlind(a
.group('PNAME'), 'both', a
.group('SBBB'))
305 def readButton(self
, hand
):
306 hand
.buttonpos
= int(self
.re_Button
.search(hand
.handText
).group('BUTTON'))
308 def readHeroCards(self
, hand
):
309 m
= self
.re_HeroCards
.search(hand
.handText
)
311 hand
.hero
= m
.group('PNAME')
312 # "2c, qh" -> ["2c","qc"]
313 # Also works with Omaha hands.
314 cards
= m
.group('CARDS')
315 cards
= [validCard(card
) for card
in cards
.split(' ')]
316 # hand.addHoleCards(cards, m.group('PNAME'))
317 hand
.addHoleCards('PREFLOP', hand
.hero
, closed
=cards
, shown
=False, mucked
=False, dealt
=True)
320 #Not involved in hand
321 hand
.involved
= False
323 def readStudPlayerCards(self
, hand
, street
):
324 logging
.warning(_("%s cannot read all stud/razz hands yet.") % hand
.sitename
)
326 def readAction(self
, hand
, street
):
327 logging
.debug("readAction (%s)" % street
)
328 m
= self
.re_Action
.finditer(hand
.streets
[street
])
330 logging
.debug("%s %s" % (action
.group('ATYPE'), action
.groupdict()))
331 if action
.group('ATYPE') == 'Raises ' or action
.group('ATYPE') == 'All-In(Raise) ':
332 bet
= action
.group('BET').replace(',', '')
333 hand
.addCallandRaise( street
, action
.group('PNAME'), bet
)
334 elif action
.group('ATYPE') == 'Calls ':
335 bet
= action
.group('BET').replace(',', '')
336 hand
.addCall( street
, action
.group('PNAME'), bet
)
337 elif action
.group('ATYPE') == 'Bets ' or action
.group('ATYPE') == 'All-In ':
338 bet
= action
.group('BET').replace(',', '')
339 hand
.addBet( street
, action
.group('PNAME'), bet
)
340 elif action
.group('ATYPE') == 'Folds':
341 hand
.addFold( street
, action
.group('PNAME'))
342 elif action
.group('ATYPE') == 'Checks':
343 hand
.addCheck( street
, action
.group('PNAME'))
344 elif action
.group('ATYPE') == ' complete to': # TODO: not supported yet ?
345 bet
= action
.group('BET').replace(',', '')
346 hand
.addComplete( street
, action
.group('PNAME'), bet
)
348 logging
.debug(_("Unimplemented %s: '%s' '%s'") % ("readAction", action
.group('PNAME'), action
.group('ATYPE')))
351 def readShowdownActions(self
, hand
):
352 """Reads lines where holecards are reported in a showdown"""
353 logging
.debug("readShowdownActions")
354 for shows
in self
.re_ShowdownAction
.finditer(hand
.handText
):
355 cards
= shows
.group('CARDS')
356 cards
= [validCard(card
) for card
in cards
.split(' ')]
357 logging
.debug("readShowdownActions %s %s" %(cards
, shows
.group('PNAME')))
358 hand
.addShownCards(cards
, shows
.group('PNAME'))
361 def readCollectPot(self
,hand
):
362 for m
in self
.re_CollectPot
.finditer(hand
.handText
):
363 pot
= m
.group('POT').replace(',','')
364 hand
.addCollectPot(player
=m
.group('PNAME'),pot
=pot
)
366 def readShownCards(self
,hand
):
367 """Reads lines where hole & board cards are mixed to form a hand (summary lines)"""
368 for m
in self
.re_CollectPot
.finditer(hand
.handText
):
370 if m
.group('CARDS') is not None:
371 cards
= m
.group('CARDS')
372 cards
= [validCard(card
) for card
in cards
.split(' ')]
373 player
= m
.group('PNAME')
374 logging
.debug("readShownCards %s cards=%s" % (player
, cards
))
375 # hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
376 hand
.addShownCards(cards
=cards
, player
=m
.group('PNAME'))
378 pass # there's no "PLAYER - Mucks" at AP that I can see
382 if card
== '10s': card
= 'Ts'
383 if card
== '10h': card
= 'Th'
384 if card
== '10d': card
= 'Td'
385 if card
== '10c': card
= 'Tc'