Expanded my entry in contributors.txt.
[fpdb-dooglus.git] / pyfpdb / PartyPokerToFpdb.py
blob1e643f34ad1493ba21375225f08a890b467bfc45
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # Copyright 2009-2011, 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 PartyPoker(HandHistoryConverter):
34 sitename = "PartyPoker"
35 codepage = "utf8"
36 siteId = 9
37 filetype = "text"
38 sym = {'USD': "\$", 'EUR': u"\u20ac", 'T$': ""}
39 currencies = {"\$": "USD", "$": "USD", u"\xe2\x82\xac": "EUR", u"\u20ac": "EUR", '': "T$"}
40 substitutions = {
41 'LEGAL_ISO' : "USD|EUR", # legal ISO currency codes
42 'LS' : u"\$|\u20ac|\xe2\x82\xac|", # Currency symbols - Euro(cp1252, utf-8)
43 'NUM' : u".,\d",
45 limits = { 'NL':'nl', 'PL':'pl', '':'fl' }
46 games = { # base, category
47 "Texas Hold'em" : ('hold','holdem'),
48 'Omaha' : ('hold','omahahi'),
49 "7 Card Stud Hi-Lo" : ('stud','studhilo'),
50 "7 Card Stud" : ('stud','studhi'),
53 Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.08': ('0.02', '0.04'),
54 '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
55 '0.30': ('0.07', '0.15'), '0.50': ('0.10', '0.25'),
56 '1.00': ('0.25', '0.50'), '1': ('0.25', '0.50'),
57 '2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'),
58 '4.00': ('1.00', '2.00'), '4': ('1.00', '2.00'),
59 '6.00': ('1.00', '3.00'), '6': ('1.00', '3.00'),
60 '10.00': ('2.00', '5.00'), '10': ('2.00', '5.00'),
61 '20.00': ('5.00', '10.00'), '20': ('5.00', '10.00'),
62 # Commented lines need verification
63 #'30.00': ('10.00', '15.00'), '30': ('10.00', '15.00'),
64 #'40.00': ('10.00', '20.00'), '40': ('10.00', '20.00'),
65 '60.00': ('15.00', '30.00'), '60': ('15.00', '30.00'),
66 #'100.00': ('25.00', '50.00'), '100': ('25.00', '50.00'),
67 #'200.00': ('50.00', '100.00'), '200': ('50.00', '100.00'),
68 #'500.00': ('??', '250.00'), '500': ('??', '250.00'),
70 NLim_Blinds_20bb = { '0.80': ('0.01', '0.02'),
71 '1.60': ('0.02', '0.04'),
72 '4': ('0.05', '0.10'),
73 '10': ('0.10', '0.25'),
74 '20': ('0.25', '0.50'),
75 '40': ('0.50', '1.00'),
76 #'10': ('0.10', '0.25'),
77 #'10': ('0.10', '0.25'),
78 #'10': ('0.10', '0.25'),
79 #'10': ('0.10', '0.25'),
80 #'10': ('0.10', '0.25'),
83 months = { 'January':1, 'Jan':1, 'February':2, 'Feb':2, 'March':3, 'Mar':3,
84 'April':4, 'Apr':4, 'May':5, 'May':5, 'June':6, 'Jun':6,
85 'July':7, 'Jul':7, 'August':8, 'Aug':8, 'September':9, 'Sep':9,
86 'October':10, 'Oct':10, 'November':11, 'Nov':11, 'December':12, 'Dec':12}
88 # Static regexes
89 re_GameInfo = re.compile(u"""
90 \*{5}\sHand\sHistory\s(F|f)or\sGame\s(?P<HID>\d+)\s\*{5}\s+
91 ((?P<CURRENCY>[%(LS)s]))?\s*
93 ([%(LS)s]?(?P<SB>[%(NUM)s]+)/[%(LS)s]?(?P<BB>[%(NUM)s]+)\s*(?:%(LEGAL_ISO)s)?\s*)|
94 ((?P<CASHBI>[%(NUM)s]+)\s(?:%(LEGAL_ISO)s)?\s*)(?P<LIMIT2>(NL|PL|))?\s*
96 (Tourney\s*)?
97 (?P<GAME>(Texas\sHold\'em|Omaha|7\sCard\sStud\sHi-Lo|7\sCard\sStud))\s*
98 (Game\sTable\s*)?
100 (\((?P<LIMIT>(NL|PL|))\)\s*)?
101 (\(STT\sTournament\s\#(?P<TOURNO>\d+)\)\s*)?
103 \s*-\s*
104 (?P<DATETIME>.+)
105 """ % substitutions, re.VERBOSE | re.UNICODE)
107 re_HandInfo = re.compile("""
108 ^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)?\s+
109 (?: \#|\(|)(?P<TABLE>\d+)\)?\s+
110 (?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
111 (\(No\sDP\)\s)?
112 \((?P<PLAY>Real|Play)\s+Money\)\s+(--\s*)? # FIXME: check if play money is correct
113 Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
114 \s+Total\s+number\s+of\s+players\s+\:\s+(?P<PLYRS>\d+)/?(?P<MAX>\d+)?
115 """, re.VERBOSE|re.MULTILINE|re.DOTALL)
117 re_GameInfoTrny = re.compile("""
118 \*{5}\sHand\sHistory\s(F|f)or\sGame\s(?P<HID>\d+)\s\*{5}\s+
119 (?P<LIMIT>(NL|PL|))\s*
120 (?P<GAME>(Texas\ Hold\'em|Omaha))\s+
121 (?:(?P<BUYIN>[%(LS)s]?[%(NUM)s]+)\s*(?P<BUYIN_CURRENCY>%(LEGAL_ISO)s)?\s*Buy-in\s+)?
122 Trny:\s?(?P<TOURNO>\d+)\s+
123 Level:\s*(?P<LEVEL>\d+)\s+
124 ((Blinds|Stakes)(?:-Antes)?)\(
125 (?P<SB>[%(NUM)s ]+)\s*
126 /(?P<BB>[%(NUM)s ]+)
127 (?:\s*-\s*(?P<ANTE>[%(NUM)s ]+)\$?)?
129 \s*\-\s*
130 (?P<DATETIME>.+)
131 """ % substitutions, re.VERBOSE | re.UNICODE)
133 re_PlayerInfo = re.compile(u"""
134 Seat\s(?P<SEAT>\d+):\s
135 (?P<PNAME>.*)\s
136 \(\s*[%(LS)s]?(?P<CASH>[%(NUM)s]+)\s*(?:%(LEGAL_ISO)s|)\s*\)
137 """ % substitutions, re.VERBOSE| re.UNICODE)
140 re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
141 re_SplitHands = re.compile('\x00+')
142 re_TailSplitHands = re.compile('(\x00+)')
143 lineSplitter = '\n'
144 re_Button = re.compile('Seat (?P<BUTTON>\d+) is the button', re.MULTILINE)
145 re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
146 re_NoSmallBlind = re.compile(
147 '^There is no Small Blind in this hand as the Big Blind '
148 'of the previous hand left the table', re.MULTILINE)
149 re_20BBmin = re.compile(r"Table 20BB Min")
151 def allHandsAsList(self):
152 list = HandHistoryConverter.allHandsAsList(self)
153 if list is None:
154 return []
155 return filter(lambda text: len(text.strip()), list)
157 def guessMaxSeats(self, hand):
158 """Return a guess at max_seats when not specified in HH."""
159 mo = self.maxOccSeat(hand)
160 if mo == 10: return mo
161 if mo == 2: return 2
162 if mo <= 6: return 6
163 # there are 9-max tables for cash and 10-max for tournaments
164 return 9 if hand.gametype['type']=='ring' else 10
166 def compilePlayerRegexs(self, hand):
167 players = set([player[1] for player in hand.players])
168 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
170 self.compiledPlayers = players
171 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
172 subst = {'PLYR': player_re, 'CUR_SYM': self.sym[hand.gametype['currency']],
173 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else '',
174 'BRAX' : u"\[\(\)\]"
176 self.re_PostSB = re.compile(
177 r"^%(PLYR)s posts small blind [%(BRAX)s]?%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s[%(BRAX)s]?\."
178 % subst, re.MULTILINE)
179 self.re_PostBB = re.compile(
180 u"%(PLYR)s posts big blind [%(BRAX)s]?%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s[%(BRAX)s]?\."
181 % subst, re.MULTILINE)
182 self.re_PostDead = re.compile(
183 r"^%(PLYR)s posts big blind + dead [%(BRAX)s]?(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s[%(BRAX)s]?\." % subst,
184 re.MULTILINE)
185 self.re_Antes = re.compile(
186 r"^%(PLYR)s posts ante [%(BRAX)s]?%(CUR_SYM)s(?P<ANTE>[.,0-9]+) ?%(CUR)s[%(BRAX)s]?" % subst,
187 re.MULTILINE)
188 self.re_HeroCards = re.compile(
189 r"^Dealt to %(PLYR)s \[\s*(?P<NEWCARDS>.+)\s*\]" % subst,
190 re.MULTILINE)
191 self.re_Action = re.compile(u"""
192 ^%(PLYR)s\s+(?P<ATYPE>bets|checks|raises|calls|folds|is\sall-In)
193 (?:\s+[%(BRAX)s]?%(CUR_SYM)s?(?P<BET>[.,\d]+)\s*(%(CUR)s)?[%(BRAX)s]?)?
194 """ % subst, re.MULTILINE|re.VERBOSE)
195 self.re_ShownCards = re.compile(
196 r"^%s (?P<SHOWED>(?:doesn\'t )?shows?) " % player_re +
197 r"\[ *(?P<CARDS>.+) *\](?P<COMBINATION>.+)\.",
198 re.MULTILINE)
199 self.re_CollectPot = re.compile(
200 r"""^%(PLYR)s \s+ wins \s+
201 %(CUR_SYM)s(?P<POT>[.,\d]+)\s*%(CUR)s""" % subst,
202 re.MULTILINE|re.VERBOSE)
204 def readSupportedGames(self):
205 return [["ring", "hold", "nl"],
206 ["ring", "hold", "pl"],
207 ["ring", "hold", "fl"],
209 ["ring", "stud", "fl"],
211 ["tour", "hold", "nl"],
212 ["tour", "hold", "pl"],
213 ["tour", "hold", "fl"],
216 def determineGameType(self, handText):
217 info = {}
218 m = self.re_GameInfo.search(handText)
219 if not m:
220 m = self.re_GameInfoTrny.search(handText)
221 if not m:
222 tmp = handText[0:150]
223 log.error(_("Unable to recognise gametype from: '%s'") % tmp)
224 log.error("determineGameType: " + _("Raising FpdbParseError"))
225 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp)
227 mg = m.groupdict()
228 #print "DEBUG: mg: %s" % mg
230 if 'LIMIT' in mg and mg['LIMIT'] != None:
231 info['limitType'] = self.limits[mg['LIMIT']]
232 if 'LIMIT2' in mg and mg['LIMIT2'] != None:
233 info['limitType'] = self.limits[mg['LIMIT2']]
234 if mg['LIMIT'] == None and mg['LIMIT2'] == None:
235 info['limitType'] = 'fl'
236 if 'GAME' in mg:
237 (info['base'], info['category']) = self.games[mg['GAME']]
238 if 'CASHBI' in mg and mg['CASHBI'] != None:
239 # The summary is using buyin rather then listing the blinds
240 # Only with NL games?
241 mg['CASHBI'] = self.clearMoneyString(mg['CASHBI'])
242 m_20BBmin = self.re_20BBmin.search(handText)
243 if m_20BBmin is not None:
244 info['sb'] = self.NLim_Blinds_20bb[mg['CASHBI']][0]
245 info['bb'] = self.NLim_Blinds_20bb[mg['CASHBI']][1]
246 else:
247 bb = Decimal(mg['CASHBI'])/100
248 sb = bb/2
249 info['sb'] = sb
250 info['bb'] = bb
251 else:
252 mg['SB'] = self.clearMoneyString(mg['SB'])
253 mg['BB'] = self.clearMoneyString(mg['BB'])
254 if 'SB' in mg:
255 info['sb'] = mg['SB']
256 if 'BB' in mg:
257 info['bb'] = mg['BB']
258 if 'CURRENCY' in mg:
259 if mg['CURRENCY'] == None:
260 info['currency'] = self.currencies['$']
261 else:
262 info['currency'] = self.currencies[mg['CURRENCY']]
263 if 'BUYIN_CURRENCY' in mg:
264 if mg['BUYIN_CURRENCY'] == None:
265 info['currency'] = self.currencies['$']
266 else:
267 info['currency'] = mg['BUYIN_CURRENCY']
268 if 'MIXED' in mg:
269 if mg['MIXED'] is not None: info['mix'] = self.mixes[mg['MIXED']]
271 if 'TOURNO' in mg and mg['TOURNO'] is None:
272 info['type'] = 'ring'
273 else:
274 info['type'] = 'tour'
276 if info['limitType'] == 'fl' and info['bb'] is not None and info['type'] == 'ring':
277 try:
278 info['sb'] = self.Lim_Blinds[mg['BB']][0]
279 info['bb'] = self.Lim_Blinds[mg['BB']][1]
280 except KeyError:
281 log.error(_("Lim_Blinds has no lookup for '%s'") % mg['BB'])
282 log.error("determineGameType: " + _("Raising FpdbParseError"))
283 raise FpdbParseError(_("Lim_Blinds has no lookup for '%s'") % mg['BB'])
284 #print "DEUBG: DGT.info: %s" % info
285 return info
288 def readHandInfo(self, hand):
289 info = {}
290 m2 = None
291 m = self.re_HandInfo.search(hand.handText,re.DOTALL)
292 if hand.gametype['type'] == 'ring':
293 m2 = self.re_GameInfo.search(hand.handText)
294 else:
295 m2 = self.re_GameInfoTrny.search(hand.handText)
296 if m is None or m2 is None:
297 log.error(_("No match in readHandInfo: '%s'") % hand.handText[0:100])
298 raise FpdbParseError(_("No match in readHandInfo: '%s'") % hand.handText[0:100])
299 info.update(m.groupdict())
300 info.update(m2.groupdict())
302 #print "DEBUG: readHand.info: %s" % info
304 # FIXME: it's dirty hack
305 # party doesnt subtract uncalled money from commited money
306 # so hand.totalPot calculation has to be redefined
307 from Hand import Pot, HoldemOmahaHand
308 def getNewTotalPot(origTotalPot):
309 def totalPot(self):
310 if self.totalpot is None:
311 self.pot.end()
312 self.totalpot = self.pot.total
313 for i,v in enumerate(self.collected):
314 if v[0] in self.pot.returned:
315 self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
316 self.collectees[v[0]] -= self.pot.returned[v[0]]
317 self.pot.returned[v[0]] = 0
318 return origTotalPot()
319 return totalPot
320 instancemethod = type(hand.totalPot)
321 hand.totalPot = instancemethod(getNewTotalPot(hand.totalPot), hand, HoldemOmahaHand)
323 for key in info:
324 pass
325 if key == 'DATETIME':
326 #Saturday, July 25, 07:53:52 EDT 2009
327 #Thursday, July 30, 21:40:41 MSKS 2009
328 #Sunday, October 25, 13:39:07 MSK 2009
329 #Mon Jul 12 13:38:32 EDT 2010
330 m2 = re.search(
331 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+)",
332 info[key],
333 re.UNICODE
335 month = self.months[m2.group('M')]
336 datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
337 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
338 # FIXME: some timezone correction required
339 #tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
340 #hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
342 if key == 'HID':
343 hand.handid = info[key]
344 if key == 'TABLE':
345 hand.tablename = info[key]
346 if key == 'MTTTABLE':
347 if info[key] != None:
348 hand.tablename = info[key]
349 hand.tourNo = info['TABLE']
350 if key == 'BUTTON':
351 hand.buttonpos = info[key]
352 if key == 'TOURNO':
353 hand.tourNo = info[key]
354 if key == 'TABLE_ID_WRAPPER':
355 if info[key] == '#':
356 # FIXME: there is no such property in Hand class
357 self.isSNG = True
358 if key == 'BUYIN':
359 if info[key] == None:
360 # Freeroll tourney
361 hand.buyin = 0
362 hand.fee = 0
363 hand.buyinCurrency = "FREE"
364 hand.isKO = False
365 elif hand.tourNo != None:
366 hand.buyin = 0
367 hand.fee = 0
368 hand.buyinCurrency = "FREE"
369 hand.isKO = False
370 if info[key].find("$")!=-1:
371 hand.buyinCurrency="USD"
372 elif info[key].find(u"€")!=-1:
373 hand.buyinCurrency="EUR"
374 else:
375 raise FpdbParseError(_("Failed to detect currency.") + " " + _("Hand ID: %s: '%s'") % (hand.handid, info[key]))
376 info[key] = info[key].strip(u'$€')
377 hand.buyin = int(100*Decimal(info[key]))
378 if key == 'LEVEL':
379 hand.level = info[key]
380 if key == 'PLAY' and info['PLAY'] != 'Real':
381 # if realy party doesn's save play money hh
382 hand.gametype['currency'] = 'play'
383 if key == 'MAX' and info[key] is not None:
384 hand.maxseats = int(info[key])
387 def readButton(self, hand):
388 m = self.re_Button.search(hand.handText)
389 if m:
390 hand.buttonpos = int(m.group('BUTTON'))
391 else:
392 log.info(_('readButton: not found'))
394 def readPlayerStacks(self, hand):
395 log.debug("readPlayerStacks")
396 m = self.re_PlayerInfo.finditer(hand.handText)
397 maxKnownStack = 0
398 zeroStackPlayers = []
399 for a in m:
400 if a.group('CASH') > '0':
401 #record max known stack for use with players with unknown stack
402 maxKnownStack = max(a.group('CASH'),maxKnownStack)
403 hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), self.clearMoneyString(a.group('CASH')))
404 else:
405 #zero stacked players are added later
406 zeroStackPlayers.append([int(a.group('SEAT')), a.group('PNAME'), self.clearMoneyString(a.group('CASH'))])
407 if hand.gametype['type'] == 'ring':
408 #finds first vacant seat after an exact seat
409 def findFirstEmptySeat(startSeat):
410 while startSeat in occupiedSeats:
411 if startSeat >= hand.maxseats:
412 startSeat = 0
413 startSeat += 1
414 return startSeat
416 re_JoiningPlayers = re.compile(r"(?P<PLAYERNAME>.*) has joined the table")
417 re_BBPostingPlayers = re.compile(r"(?P<PLAYERNAME>.*) posts big blind")
419 match_JoiningPlayers = re_JoiningPlayers.findall(hand.handText)
420 match_BBPostingPlayers = re_BBPostingPlayers.findall(hand.handText)
422 #add every player with zero stack, but:
423 #if a zero stacked player is just joined the table in this very hand then set his stack to maxKnownStack
424 for p in zeroStackPlayers:
425 if p[1] in match_JoiningPlayers:
426 p[2] = self.clearMoneyString(maxKnownStack)
427 hand.addPlayer(p[0],p[1],p[2])
429 seatedPlayers = list([(f[1]) for f in hand.players])
431 #it works for all known cases as of 2010-09-28
432 #should be refined with using match_ActivePlayers instead of match_BBPostingPlayers
433 #as a leaving and rejoining player could be active without posting a BB (sample HH needed)
434 unseatedActivePlayers = list(set(match_BBPostingPlayers) - set(seatedPlayers))
436 if unseatedActivePlayers:
437 for player in unseatedActivePlayers:
438 previousBBPoster = match_BBPostingPlayers[match_BBPostingPlayers.index(player)-1]
439 previousBBPosterSeat = dict([(f[1], f[0]) for f in hand.players])[previousBBPoster]
440 occupiedSeats = list([(f[0]) for f in hand.players])
441 occupiedSeats.sort()
442 newPlayerSeat = findFirstEmptySeat(previousBBPosterSeat)
443 hand.addPlayer(newPlayerSeat,player,self.clearMoneyString(maxKnownStack))
445 def markStreets(self, hand):
446 if hand.gametype['base'] in ("hold"):
447 m = re.search(r"\*{2} Dealing down cards \*{2}"
448 r"(?P<PREFLOP>.+?)"
449 r"(?:\*{2} Dealing Flop \*{2} (?P<FLOP>\[ \S\S, \S\S, \S\S \].+?))?"
450 r"(?:\*{2} Dealing Turn \*{2} (?P<TURN>\[ \S\S \].+?))?"
451 r"(?:\*{2} Dealing River \*{2} (?P<RIVER>\[ \S\S \].+?))?$"
452 , hand.handText,re.DOTALL)
453 elif hand.gametype['base'] in ("stud"):
454 m = re.search(r"(?P<ANTES>.+)"
455 r"(?:\*{2} Dealing \*{2}(?P<THIRD>.+))?"
456 r"(?:\*{2} Dealing Fourth street \*{2}(?P<FOURTH>.+))?"
457 r"(?:\*{2} Dealing Fifth street \*{2}(?P<FIFTH>.+))?"
458 r"(?:\*{2} Dealing Sixth street \*{2}(?P<SIXTH>.+))?"
459 r"(?:\*{2} Dealing Seventh street \*{2}(?P<SEVENTH>.+))?"
460 , hand.handText,re.DOTALL)
462 hand.addStreets(m)
464 def readCommunityCards(self, hand, street):
465 if street in ('FLOP','TURN','RIVER'):
466 m = self.re_Board.search(hand.streets[street])
467 hand.setCommunityCards(street, renderCards(m.group('CARDS')))
469 def readAntes(self, hand):
470 log.debug("reading antes")
471 m = self.re_Antes.finditer(hand.handText)
472 for player in m:
473 hand.addAnte(player.group('PNAME'), player.group('ANTE'))
475 def readBlinds(self, hand):
476 noSmallBlind = bool(self.re_NoSmallBlind.search(hand.handText))
477 if hand.gametype['type'] == 'ring':
478 try:
479 assert noSmallBlind==False
480 liveBlind = True
481 for m in self.re_PostSB.finditer(hand.handText):
482 if liveBlind:
483 hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
484 liveBlind = False
485 else:
486 # Post dead blinds as ante
487 hand.addBlind(m.group('PNAME'), 'secondsb', m.group('SB'))
488 except: # no small blind
489 hand.addBlind(None, None, None)
491 for a in self.re_PostBB.finditer(hand.handText):
492 hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
494 deadFilter = lambda s: s.replace(',', '.')
495 for a in self.re_PostDead.finditer(hand.handText):
496 hand.addBlind(a.group('PNAME'), 'both', deadFilter(a.group('BBNDEAD')))
497 else:
498 # party doesn't track blinds for tournaments
499 # so there're some cra^Wcaclulations
500 if hand.buttonpos == 0:
501 self.readButton(hand)
502 # NOTE: code below depends on Hand's implementation
503 # playersMap - dict {seat: (pname,stack)}
504 playersMap = dict([(f[0], f[1:3]) for f in hand.players])
505 maxSeat = max(playersMap)
507 def findFirstNonEmptySeat(startSeat):
508 while startSeat not in playersMap:
509 if startSeat >= maxSeat:
510 startSeat = 0
511 startSeat += 1
512 return startSeat
513 smartMin = lambda A,B: A if float(A) <= float(B) else B
515 if noSmallBlind:
516 hand.addBlind(None, None, None)
517 smallBlindSeat = int(hand.buttonpos)
518 else:
519 smallBlindSeat = findFirstNonEmptySeat(int(hand.buttonpos) + 1)
520 blind = smartMin(hand.sb, playersMap[smallBlindSeat][1])
521 hand.addBlind(playersMap[smallBlindSeat][0], 'small blind', blind)
523 bigBlindSeat = findFirstNonEmptySeat(smallBlindSeat + 1)
524 blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
525 hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
527 def readBringIn(self, hand):
528 pass
529 #m = self.re_BringIn.search(hand.handText,re.DOTALL)
530 #if m:
531 # #~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
532 # hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
534 def readHeroCards(self, hand):
535 # we need to grab hero's cards
536 for street in ('PREFLOP',):
537 if street in hand.streets.keys():
538 m = self.re_HeroCards.finditer(hand.streets[street])
539 for found in m:
540 hand.hero = found.group('PNAME')
541 newcards = renderCards(found.group('NEWCARDS'))
542 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
544 def readAction(self, hand, street):
545 m = self.re_Action.finditer(hand.streets[street])
546 for action in m:
547 acts = action.groupdict()
548 #print "DEBUG: acts: %s" % acts
549 playerName = action.group('PNAME')
550 amount = self.clearMoneyString(action.group('BET')) if action.group('BET') else None
551 actionType = action.group('ATYPE')
553 if actionType == 'raises':
554 if street == 'PREFLOP' and \
555 playerName in [item[0] for item in hand.actions['BLINDSANTES'] if item[2]!='ante']:
556 # preflop raise from blind
557 hand.addCallandRaise( street, playerName, amount )
558 else:
559 hand.addCallandRaise( street, playerName, amount )
560 elif actionType == 'calls':
561 hand.addCall( street, playerName, amount )
562 elif actionType == 'bets':
563 hand.addBet( street, playerName, amount )
564 elif actionType == 'folds':
565 hand.addFold( street, playerName )
566 elif actionType == 'checks':
567 hand.addCheck( street, playerName )
568 elif actionType == 'is all-In':
569 hand.addAllIn(street, playerName, amount)
570 else:
571 raise FpdbParseError((_("Unimplemented %s: '%s' '%s'") + " hid:%s") % ("readAction", playerName, actionType, hand.handid))
573 def readShowdownActions(self, hand):
574 # all action in readShownCards
575 pass
577 def readCollectPot(self,hand):
578 for m in self.re_CollectPot.finditer(hand.handText):
579 hand.addCollectPot(player=m.group('PNAME'),pot=self.clearMoneyString(m.group('POT')))
581 def readShownCards(self,hand):
582 for m in self.re_ShownCards.finditer(hand.handText):
583 if m.group('CARDS') is not None:
584 cards = renderCards(m.group('CARDS'))
586 mucked = m.group('SHOWED') != "show"
588 hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked)
590 @staticmethod
591 def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
592 "Returns string to search in windows titles"
593 if type=="tour":
594 TableName = table_name.split(" ")
595 print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number)
596 if len(TableName[1]) > 6:
597 return "#%s" % (table_number)
598 else:
599 return "%s.+Table\s#%s" % (TableName[0], table_number)
600 else:
601 return table_name
603 def renderCards(string):
604 "Splits strings like ' Js, 4d '"
605 cards = string.strip().split(' ')
606 return filter(len, map(lambda x: x.strip(' ,'), cards))