Improve session viewer. There were some off-by-one errors.
[fpdb-dooglus.git] / pyfpdb / Win2dayToFpdb.py
blob70cb59f820a6e5c3feb455a6bb5ff0470eb7b940
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 # Win2day HH Format
30 class Win2day(HandHistoryConverter):
32 sitename = "Win2day"
33 filetype = "text"
34 codepage = "utf-8"
35 siteId = 4
37 # Static regexes
38 re_GameInfo = re.compile("""<HISTORY\sID="(?P<HID>[0-9]+)"\sSESSION="session[0-9]+\.xml"\s
39 TABLE="(?P<TABLE>[-\sa-zA-Z0-9\xc0-\xfc/.]+)"\s
40 GAME="(?P<GAME>[_A-Z]+)"\sGAMETYPE="[_a-zA-Z]+"\sGAMEKIND="[_a-zA-Z]+"\s
41 TABLECURRENCY="(?P<CURRENCY>[A-Z]+)"\s
42 LIMIT="(?P<LIMIT>NL|PL)"\s
43 STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)"\s
44 DATE="(?P<DATETIME>[0-9]+)"\s
45 (TABLETOURNEYID=""\s)?
46 WIN="[.0-9]+"\sLOSS="[.0-9]+"
47 """, re.MULTILINE| re.VERBOSE)
48 re_SplitHands = re.compile('</HISTORY>')
49 re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
50 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)
51 #<PLAYER NAME="prato" SEAT="1" AMOUNT="61.29"></PLAYER>
52 re_PlayerInfo = re.compile('^<PLAYER NAME="(?P<PNAME>.*)" SEAT="(?P<SEAT>[0-9]+)" AMOUNT="(?P<CASH>[.0-9]+)"></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 def compilePlayerRegexs(self, hand):
58 players = set([player[1] for player in hand.players])
59 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
60 # we need to recompile the player regexs.
61 self.compiledPlayers = players
62 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
63 logging.debug("player_re: " + player_re)
64 #<ACTION TYPE="HAND_BLINDS" PLAYER="prato" KIND="HAND_SB" VALUE="0.25"></ACTION>
66 self.re_PostSB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_SB" VALUE="(?P<SB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
67 self.re_PostBB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_BB" VALUE="(?P<BB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
68 self.re_Antes = re.compile(r"^%s: posts the ante \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
69 self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
70 self.re_PostBoth = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
72 #r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n<CARD LINK="(?P<CARD1>[0-9]+)"></CARD>\n<CARD LINK="(?P<CARD2>[0-9]+)"></CARD></ACTION>'
73 self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</ACTION>' % player_re, re.MULTILINE)
75 #'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
76 self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)
78 self.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)
79 #<RESULT PLAYER="wig0r" WIN="4.10" HAND="$(STR_G_WIN_TWOPAIR) $(STR_G_CARDS_TENS) $(STR_G_ANDTEXT) $(STR_G_CARDS_EIGHTS)">
81 self.re_CollectPot = re.compile(r'<RESULT PLAYER="%s" WIN="(?P<POT>[.\d]+)" HAND=".+">' % player_re, re.MULTILINE)
82 self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
83 self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
86 def readSupportedGames(self):
87 return [["ring", "hold", "nl"],
88 ["ring", "hold", "pl"],
89 ["ring", "hold", "fl"],
90 ["ring", "stud", "fl"],
91 ["ring", "draw", "fl"],
92 ["ring", "omaha", "pl"]
95 def determineGameType(self, handText):
96 info = {'type':'ring'}
98 m = self.re_GameInfo.search(handText)
99 if not m:
100 tmp = handText[0:1000]
101 log.error(_("Unable to recognise gametype from: '%s'") % tmp)
102 log.error("determineGameType: " + _("Raising FpdbParseError"))
103 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp)
105 mg = m.groupdict()
106 #print "DEBUG: mg: %s" % mg
108 # translations from captured groups to our info strings
109 #limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' }
110 limits = { 'NL':'nl', 'PL':'pl'}
111 games = { # base, category
112 "GAME_THM" : ('hold','holdem'),
113 "GAME_OMA" : ('hold','omahahi'),
115 #'Omaha Hi/Lo' : ('hold','omahahilo'),
116 # 'Razz' : ('stud','razz'),
117 #'7 Card Stud' : ('stud','studhi'),
118 # 'Badugi' : ('draw','badugi')
120 if 'LIMIT' in mg:
121 info['limitType'] = limits[mg['LIMIT']]
122 if 'GAME' in mg:
123 (info['base'], info['category']) = games[mg['GAME']]
124 if 'SB' in mg:
125 info['sb'] = mg['SB']
126 if 'BB' in mg:
127 info['bb'] = mg['BB']
128 if 'CURRENCY' in mg:
129 info['currency'] = mg['CURRENCY']
130 # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
132 return info
135 def readHandInfo(self, hand):
136 info = {}
137 m = self.re_HandInfo.search(hand.handText,re.DOTALL)
138 if m:
139 info.update(m.groupdict())
140 # TODO: Be less lazy and parse maxseats from the HandInfo regex
141 if m.group('TABLEATTRIBUTES'):
142 m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES'))
143 hand.maxseats = int(m2.group(1))
144 m = self.re_GameInfo.search(hand.handText)
145 if m: info.update(m.groupdict())
146 m = self.re_Button.search(hand.handText)
147 if m: info.update(m.groupdict())
148 # TODO : I rather like the idea of just having this dict as hand.info
149 logging.debug("readHandInfo: %s" % info)
150 for key in info:
151 if key == 'DATETIME':
152 # Win2day uses UTC timestamp
153 hand.startTime = datetime.datetime.fromtimestamp(int(info[key]))
154 if key == 'HID':
155 hand.handid = info[key]
156 if key == 'TABLE':
157 hand.tablename = info[key]
158 if key == 'BUTTON':
159 hand.buttonpos = info[key]
161 def readButton(self, hand):
162 m = self.re_Button.search(hand.handText)
163 if m:
164 for player in hand.players:
165 if player[1] == m.group('BUTTON'):
166 hand.buttonpos = player[0]
167 break
168 else:
169 logging.info(_('readButton: not found'))
171 def readPlayerStacks(self, hand):
172 logging.debug("readPlayerStacks")
173 m = self.re_PlayerInfo.finditer(hand.handText)
174 players = []
175 for a in m:
176 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
178 def markStreets(self, hand):
179 # PREFLOP = ** Dealing down cards **
180 # This re fails if, say, river is missing; then we don't get the ** that starts the river.
181 if hand.gametype['base'] in ("hold"):
182 #m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
183 # r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
184 # r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
185 # r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
187 m = re.search('<ACTION TYPE="HAND_BLINDS" PLAYER=".+" KIND="HAND_BB" VALUE="[.0-9]+"></ACTION>(?P<PREFLOP>.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP")|.+)'
188 '((?P<FLOP><ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN")|.+))?'
189 '((?P<TURN><ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER")|.+))?'
190 '((?P<RIVER><ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER" POT="[.0-9]+">.+(?=<SHOWDOWN NAME="HAND_SHOWDOWN")|.+))?', hand.handText,re.DOTALL)
192 hand.addStreets(m)
194 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
195 if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
196 #print "DEBUG readCommunityCards:", street, hand.streets.group(street)
198 boardCards = []
199 if street == 'FLOP':
200 m = self.re_Card.findall(hand.streets[street])
201 for card in m:
202 boardCards.append(self.convertWin2dayCards(card))
203 else:
204 m = self.re_BoardLast.search(hand.streets[street])
205 boardCards.append(self.convertWin2dayCards(m.group('CARD')))
207 hand.setCommunityCards(street, boardCards)
209 def readAntes(self, hand):
210 logging.debug(_("reading antes"))
211 m = self.re_Antes.finditer(hand.handText)
212 for player in m:
213 #~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
214 hand.addAnte(player.group('PNAME'), player.group('ANTE'))
216 def readBringIn(self, hand):
217 m = self.re_BringIn.search(hand.handText,re.DOTALL)
218 if m:
219 #~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
220 hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
222 def readBlinds(self, hand):
223 try:
224 m = self.re_PostSB.search(hand.handText)
225 hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
226 except: # no small blind
227 hand.addBlind(None, None, None)
228 for a in self.re_PostBB.finditer(hand.handText):
229 hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
230 for a in self.re_PostBoth.finditer(hand.handText):
231 hand.addBlind(a.group('PNAME'), 'both', a.group('SBBB'))
233 def readHeroCards(self, hand):
234 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
235 # we need to grab hero's cards
236 m = self.re_HeroCards.finditer(hand.streets['PREFLOP'])
237 newcards = []
238 for found in m:
239 hand.hero = found.group('PNAME')
240 for card in self.re_Card.finditer(found.group('CARDS')):
241 #print self.convertWin2dayCards(card.group('CARD'))
242 newcards.append(self.convertWin2dayCards(card.group('CARD')))
244 #hand.addHoleCards(holeCards, m.group('PNAME'))
245 hand.addHoleCards('PREFLOP', hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
247 def convertWin2dayCards(self, card):
248 card = int(card)
249 retCard = ''
250 cardconvert = { 1:'A',
251 10:'T',
252 11:'J',
253 12:'Q',
254 13:'K'}
255 realNumber = card % 13 + 1
256 if(realNumber in cardconvert):
257 retCard += cardconvert[realNumber]
258 else:
259 retCard += str(realNumber)
261 if(card > 38):
262 retCard += 's'
263 elif(card > 25):
264 retCard += 'h'
265 elif(card > 12):
266 retCard += 'c'
267 else:
268 retCard += 'd'
270 return(retCard)
272 def readDrawCards(self, hand, street):
273 logging.debug("readDrawCards")
274 m = self.re_HeroCards.finditer(hand.streets[street])
275 if m == None:
276 hand.involved = False
277 else:
278 for player in m:
279 hand.hero = player.group('PNAME') # Only really need to do this once
280 newcards = player.group('NEWCARDS')
281 oldcards = player.group('OLDCARDS')
282 if newcards == None:
283 newcards = []
284 else:
285 newcards = newcards.split(' ')
286 if oldcards == None:
287 oldcards = []
288 else:
289 oldcards = oldcards.split(' ')
290 hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
293 def readStudPlayerCards(self, hand, street):
294 # See comments of reference implementation in FullTiltToFpdb.py
295 # logging.debug("readStudPlayerCards")
296 m = self.re_HeroCards.finditer(hand.streets[street])
297 for player in m:
298 #~ logging.debug(player.groupdict())
299 (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
300 if oldcards:
301 oldcards = [c.strip() for c in oldcards.split(' ')]
302 if newcards:
303 newcards = [c.strip() for c in newcards.split(' ')]
304 if street=='ANTES':
305 return
306 elif street=='THIRD':
307 # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
308 # hero: [xx][o]
309 # others: [o]
310 hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
311 elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
312 # 4th:
313 # hero: [xxo] [o]
314 # others: [o] [o]
315 # 5th:
316 # hero: [xxoo] [o]
317 # others: [oo] [o]
318 # 6th:
319 # hero: [xxooo] [o]
320 # others: [ooo] [o]
321 hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
322 # we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
323 elif street=='SEVENTH' and newcards:
324 # hero: [xxoooo] [x]
325 # others: not reported.
326 hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
328 def readAction(self, hand, street):
329 m = self.re_Action.finditer(hand.streets[street])
330 for action in m:
331 if action.group('ATYPE') == 'ACTION_RAISE':
332 hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
333 elif action.group('ATYPE') == 'ACTION_CALL':
334 hand.addCall( street, action.group('PNAME'), action.group('BET') )
335 elif action.group('ATYPE') == 'ACTION_ALLIN':
336 hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
337 elif action.group('ATYPE') == 'ACTION_BET':
338 hand.addBet( street, action.group('PNAME'), action.group('BET') )
339 elif action.group('ATYPE') == 'ACTION_FOLD':
340 hand.addFold( street, action.group('PNAME'))
341 elif action.group('ATYPE') == 'ACTION_CHECK':
342 hand.addCheck( street, action.group('PNAME'))
343 elif action.group('ATYPE') == 'ACTION_DISCARD':
344 hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED'))
345 elif action.group('ATYPE') == 'ACTION_STAND':
346 hand.addStandsPat( street, action.group('PNAME'))
347 else:
348 print (_("DEBUG:") + _("Unimplemented %s: '%s' '%s'") % ("readAction", action.group('PNAME'), action.group('ATYPE')))
351 def readShowdownActions(self, hand):
352 for shows in self.re_ShowdownAction.finditer(hand.handText):
353 showdownCards = []
354 for card in self.re_Card.finditer(shows.group('CARDS')):
355 #print "DEBUG:", card, card.group('CARD'), self.convertWin2dayCards(card.group('CARD'))
356 showdownCards.append(self.convertWin2dayCards(card.group('CARD')))
358 hand.addShownCards(showdownCards, shows.group('PNAME'))
360 def readCollectPot(self,hand):
361 for m in self.re_CollectPot.finditer(hand.handText):
362 potcoll = Decimal(m.group('POT'))
363 if potcoll > 0:
364 hand.addCollectPot(player=m.group('PNAME'),pot=potcoll)
366 def readShownCards(self,hand):
367 for m in self.re_ShownCards.finditer(hand.handText):
368 if m.group('CARDS') is not None:
369 cards = m.group('CARDS')
370 cards = cards.split(' ')
371 hand.addShownCards(cards=cards, player=m.group('PNAME'))