Use debian 2.7 only
[fpbd-bostik.git] / pyfpdb / BossToFpdb.py
blobb0968651e7c3767376022377afa2322d7bb676bf
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright 2008-2011, 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 datetime
26 from HandHistoryConverter import *
28 # Boss HH Format
30 class Boss(HandHistoryConverter):
32 sitename = "Boss"
33 filetype = "text"
34 codepage = "utf-8"
35 siteId = 4
37 # Static regexes
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):
81 pass
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):
95 info = {}
96 m = self.re_GameInfo.search(handText)
97 if not m:
98 tmp = handText[0:200]
99 log.error(_("BossToFpdb.determineGameType: '%s'") % tmp)
100 raise FpdbParseError
102 mg = m.groupdict()
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'),
113 if 'GAMEKIND' in mg:
114 info['type'] = 'ring'
115 if mg['GAMEKIND'] == 'GAMEKIND_TOURNAMENT':
116 info['type'] = 'tour'
117 if 'LIMIT' in mg:
118 info['limitType'] = limits[mg['LIMIT']]
119 if 'GAME' in mg:
120 (info['base'], info['category']) = games[mg['GAME']]
121 if 'SB' in mg:
122 info['sb'] = mg['SB']
123 if 'BB' in mg:
124 info['bb'] = mg['BB']
125 if 'CURRENCY' in mg:
126 info['currency'] = mg['CURRENCY']
127 # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
128 return info
131 def readHandInfo(self, hand):
132 info = {}
133 m = self.re_GameInfo.search(hand.handText)
135 if m is None:
136 tmp = hand.handText[0:200]
137 log.error(_("BossToFpdb.readHandInfo: '%s'") % tmp)
138 raise FpdbParseError
140 info.update(m.groupdict())
141 m = self.re_Button.search(hand.handText)
142 if m: info.update(m.groupdict())
144 for key in info:
145 if key == 'DATETIME':
146 # Boss uses UTC timestamp
147 hand.startTime = datetime.datetime.fromtimestamp(int(info[key]))
148 if key == 'HID':
149 hand.handid = info[key]
150 if key == 'TABLE':
151 hand.tablename = info[key]
152 if key == 'BUTTON':
153 hand.buttonpos = info[key]
154 if key == 'LEVEL':
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.
162 hand.buyin = 100
163 hand.fee = 10
164 hand.isKO = False
167 def readButton(self, hand):
168 m = self.re_Button.search(hand.handText)
169 if m:
170 for player in hand.players:
171 if player[1] == m.group('BUTTON'):
172 hand.buttonpos = player[0]
173 break
174 else:
175 log.info('readButton: ' + _('not found'))
177 def readPlayerStacks(self, hand):
178 logging.debug("readPlayerStacks")
179 m = self.re_PlayerInfo.finditer(hand.handText)
180 players = []
181 for a in m:
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'))
185 else:
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)
200 #import pprint
201 #pprint.pprint(m.groupdict())
202 hand.addStreets(m)
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)
208 boardCards = []
209 if street == 'FLOP':
210 m = self.re_Card.findall(hand.streets[street])
211 for card in m:
212 boardCards.append(self.convertBossCards(card))
213 else:
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)
222 for player in m:
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)
228 if m:
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):
233 try:
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])
249 newcards = []
250 for found in m:
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):
257 card = int(card)
258 retCard = ''
259 cardconvert = { 1:'A',
260 10:'T',
261 11:'J',
262 12:'Q',
263 13:'K'}
264 realNumber = card % 13 + 1
265 if(realNumber in cardconvert):
266 retCard += cardconvert[realNumber]
267 else:
268 retCard += str(realNumber)
270 if(card > 38):
271 retCard += 's'
272 elif(card > 25):
273 retCard += 'h'
274 elif(card > 12):
275 retCard += 'c'
276 else:
277 retCard += 'd'
279 return(retCard)
281 def readDrawCards(self, hand, street):
282 logging.debug("readDrawCards")
283 m = self.re_HeroCards.finditer(hand.streets[street])
284 if m == None:
285 hand.involved = False
286 else:
287 for player in m:
288 hand.hero = player.group('PNAME') # Only really need to do this once
289 newcards = player.group('NEWCARDS')
290 oldcards = player.group('OLDCARDS')
291 if newcards == None:
292 newcards = []
293 else:
294 newcards = newcards.split(' ')
295 if oldcards == None:
296 oldcards = []
297 else:
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])
306 for player in m:
307 #~ logging.debug(player.groupdict())
308 (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
309 if oldcards:
310 oldcards = [c.strip() for c in oldcards.split(' ')]
311 if newcards:
312 newcards = [c.strip() for c in newcards.split(' ')]
313 if street=='ANTES':
314 return
315 elif street=='THIRD':
316 # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
317 # hero: [xx][o]
318 # others: [o]
319 hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
320 elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
321 # 4th:
322 # hero: [xxo] [o]
323 # others: [o] [o]
324 # 5th:
325 # hero: [xxoo] [o]
326 # others: [oo] [o]
327 # 6th:
328 # hero: [xxooo] [o]
329 # others: [ooo] [o]
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:
333 # hero: [xxoooo] [x]
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])
339 for action in m:
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') )
356 else:
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):
362 showdownCards = []
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'))
372 if potcoll > 0:
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'))