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 ########################################################################
22 _
= L10n
.get_translation()
26 from HandHistoryConverter
import *
30 class Boss(HandHistoryConverter
):
38 re_GameInfo
= re
.compile("""<HISTORY\sID="(?P<HID>[0-9]+)"\s
39 SESSION="session(?P<SESSIONID>[0-9]+)\.xml"\s
40 TABLE="(?P<TABLE>[-\sa-zA-Z0-9\xc0-\xfc/.]+)"\s
41 GAME="(?P<GAME>GAME_THM|GAME_OMA|GAME_FCD)"\sGAMETYPE="[_a-zA-Z]+"\s
42 GAMEKIND="(?P<GAMEKIND>[_a-zA-Z]+)"\s
43 TABLECURRENCY="(?P<CURRENCY>[A-Z]+)"\s
44 LIMIT="(?P<LIMIT>NL|PL|FL)"\s
45 STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)"\s
46 DATE="(?P<DATETIME>[0-9]+)"\s
47 (TABLETOURNEYID=""\s)?
48 WIN="[.0-9]+"\sLOSS="[.0-9]+"
49 """, re
.MULTILINE| re
.VERBOSE
)
50 re_SplitHands
= re
.compile('</HISTORY>')
51 re_Button
= re
.compile('<ACTION TYPE="HAND_DEAL" PLAYER="(?P<BUTTON>[^"]+)">\n<CARD LINK="[0-9b]+"></CARD>\n<CARD LINK="[0-9b]+"></CARD></ACTION>\n<ACTION TYPE="ACTION_', re
.MULTILINE
)
52 re_PlayerInfo
= re
.compile('^<PLAYER NAME="(?P<PNAME>.*)" SEAT="(?P<SEAT>[0-9]+)" AMOUNT="(?P<CASH>[.0-9]+)"( STATE="(?P<STATE>STATE_EMPTY|STATE_PLAYING)" DEALER="(Y|N)")?></PLAYER>', re
.MULTILINE
)
53 re_Card
= re
.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re
.MULTILINE
)
54 re_BoardLast
= re
.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re
.MULTILINE
)
57 # we need to recompile the player regexs.
58 player_re
= '(?P<PNAME>[^"]+)'
59 #logging.debug("player_re: " + player_re)
60 #<ACTION TYPE="HAND_BLINDS" PLAYER="prato" KIND="HAND_SB" VALUE="0.25"></ACTION>
62 re_PostSB
= re
.compile(r
'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_SB" VALUE="(?P<SB>[.0-9]+)"></ACTION>' % player_re
, re
.MULTILINE
)
63 re_PostBB
= re
.compile(r
'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_BB" VALUE="(?P<BB>[.0-9]+)"></ACTION>' % player_re
, re
.MULTILINE
)
64 re_Antes
= re
.compile(r
"^%s: posts the ante \$?(?P<ANTE>[.0-9]+)" % player_re
, re
.MULTILINE
)
65 re_BringIn
= re
.compile(r
"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re
, re
.MULTILINE
)
66 re_PostBoth
= re
.compile(r
'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re
, re
.MULTILINE
)
68 re_HeroCards
= re
.compile(r
'PLAYER="%s">(?P<CARDS>(\s+<CARD LINK="[0-9]+"></CARD>){2,5})</ACTION>' % player_re
, re
.MULTILINE
)
70 #'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
71 re_Action
= re
.compile(r
'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re
, re
.MULTILINE
)
73 re_ShowdownAction
= re
.compile(r
'<RESULT PLAYER="%s" WIN="[.0-9]+" HAND="(?P<HAND>\(\$STR_G_FOLD\)|[\$\(\)_ A-Z]+)">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</RESULT>' % player_re
, re
.MULTILINE
)
74 #<RESULT PLAYER="wig0r" WIN="4.10" HAND="$(STR_G_WIN_TWOPAIR) $(STR_G_CARDS_TENS) $(STR_G_ANDTEXT) $(STR_G_CARDS_EIGHTS)">
76 re_CollectPot
= re
.compile(r
'<RESULT PLAYER="%s" WIN="(?P<POT>[.\d]+)" HAND=".+">' % player_re
, re
.MULTILINE
)
77 re_sitsOut
= re
.compile("^%s sits out" % player_re
, re
.MULTILINE
)
78 re_ShownCards
= re
.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re
, re
.MULTILINE
)
80 def compilePlayerRegexs(self
, hand
):
83 def readSupportedGames(self
):
84 return [["ring", "hold", "nl"],
85 ["ring", "hold", "pl"],
86 ["ring", "hold", "fl"],
87 ["ring", "stud", "fl"],
88 ["ring", "draw", "fl"],
89 ["tour", "hold", "fl"],
90 ["tour", "hold", "pl"],
91 ["tour", "hold", "nl"],
94 def determineGameType(self
, handText
):
96 m
= self
.re_GameInfo
.search(handText
)
99 log
.error(_("BossToFpdb.determineGameType: '%s'") % tmp
)
103 #print "DEBUG: mg: %s" % mg
105 # translations from captured groups to our info strings
106 #limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' }
107 limits
= { 'NL':'nl', 'PL':'pl', 'FL':'fl'}
108 games
= { # base, category
109 "GAME_THM" : ('hold','holdem'),
110 "GAME_OMA" : ('hold','omahahi'),
111 "GAME_FCD" : ('draw','fivedraw'),
114 info
['type'] = 'ring'
115 if mg
['GAMEKIND'] == 'GAMEKIND_TOURNAMENT':
116 info
['type'] = 'tour'
118 info
['limitType'] = limits
[mg
['LIMIT']]
120 (info
['base'], info
['category']) = games
[mg
['GAME']]
122 info
['sb'] = mg
['SB']
124 info
['bb'] = mg
['BB']
126 info
['currency'] = mg
['CURRENCY']
127 # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
131 def readHandInfo(self
, hand
):
133 m
= self
.re_GameInfo
.search(hand
.handText
)
136 tmp
= hand
.handText
[0:200]
137 log
.error(_("BossToFpdb.readHandInfo: '%s'") % tmp
)
140 info
.update(m
.groupdict())
141 m
= self
.re_Button
.search(hand
.handText
)
142 if m
: info
.update(m
.groupdict())
145 if key
== 'DATETIME':
146 # Boss uses UTC timestamp
147 hand
.startTime
= datetime
.datetime
.fromtimestamp(int(info
[key
]))
149 hand
.handid
= info
[key
]
151 hand
.tablename
= info
[key
]
153 hand
.buttonpos
= info
[key
]
155 hand
.level
= info
[key
]
156 if hand
.gametype
['type'] == 'tour':
157 if key
== 'SESSIONID': # No idea why Boss doesn't use the TABLETOURNEYID xml field...
158 hand
.tourNo
= info
[key
]
159 if key
== 'CURRENCY':
160 hand
.buyinCurrency
= info
[key
]
161 # Hmm. Other useful tourney info doesn't appear to be readily available.
167 def readButton(self
, hand
):
168 m
= self
.re_Button
.search(hand
.handText
)
170 for player
in hand
.players
:
171 if player
[1] == m
.group('BUTTON'):
172 hand
.buttonpos
= player
[0]
175 log
.info('readButton: ' + _('not found'))
177 def readPlayerStacks(self
, hand
):
178 logging
.debug("readPlayerStacks")
179 m
= self
.re_PlayerInfo
.finditer(hand
.handText
)
182 if a
.group('STATE') is not None:
183 if a
.group('STATE') == 'STATE_PLAYING':
184 hand
.addPlayer(int(a
.group('SEAT')), a
.group('PNAME'), a
.group('CASH'))
186 hand
.addPlayer(int(a
.group('SEAT')), a
.group('PNAME'), a
.group('CASH'))
188 def markStreets(self
, hand
):
189 # PREFLOP = ** Dealing down cards **
190 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
191 if hand
.gametype
['base'] in ("hold"):
192 m
= re
.search('<ACTION TYPE="HAND_BLINDS" PLAYER=".+" KIND="(HAND_BB|HAND_SB)" VALUE="[.0-9]+"></ACTION>(?P<PREFLOP>.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP")|.+)'
193 '((?P<FLOP><ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN")|.+))?'
194 '((?P<TURN><ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER")|.+))?'
195 '((?P<RIVER><ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER" POT="[.0-9]+">.+(?=<SHOWDOWN NAME="HAND_SHOWDOWN")|.+))?', hand
.handText
,re
.DOTALL
)
196 if hand
.gametype
['category'] in ('27_1draw', 'fivedraw'):
197 m
= re
.search(r
'(?P<PREDEAL>.+?(?=<ACTION TYPE="HAND_DEAL")|.+)'
198 r
'(<ACTION TYPE="HAND_DEAL"(?P<DEAL>.+(?=<ACTION TYPE="HAND_BOARD")|.+))?'
199 r
'(<ACTION TYPE="(?P<DRAWONE>.+))?', hand
.handText
,re
.DOTALL
)
201 #pprint.pprint(m.groupdict())
204 def readCommunityCards(self
, hand
, street
): # street has been matched by markStreets, so exists in this hand
205 if street
in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
206 #print "DEBUG readCommunityCards:", street, hand.streets.group(street)
210 m
= self
.re_Card
.findall(hand
.streets
[street
])
212 boardCards
.append(self
.convertBossCards(card
))
214 m
= self
.re_BoardLast
.search(hand
.streets
[street
])
215 boardCards
.append(self
.convertBossCards(m
.group('CARD')))
217 hand
.setCommunityCards(street
, boardCards
)
219 def readAntes(self
, hand
):
220 logging
.debug(_("reading antes"))
221 m
= self
.re_Antes
.finditer(hand
.handText
)
223 #~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
224 hand
.addAnte(player
.group('PNAME'), player
.group('ANTE'))
226 def readBringIn(self
, hand
):
227 m
= self
.re_BringIn
.search(hand
.handText
,re
.DOTALL
)
229 #~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
230 hand
.addBringIn(m
.group('PNAME'), m
.group('BRINGIN'))
232 def readBlinds(self
, hand
):
234 m
= self
.re_PostSB
.search(hand
.handText
)
235 hand
.addBlind(m
.group('PNAME'), 'small blind', m
.group('SB'))
236 except: # no small blind
237 hand
.addBlind(None, None, None)
238 for a
in self
.re_PostBB
.finditer(hand
.handText
):
239 hand
.addBlind(a
.group('PNAME'), 'big blind', a
.group('BB'))
240 for a
in self
.re_PostBoth
.finditer(hand
.handText
):
241 hand
.addBlind(a
.group('PNAME'), 'both', a
.group('SBBB'))
243 def readHeroCards(self
, hand
):
244 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
245 # we need to grab hero's cards
246 for street
in ('PREFLOP', 'DEAL'):
247 if street
in hand
.streets
.keys():
248 m
= self
.re_HeroCards
.finditer(hand
.streets
[street
])
251 hand
.hero
= found
.group('PNAME')
252 for card
in self
.re_Card
.finditer(found
.group('CARDS')):
253 newcards
.append(self
.convertBossCards(card
.group('CARD')))
254 hand
.addHoleCards(street
, hand
.hero
, closed
=newcards
, shown
=False, mucked
=False, dealt
=True)
256 def convertBossCards(self
, card
):
259 cardconvert
= { 1:'A',
264 realNumber
= card
% 13 + 1
265 if(realNumber
in cardconvert
):
266 retCard
+= cardconvert
[realNumber
]
268 retCard
+= str(realNumber
)
281 def readDrawCards(self
, hand
, street
):
282 logging
.debug("readDrawCards")
283 m
= self
.re_HeroCards
.finditer(hand
.streets
[street
])
285 hand
.involved
= False
288 hand
.hero
= player
.group('PNAME') # Only really need to do this once
289 newcards
= player
.group('NEWCARDS')
290 oldcards
= player
.group('OLDCARDS')
294 newcards
= newcards
.split(' ')
298 oldcards
= oldcards
.split(' ')
299 hand
.addDrawHoleCards(newcards
, oldcards
, player
.group('PNAME'), street
)
302 def readStudPlayerCards(self
, hand
, street
):
303 # See comments of reference implementation in FullTiltToFpdb.py
304 # logging.debug("readStudPlayerCards")
305 m
= self
.re_HeroCards
.finditer(hand
.streets
[street
])
307 #~ logging.debug(player.groupdict())
308 (pname
, oldcards
, newcards
) = (player
.group('PNAME'), player
.group('OLDCARDS'), player
.group('NEWCARDS'))
310 oldcards
= [c
.strip() for c
in oldcards
.split(' ')]
312 newcards
= [c
.strip() for c
in newcards
.split(' ')]
315 elif street
=='THIRD':
316 # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
319 hand
.addPlayerCards(player
= player
.group('PNAME'), street
= street
, closed
= oldcards
, open = newcards
)
320 elif street
in ('FOURTH', 'FIFTH', 'SIXTH'):
330 hand
.addPlayerCards(player
= player
.group('PNAME'), street
= street
, open = newcards
)
331 # we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
332 elif street
=='SEVENTH' and newcards
:
334 # others: not reported.
335 hand
.addPlayerCards(player
= player
.group('PNAME'), street
= street
, closed
= newcards
)
337 def readAction(self
, hand
, street
):
338 m
= self
.re_Action
.finditer(hand
.streets
[street
])
340 if action
.group('ATYPE') == 'ACTION_FOLD':
341 hand
.addFold( street
, action
.group('PNAME'))
342 elif action
.group('ATYPE') == 'ACTION_CHECK':
343 hand
.addCheck( street
, action
.group('PNAME'))
344 elif action
.group('ATYPE') == 'ACTION_CALL':
345 hand
.addCall( street
, action
.group('PNAME'), action
.group('BET') )
346 elif action
.group('ATYPE') == 'ACTION_RAISE':
347 hand
.addRaiseBy( street
, action
.group('PNAME'), action
.group('BET') )
348 elif action
.group('ATYPE') == 'ACTION_BET':
349 hand
.addBet( street
, action
.group('PNAME'), action
.group('BET') )
350 elif action
.group('ATYPE') == 'ACTION_DISCARD':
351 hand
.addDiscard(street
, action
.group('PNAME'), action
.group('NODISCARDED'), action
.group('DISCARDED'))
352 elif action
.group('ATYPE') == 'ACTION_STAND':
353 hand
.addStandsPat( street
, action
.group('PNAME'))
354 elif action
.group('ATYPE') == 'ACTION_ALLIN':
355 hand
.addRaiseBy( street
, action
.group('PNAME'), action
.group('BET') )
357 print (_("DEBUG:") + _("Unimplemented %s: '%s' '%s'") % ("readAction", action
.group('PNAME'), action
.group('ATYPE')))
360 def readShowdownActions(self
, hand
):
361 for shows
in self
.re_ShowdownAction
.finditer(hand
.handText
):
363 for card
in self
.re_Card
.finditer(shows
.group('CARDS')):
364 #print "DEBUG:", card, card.group('CARD'), self.convertBossCards(card.group('CARD'))
365 showdownCards
.append(self
.convertBossCards(card
.group('CARD')))
367 hand
.addShownCards(showdownCards
, shows
.group('PNAME'))
369 def readCollectPot(self
,hand
):
370 for m
in self
.re_CollectPot
.finditer(hand
.handText
):
371 potcoll
= Decimal(m
.group('POT'))
373 hand
.addCollectPot(player
=m
.group('PNAME'),pot
=potcoll
)
375 def readShownCards(self
,hand
):
376 for m
in self
.re_ShownCards
.finditer(hand
.handText
):
377 if m
.group('CARDS') is not None:
378 cards
= m
.group('CARDS')
379 cards
= cards
.split(' ')
380 hand
.addShownCards(cards
=cards
, player
=m
.group('PNAME'))