If you only have games of a single limit type (fixed, pot, or no limit), but of more...
[fpdb-dooglus.git] / pyfpdb / PartyPokerToFpdb.py
blobc1f33295daafd7739062266e3487b1ee2a359c8f
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright 2009-2010, Grigorij Indigirkin
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 from collections import defaultdict
27 from Configuration import LOCALE_ENCODING
28 from Exceptions import FpdbParseError
29 from HandHistoryConverter import *
31 # PartyPoker HH Format
33 class FpdbParseError(FpdbParseError):
34 "Usage: raise FpdbParseError(<msg>[, hh=<hh>][, hid=<hid>])"
36 def __init__(self, msg='', hh=None, hid=None):
37 return super(FpdbParseError, self).__init__(msg, hid=hid)
39 def wrapHh(self, hh):
40 return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
41 {'DELIMETER': '#'*50, 'HH': hh}
43 class PartyPoker(HandHistoryConverter):
44 sitename = "PartyPoker"
45 codepage = "utf8"
46 siteId = 9
47 filetype = "text"
48 sym = {'USD': "\$", 'EUR': u"\u20ac", 'T$': ""}
49 currencies = {"\$": "USD", "$": "USD", u"\xe2\x82\xac": "EUR", u"\u20ac": "EUR", '': "T$"}
50 substitutions = {
51 'LEGAL_ISO' : "USD|EUR", # legal ISO currency codes
52 'LS' : "\$|\u20AC|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8)
55 # Static regexes
56 # $5 USD NL Texas Hold'em - Saturday, July 25, 07:53:52 EDT 2009
57 # NL Texas Hold'em $1 USD Buy-in Trny:45685440 Level:8 Blinds-Antes(600/1 200 -50) - Sunday, May 17, 11:25:07 MSKS 2009
58 re_GameInfoRing = re.compile(u"""
59 (?P<CURRENCY>[%(LS)s])\s*(?P<RINGLIMIT>[.,0-9]+)([.,0-9/$]+)?\s*(?:%(LEGAL_ISO)s)?\s*
60 (?P<LIMIT>(NL|PL|))\s*
61 (?P<GAME>(Texas\ Hold\'em|Omaha|7\ Card\ Stud\ Hi-Lo))
62 \s*\-\s*
63 (?P<DATETIME>.+)
64 """ % substitutions, re.VERBOSE | re.UNICODE)
65 re_GameInfoTrny = re.compile("""
66 (?P<LIMIT>(NL|PL|))\s*
67 (?P<GAME>(Texas\ Hold\'em|Omaha))\s+
68 (?:(?P<BUYIN>\$?[.,0-9]+)\s*(?P<BUYIN_CURRENCY>%(LEGAL_ISO)s)?\s*Buy-in\s+)?
69 Trny:\s?(?P<TOURNO>\d+)\s+
70 Level:\s*(?P<LEVEL>\d+)\s+
71 ((Blinds|Stakes)(?:-Antes)?)\(
72 (?P<SB>[.,0-9 ]+)\s*
73 /(?P<BB>[.,0-9 ]+)
74 (?:\s*-\s*(?P<ANTE>[.,0-9 ]+)\$?)?
76 \s*\-\s*
77 (?P<DATETIME>.+)
78 """ % substitutions, re.VERBOSE | re.UNICODE)
79 re_Hid = re.compile("Game \#(?P<HID>\d+) starts.")
81 re_PlayerInfo = re.compile(u"""
82 Seat\s(?P<SEAT>\d+):\s
83 (?P<PNAME>.*)\s
84 \(\s*[%(LS)s]?(?P<CASH>[0-9,.]+)\s*(?:%(LEGAL_ISO)s|)\s*\)
85 """ % substitutions, re.VERBOSE| re.UNICODE)
87 re_HandInfo = re.compile("""
88 ^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)?\s+
89 (?: \#|\(|)(?P<TABLE>\d+)\)?\s+
90 (?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
91 (\(No\sDP\)\s)?
92 \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
93 Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
94 \s+Total\s+number\s+of\s+players\s+\:\s+(?P<PLYRS>\d+)/?(?P<MAX>\d+)?
95 """,
96 re.VERBOSE|re.MULTILINE|re.DOTALL)
98 re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
99 re_SplitHands = re.compile('\x00+')
100 re_TailSplitHands = re.compile('(\x00+)')
101 lineSplitter = '\n'
102 re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE)
103 re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
104 re_NoSmallBlind = re.compile(
105 '^There is no Small Blind in this hand as the Big Blind '
106 'of the previous hand left the table', re.MULTILINE)
107 re_20BBmin = re.compile(r"Table 20BB Min")
109 def allHandsAsList(self):
110 list = HandHistoryConverter.allHandsAsList(self)
111 if list is None:
112 return []
113 return filter(lambda text: len(text.strip()), list)
115 def guessMaxSeats(self, hand):
116 """Return a guess at max_seats when not specified in HH."""
117 mo = self.maxOccSeat(hand)
118 if mo == 10: return mo
119 if mo == 2: return 2
120 if mo <= 6: return 6
121 # there are 9-max tables for cash and 10-max for tournaments
122 return 9 if hand.gametype['type']=='ring' else 10
124 def compilePlayerRegexs(self, hand):
125 players = set([player[1] for player in hand.players])
126 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
128 self.compiledPlayers = players
129 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
130 subst = {'PLYR': player_re, 'CUR_SYM': self.sym[hand.gametype['currency']],
131 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
132 self.re_PostSB = re.compile(
133 r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\."
134 % subst, re.MULTILINE)
135 self.re_PostBB = re.compile(
136 u"%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\."
137 % subst, re.MULTILINE)
138 self.re_PostDead = re.compile(
139 r"^%(PLYR)s posts big blind + dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
140 re.MULTILINE)
141 self.re_Antes = re.compile(
142 r"^%(PLYR)s posts ante \[%(CUR_SYM)s(?P<ANTE>[.,0-9]+) ?%(CUR)s\]" % subst,
143 re.MULTILINE)
144 self.re_HeroCards = re.compile(
145 r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
146 re.MULTILINE)
147 self.re_Action = re.compile(u"""
148 ^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In)
149 (?:\s+\[%(CUR_SYM)s(?P<BET>[.,\d]+)\s*%(CUR)s\])?
150 """ % subst, re.MULTILINE|re.VERBOSE)
151 self.re_ShownCards = re.compile(
152 r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
153 r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
154 re.MULTILINE)
155 self.re_CollectPot = re.compile(
156 r"""^%(PLYR)s \s+ wins \s+
157 %(CUR_SYM)s(?P<POT>[.,\d]+)\s*%(CUR)s""" % subst,
158 re.MULTILINE|re.VERBOSE)
160 def readSupportedGames(self):
161 return [["ring", "hold", "nl"],
162 ["ring", "hold", "pl"],
163 ["ring", "hold", "fl"],
165 ["tour", "hold", "nl"],
166 ["tour", "hold", "pl"],
167 ["tour", "hold", "fl"],
170 def _getGameType(self, handText):
171 if not hasattr(self, '_gameType'):
172 self._gameType = None
173 if self._gameType is None:
174 # let's determine whether hand is trny
175 # and whether 5-th line contains head line
176 headLine = handText.split(self.lineSplitter)[4]
177 for headLineContainer in headLine, handText:
178 for regexp in self.re_GameInfoTrny, self.re_GameInfoRing:
179 m = regexp.search(headLineContainer)
180 if m is not None:
181 self._gameType = m
182 return self._gameType
183 return self._gameType
185 def determineGameType(self, handText):
186 """inspect the handText and return the gametype dict
188 gametype dict is:
189 {'limitType': xxx, 'base': xxx, 'category': xxx}"""
191 info = {}
192 m = self._getGameType(handText)
193 m_20BBmin = self.re_20BBmin.search(handText)
194 if m is None:
195 tmp = handText[0:100]
196 log.error(_("determineGameType: Unable to recognise gametype from: '%s'") % tmp)
197 log.error(_("determineGameType: Raising FpdbParseError"))
198 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp)
199 return None
201 mg = m.groupdict()
202 # translations from captured groups to fpdb info strings
203 limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
204 games = { # base, category
205 "Texas Hold'em" : ('hold','holdem'),
206 'Omaha' : ('hold','omahahi'),
207 "7 Card Stud Hi-Lo" : ('stud','studhi'),
210 for expectedField in ['LIMIT', 'GAME']:
211 if mg[expectedField] is None:
212 raise FpdbParseError(_("Cannot fetch field '%s'") % expectedField)
213 try:
214 info['limitType'] = limits[mg['LIMIT'].strip()]
215 except:
216 raise FpdbParseError(_("Unknown limit '%s'") % mg['LIMIT'])
218 try:
219 (info['base'], info['category']) = games[mg['GAME']]
220 except:
221 raise FpdbParseError(_("Unknown game type '%s'") % mg['GAME'])
223 if 'TOURNO' in mg:
224 info['type'] = 'tour'
225 else:
226 info['type'] = 'ring'
228 if info['type'] == 'ring':
229 if m_20BBmin is None:
230 bb = float(mg['RINGLIMIT'])/100.0
231 else:
232 bb = float(mg['RINGLIMIT'])/40.0
234 if bb == 0.25:
235 sb = 0.10
236 else:
237 sb = bb/2.0
239 info['bb'] = "%.2f" % (bb)
240 info['sb'] = "%.2f" % (sb)
241 info['currency'] = self.currencies[mg['CURRENCY']]
242 else:
243 info['sb'] = self.clearMoneyString(mg['SB'])
244 info['bb'] = self.clearMoneyString(mg['BB'])
245 info['currency'] = 'T$'
247 return info
250 def readHandInfo(self, hand):
251 info = {}
252 try:
253 info.update(self.re_Hid.search(hand.handText).groupdict())
254 except AttributeError, e:
255 raise FpdbParseError(_("Cannot read HID for current hand: %s" % e))
257 try:
258 info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
259 except:
260 raise FpdbParseError(_("Cannot read Handinfo for current hand"), hid = info['HID'])
262 try:
263 info.update(self._getGameType(hand.handText).groupdict())
264 except:
265 raise FpdbParseError(_("Cannot read GameType for current hand"), hid = info['HID'])
268 m = self.re_CountedSeats.search(hand.handText)
269 if m: info.update(m.groupdict())
272 # FIXME: it's dirty hack
273 # party doesnt subtract uncalled money from commited money
274 # so hand.totalPot calculation has to be redefined
275 from Hand import Pot, HoldemOmahaHand
276 def getNewTotalPot(origTotalPot):
277 def totalPot(self):
278 if self.totalpot is None:
279 self.pot.end()
280 self.totalpot = self.pot.total
281 for i,v in enumerate(self.collected):
282 if v[0] in self.pot.returned:
283 self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
284 self.collectees[v[0]] -= self.pot.returned[v[0]]
285 self.pot.returned[v[0]] = 0
286 return origTotalPot()
287 return totalPot
288 instancemethod = type(hand.totalPot)
289 hand.totalPot = instancemethod(getNewTotalPot(hand.totalPot), hand, HoldemOmahaHand)
293 log.debug("readHandInfo: %s" % info)
294 for key in info:
295 if key == 'DATETIME':
296 #Saturday, July 25, 07:53:52 EDT 2009
297 #Thursday, July 30, 21:40:41 MSKS 2009
298 #Sunday, October 25, 13:39:07 MSK 2009
299 m2 = re.search(
300 r"\w+,\s+(?P<M>\w+)\s+(?P<D>\d+),\s+(?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+)\s+(?P<TZ>[A-Z]+)\s+(?P<Y>\d+)",
301 info[key],
302 re.UNICODE
304 months = ['January', 'February', 'March', 'April','May', 'June',
305 'July','August','September','October','November','December']
306 if m2.group('M') not in months:
307 raise FpdbParseError("Only english hh is supported", hid=info["HID"])
308 month = months.index(m2.group('M')) + 1
309 datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
310 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
311 # FIXME: some timezone correction required
312 #tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
313 #hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
315 if key == 'HID':
316 hand.handid = info[key]
317 if key == 'TABLE':
318 hand.tablename = info[key]
319 if key == 'MTTTABLE':
320 if info[key] != None:
321 hand.tablename = info[key]
322 hand.tourNo = info['TABLE']
323 if key == 'BUTTON':
324 hand.buttonpos = info[key]
325 if key == 'TOURNO':
326 hand.tourNo = info[key]
327 if key == 'TABLE_ID_WRAPPER':
328 if info[key] == '#':
329 # FIXME: there is no such property in Hand class
330 self.isSNG = True
331 if key == 'BUYIN':
332 # FIXME: it's dirty hack T_T
333 # code below assumes that tournament rake is equal to zero
334 if info[key] == None:
335 hand.buyin = '$0+$0'
336 else:
337 cur = info[key][0] if info[key][0] not in '0123456789' else ''
338 hand.buyin = info[key] + '+%s0' % cur
339 if key == 'LEVEL':
340 hand.level = info[key]
341 if key == 'PLAY' and info['PLAY'] != 'Real':
342 # if realy party doesn's save play money hh
343 hand.gametype['currency'] = 'play'
344 if key == 'MAX' and info[key] is not None:
345 hand.maxseats = int(info[key])
348 def readButton(self, hand):
349 m = self.re_Button.search(hand.handText)
350 if m:
351 hand.buttonpos = int(m.group('BUTTON'))
352 else:
353 log.info(_('readButton: not found'))
355 def readPlayerStacks(self, hand):
356 log.debug("readPlayerStacks")
357 m = self.re_PlayerInfo.finditer(hand.handText)
358 maxKnownStack = 0
359 zeroStackPlayers = []
360 for a in m:
361 if a.group('CASH') > '0':
362 #record max known stack for use with players with unknown stack
363 maxKnownStack = max(a.group('CASH'),maxKnownStack)
364 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), self.clearMoneyString(a.group('CASH')))
365 else:
366 #zero stacked players are added later
367 zeroStackPlayers.append([int(a.group('SEAT')), a.group('PNAME'), self.clearMoneyString(a.group('CASH'))])
368 if hand.gametype['type'] == 'ring':
369 #finds first vacant seat after an exact seat
370 def findFirstEmptySeat(startSeat):
371 while startSeat in occupiedSeats:
372 if startSeat >= hand.maxseats:
373 startSeat = 0
374 startSeat += 1
375 return startSeat
377 re_JoiningPlayers = re.compile(r"(?P<PLAYERNAME>.*) has joined the table")
378 re_BBPostingPlayers = re.compile(r"(?P<PLAYERNAME>.*) posts big blind")
380 match_JoiningPlayers = re_JoiningPlayers.findall(hand.handText)
381 match_BBPostingPlayers = re_BBPostingPlayers.findall(hand.handText)
383 #add every player with zero stack, but:
384 #if a zero stacked player is just joined the table in this very hand then set his stack to maxKnownStack
385 for p in zeroStackPlayers:
386 if p[1] in match_JoiningPlayers:
387 p[2] = self.clearMoneyString(maxKnownStack)
388 hand.addPlayer(p[0],p[1],p[2])
390 seatedPlayers = list([(f[1]) for f in hand.players])
392 #it works for all known cases as of 2010-09-28
393 #should be refined with using match_ActivePlayers instead of match_BBPostingPlayers
394 #as a leaving and rejoining player could be active without posting a BB (sample HH needed)
395 unseatedActivePlayers = list(set(match_BBPostingPlayers) - set(seatedPlayers))
397 if unseatedActivePlayers:
398 for player in unseatedActivePlayers:
399 previousBBPoster = match_BBPostingPlayers[match_BBPostingPlayers.index(player)-1]
400 previousBBPosterSeat = dict([(f[1], f[0]) for f in hand.players])[previousBBPoster]
401 occupiedSeats = list([(f[0]) for f in hand.players])
402 occupiedSeats.sort()
403 newPlayerSeat = findFirstEmptySeat(previousBBPosterSeat)
404 hand.addPlayer(newPlayerSeat,player,self.clearMoneyString(maxKnownStack))
406 def markStreets(self, hand):
407 m = re.search(
408 r"\*{2} Dealing down cards \*{2}"
409 r"(?P<PREFLOP>.+?)"
410 r"(?:\*{2} Dealing Flop \*{2} (?P<FLOP>\[ \S\S, \S\S, \S\S \].+?))?"
411 r"(?:\*{2} Dealing Turn \*{2} (?P<TURN>\[ \S\S \].+?))?"
412 r"(?:\*{2} Dealing River \*{2} (?P<RIVER>\[ \S\S \].+?))?$"
413 , hand.handText,re.DOTALL)
414 hand.addStreets(m)
416 def readCommunityCards(self, hand, street):
417 if street in ('FLOP','TURN','RIVER'):
418 m = self.re_Board.search(hand.streets[street])
419 hand.setCommunityCards(street, renderCards(m.group('CARDS')))
421 def readAntes(self, hand):
422 log.debug("reading antes")
423 m = self.re_Antes.finditer(hand.handText)
424 for player in m:
425 hand.addAnte(player.group('PNAME'), player.group('ANTE'))
427 def readBlinds(self, hand):
428 noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText))
429 if hand.gametype['type'] == 'ring':
430 try:
431 assert noSmallBlind==False
432 liveBlind = True
433 for m in self.re_PostSB.finditer(hand.handText):
434 if liveBlind:
435 hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
436 liveBlind = False
437 else:
438 # Post dead blinds as ante
439 hand.addBlind(m.group('PNAME'), 'secondsb', m.group('SB'))
440 except: # no small blind
441 hand.addBlind(None, None, None)
443 for a in self.re_PostBB.finditer(hand.handText):
444 hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
446 deadFilter = lambda s: s.replace(',', '.')
447 for a in self.re_PostDead.finditer(hand.handText):
448 hand.addBlind(a.group('PNAME'), 'both', deadFilter(a.group('BBNDEAD')))
449 else:
450 # party doesn't track blinds for tournaments
451 # so there're some cra^Wcaclulations
452 if hand.buttonpos == 0:
453 self.readButton(hand)
454 # NOTE: code below depends on Hand's implementation
455 # playersMap - dict {seat: (pname,stack)}
456 playersMap = dict([(f[0], f[1:3]) for f in hand.players])
457 maxSeat = max(playersMap)
459 def findFirstNonEmptySeat(startSeat):
460 while startSeat not in playersMap:
461 if startSeat >= maxSeat:
462 startSeat = 0
463 startSeat += 1
464 return startSeat
465 smartMin = lambda A,B: A if float(A) <= float(B) else B
467 if noSmallBlind:
468 hand.addBlind(None, None, None)
469 smallBlindSeat = int(hand.buttonpos)
470 else:
471 smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1)
472 blind = smartMin(hand.sb, playersMap[smallBlindSeat][1])
473 hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind)
475 bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 1)
476 blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
477 hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
479 def readHeroCards(self, hand):
480 # we need to grab hero's cards
481 for street in ('PREFLOP',):
482 if street in hand.streets.keys():
483 m = self.re_HeroCards.finditer(hand.streets[street])
484 for found in m:
485 hand.hero = found.group('PNAME')
486 newcards = renderCards(found.group('NEWCARDS'))
487 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
489 def readAction(self, hand, street):
490 m = self.re_Action.finditer(hand.streets[street])
491 for action in m:
492 acts = action.groupdict()
493 playerName = action.group('PNAME')
494 amount = self.clearMoneyString(action.group('BET')) if action.group('BET') else None
495 actionType = action.group('ATYPE')
497 if actionType == 'is all-In':
498 # party's allin can mean either raise or bet or call
499 Bp = hand.lastBet[street]
500 if Bp == 0:
501 actionType = 'bets'
502 elif Bp < Decimal(amount):
503 actionType = 'raises'
504 else:
505 actionType = 'calls'
507 if actionType == 'raises':
508 if street == 'PREFLOP' and \
509 playerName in [item[0] for item in hand.actions['BLINDSANTES'] if item[2]!='ante']:
510 # preflop raise from blind
511 hand.addCallandRaise( street, playerName, amount )
512 else:
513 hand.addCallandRaise( street, playerName, amount )
514 elif actionType == 'calls':
515 hand.addCall( street, playerName, amount )
516 elif actionType == 'bets':
517 hand.addBet( street, playerName, amount )
518 elif actionType == 'folds':
519 hand.addFold( street, playerName )
520 elif actionType == 'checks':
521 hand.addCheck( street, playerName )
522 else:
523 raise FpdbParseError(
524 _("Unimplemented readAction: '%s' '%s'") % (playerName,actionType,),
525 hid = hand.hid, )
527 def readShowdownActions(self, hand):
528 # all action in readShownCards
529 pass
531 def readCollectPot(self,hand):
532 for m in self.re_CollectPot.finditer(hand.handText):
533 hand.addCollectPot(player=m.group('PNAME'),pot=self.clearMoneyString(m.group('POT')))
535 def readShownCards(self,hand):
536 for m in self.re_ShownCards.finditer(hand.handText):
537 if m.group('CARDS') is not None:
538 cards = renderCards(m.group('CARDS'))
540 mucked = m.group('SHOWED') != "show"
542 hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked)
544 @staticmethod
545 def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
546 "Returns string to search in windows titles"
547 if type=="tour":
548 TableName = table_name.split(" ")
549 print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number)
550 if len(TableName[1]) > 6:
551 return "#%s" % (table_number)
552 else:
553 return "%s.+Table\s#%s" % (TableName[0], table_number)
554 else:
555 return table_name
557 def renderCards(string):
558 "Splits strings like ' Js, 4d '"
559 cards = string.strip().split(' ')
560 return filter(len, map(lambda x: x.strip(' ,'), cards))
563 if __name__ == "__main__":
564 parser = OptionParser()
565 parser.add_option("-i", "--input", dest="ipath", help=_("parse input hand history"))
566 parser.add_option("-o", "--output", dest="opath", help=_("output translation to"), default="-")
567 parser.add_option("-f", "--follow", dest="follow", help=_("follow (tail -f) the input"), action="store_true", default=False)
568 parser.add_option("-q", "--quiet",
569 action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
570 parser.add_option("-v", "--verbose",
571 action="store_const", const=logging.INFO, dest="verbosity")
572 parser.add_option("--vv",
573 action="store_const", const=logging.DEBUG, dest="verbosity")
575 (options, args) = parser.parse_args()
577 e = PartyPoker(in_path = options.ipath, out_path = options.opath, follow = options.follow)