Coverage for MergeToFpdb.py: 0%
443 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 18:50 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2010-2011, Matthew Boss
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.
10#
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.
15#
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
20########################################################################
22from __future__ import division
24from past.utils import old_div
25#import L10n
26#_ = L10n.get_translation()
28# TODO:
29#
30# -- Assumes that the currency of ring games is USD
31# -- Only accepts 'realmoney="true"'
32# -- A hand's time-stamp does not record seconds past the minute (a limitation of the history format)
33# -- hand.maxseats can only be guessed at
34# -- Cannot parse tables that run it twice
35# -- Cannot parse hands in which someone is all in in one of the blinds.
37import sys
38from HandHistoryConverter import *
39import MergeStructures
40from decimal_wrapper import Decimal
43class Merge(HandHistoryConverter):
44 sitename = "Merge"
45 filetype = "text"
46 codepage = ("cp1252", "utf8")
47 siteId = 12
48 copyGameHeader = True
49 Structures = MergeStructures.MergeStructures()
51 limits = { 'No Limit':'nl', 'No Limit ':'nl', 'Limit':'fl', 'Pot Limit':'pl', 'Pot Limit ':'pl', 'Half Pot Limit':'hp'}
52 games = { # base, category
53 'Holdem' : ('hold','holdem'),
54 'Omaha' : ('hold','omahahi'),
55 'Omaha H/L8' : ('hold','omahahilo'),
56 '2-7 Lowball' : ('draw','27_3draw'),
57 'A-5 Lowball' : ('draw','a5_3draw'),
58 'Badugi' : ('draw','badugi'),
59 '5-Draw w/Joker' : ('draw','fivedraw'),
60 '5-Draw' : ('draw','fivedraw'),
61 '7-Stud' : ('stud','studhi'),
62 '7-Stud H/L8' : ('stud','studhilo'),
63 '5-Stud' : ('stud','5_studhi'),
64 'Razz' : ('stud','razz'),
65 }
67 mixes = {
68 'HA' : 'ha',
69 'RASH' : 'rash',
70 'HO' : 'ho',
71 'SHOE' : 'shoe',
72 'HORSE' : 'horse',
73 'HOSE' : 'hose',
74 'HAR' : 'har'
75 }
77 Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'),
78 '0.20': ('0.05', '0.10'),
79 '0.25': ('0.05', '0.10'), '0.50': ('0.10', '0.25'),
80 '1.00': ('0.25', '0.50'), '1': ('0.25', '0.50'),
81 '2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'),
82 '4.00': ('1.00', '2.00'), '4': ('1.00', '2.00'),
83 '6.00': ('1.50', '3.00'), '6': ('1.50', '3.00'),
84 '8.00': ('2.00', '4.00'), '8': ('2.00', '4.00'),
85 '10.00': ('2.00', '5.00'), '10': ('2.00', '5.00'),
86 '12.00': ('3.00', '6.00'), '12': ('3.00', '6.00'),
87 '20.00': ('5.00', '10.00'), '20': ('5.00', '10.00'),
88 '30.00': ('10.00', '15.00'), '30': ('10.00', '15.00'),
89 '40.00': ('10.00', '20.00'), '40': ('10.00', '20.00'),
90 '50.00': ('10.00', '25.00'), '50': ('10.00', '25.00'),
91 '60.00': ('15.00', '30.00'), '60': ('15.00', '30.00'),
92 '100.00': ('25.00', '50.00'), '100': ('25.00', '50.00'),
93 '200.00': ('50.00', '100.00'), '200': ('50.00', '100.00'),
94 '400.00': ('100.00', '200.00'), '400': ('100.00', '200.00'),
95 }
97 Multigametypes = { '1': ('hold','holdem'),
98 '2': ('hold','holdem'),
99 '4': ('hold','omahahi'),
100 '9': ('hold', 'holdem'),
101 '23': ('hold', 'holdem'),
102 '34': ('hold','omahahilo'),
103 '35': ('hold','omahahilo'),
104 '37': ('hold','omahahilo'),
105 '38': ('stud','studhi'),
106 '39': ('stud','studhi'),
107 '41': ('stud','studhi'),
108 '42': ('stud','studhi'),
109 '43': ('stud','studhilo'),
110 '45': ('stud','studhilo'),
111 '46': ('stud','razz'),
112 '47': ('stud','razz'),
113 '49': ('stud','razz')
114 }
117 # Static regexes
118 re_Identify = re.compile(u'<game\sid=\"[0-9]+\-[0-9]+\"\sstarttime')
119 re_SplitHands = re.compile(r'</game>\n+(?=<)')
120 re_TailSplitHands = re.compile(r'(</game>)')
121 re_GameInfo = re.compile(r'<description type="(?P<GAME>Holdem|Omaha|Omaha|Omaha\sH/L8|2\-7\sLowball|A\-5\sLowball|Badugi|5\-Draw\sw/Joker|5\-Draw|7\-Stud|7\-Stud\sH/L8|5\-Stud|Razz|HORSE|RASH|HA|HO|SHOE|HOSE|HAR)(?P<TYPE>\sTournament)?" stakes="(?P<LIMIT>(No Limit|Limit|Pot Limit|Half Pot Limit)\s?)(\sLevel\s\d+\sBlinds)?(\s\(?\$?(?P<SB>[.0-9]+)?/?\$?(?P<BB>[.0-9]+)?(?P<blah>.*)\)?)?"(\sversion="\d+")?\s?/>\s?', re.MULTILINE)
122 # <game id="46154255-645" starttime="20111230232051" numholecards="2" gametype="1" seats="9" realmoney="false" data="20111230|Play Money (46154255)|46154255|46154255-645|false">
123 # <game id="46165919-1" starttime="20111230161824" numholecards="2" gametype="23" seats="10" realmoney="true" data="20111230|Fun Step 1|46165833-1|46165919-1|true">
124 # <game id="46289039-1" starttime="20120101200100" numholecards="2" gametype="23" seats="9" realmoney="true" data="20120101|$200 Freeroll - NL Holdem - 20%3A00|46245544-1|46289039-1|true">
125 re_HandInfo = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>.+?)" numholecards="[0-9]+" gametype="[0-9]+" (stakes=".*" )?(multigametype="(?P<MULTIGAMETYPE1>\d+)" )?(seats="(?P<SEATS>[0-9]+)" )?realmoney="(?P<REALMONEY>(true|false))" (multigametype="(?P<MULTIGAMETYPE2>\d+)" )?(data="[0-9]+[|:](?P<TABLENAME>[^|:]+)[|:](?P<TDATA>[^|:]+)[|:]?)?.*>', re.MULTILINE)
126 re_Button = re.compile(r'<players dealer="(?P<BUTTON>[0-9]+)"\s?>')
127 re_PlayerInfo = re.compile(r'<player seat="(?P<SEAT>[0-9]+)" nickname="(?P<PNAME>.+)" balance="\$?(?P<CASH>[.0-9]+)" dealtin="(?P<DEALTIN>(true|false))" />', re.MULTILINE)
128 re_Board = re.compile(r'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re.MULTILINE)
129 re_Buyin = re.compile(r'\$(?P<BUYIN>[.,0-9]+)\s(?P<TYPE>Freeroll|Satellite|Guaranteed)?', re.MULTILINE)
130 re_secondGame = re.compile(r'\$?(?P<SB>[.0-9]+)?/?\$?(?P<BB>[.0-9]+)', re.MULTILINE)
132 # The following are also static regexes: there is no need to call
133 # compilePlayerRegexes (which does nothing), since players are identified
134 # not by name but by seat number
135 re_PostSB = re.compile(r'<event sequence="[0-9]+" type="SMALL_BLIND" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"\s?/>', re.MULTILINE)
136 re_PostBB = re.compile(r'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"\s?/>', re.MULTILINE)
137 re_PostBoth = re.compile(r'<event sequence="[0-9]+" type="RETURN_BLIND" (?P<TIMESTAMP>timestamp="[0-9]+" )?player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"\s?/>', re.MULTILINE)
138 re_Antes = re.compile(r'<event sequence="[0-9]+" type="ANTE" (?P<TIMESTAMP>timestamp="\d+" )?player="(?P<PSEAT>[0-9])" amount="(?P<ANTE>[.0-9]+)"\s?/>', re.MULTILINE)
139 re_BringIn = re.compile(r'<event sequence="[0-9]+" type="BRING_IN" (?P<TIMESTAMP>timestamp="\d+" )?player="(?P<PSEAT>[0-9])" amount="(?P<BRINGIN>[.0-9]+)"\s?/>', re.MULTILINE)
140 re_HeroCards = re.compile(r'<cards type="(HOLE|DRAW_DRAWN_CARDS)" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
141 re_Action = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT|DRAW|COMPLETE)"( timestamp="(?P<TIMESTAMP>[0-9]+)")? player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?( text="(?P<TXT>.+)")?\s?/>', re.MULTILINE)
142 re_AllActions = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT|DRAW|COMPLETE|BIG_BLIND|INITIAL_BLIND|SMALL_BLIND|RETURN_BLIND|BRING_IN|ANTE)"( timestamp="(?P<TIMESTAMP>[0-9]+)")? player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?( text="(?P<TXT>.+)")?\s?/>', re.MULTILINE)
143 re_CollectPot = re.compile(r'<winner amount="(?P<POT>[.0-9]+)" uncalled="(?P<UNCALLED>false|true)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
144 re_SitsOut = re.compile(r'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE)
145 re_ShownCards = re.compile(r'<cards type="(?P<SHOWED>SHOWN|MUCKED)" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE)
146 re_Connection = re.compile(r'<event sequence="[0-9]+" type="(?P<TYPE>RECONNECTED|DISCONNECTED)" timestamp="[0-9]+" player="[0-9]"\s?/>', re.MULTILINE)
147 re_Cancelled = re.compile(r'<event sequence="\d+" type="GAME_CANCELLED" timestamp="\d+"\s?/>', re.MULTILINE)
148 re_LeaveTable = re.compile(r'<event sequence="\d+" type="LEAVE" timestamp="\d+" player="\d"\s?/>', re.MULTILINE)
149 re_PlayerOut = re.compile(r'<event sequence="\d+" type="(PLAYER_OUT|LEAVE)" timestamp="\d+" player="(?P<PSEAT>[0-9])"\s?/>', re.MULTILINE)
150 re_EndOfHand = re.compile(r'<round id="END_OF_GAME"', re.MULTILINE)
151 re_DateTime = re.compile(r'(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)', re.MULTILINE)
152 re_PlayMoney = re.compile(r'realmoney="false"')
154 def compilePlayerRegexs(self, hand):
155 pass
157 def playerNameFromSeatNo(self, seatNo, hand):
158 # This special function is required because Merge Poker records
159 # actions by seat number (0 based), not by the player's name
160 for p in hand.players:
161 if p[0] == int(seatNo)+1:
162 return p[1]
164 def readSupportedGames(self):
165 return [["ring", "hold", "nl"],
166 ["ring", "hold", "pl"],
167 ["ring", "hold", "fl"],
168 ["ring", "hold", "hp"],
170 ["ring", "stud", "fl"],
171 ["ring", "stud", "pl"],
172 ["ring", "stud", "nl"],
174 ["ring", "draw", "fl"],
175 ["ring", "draw", "pl"],
176 ["ring", "draw", "nl"],
177 ["ring", "draw", "hp"],
179 ["tour", "hold", "nl"],
180 ["tour", "hold", "pl"],
181 ["tour", "hold", "fl"],
183 ["tour", "stud", "fl"],
184 ["tour", "stud", "pl"],
185 ["tour", "stud", "nl"],
187 ["tour", "draw", "fl"],
188 ["tour", "draw", "pl"],
189 ["tour", "draw", "nl"],
190 ]
192 def parseHeader(self, handText, whole_file):
193 gametype = self.determineGameType(handText)
194 if gametype is None:
195 gametype = self.determineGameType(whole_file)
196 if gametype is None:
197 if not re.search('<description', whole_file):
198 raise FpdbHandPartial("Partial hand history: No <desription> tag")
199 else:
200 tmp = handText[0:200]
201 log.error(("MergeToFpdb.determineGameType: '%s'") % tmp)
202 raise FpdbParseError
203 else:
204 if 'mix' in gametype and gametype['mix']!=None:
205 self.mergeMultigametypes(handText)
206 return gametype
208 def determineGameType(self, handText):
209 """return dict with keys/values:
210 'type' in ('ring', 'tour')
211 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl', 'hp')
212 'base' in ('hold', 'stud', 'draw')
213 'category' in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi')
214 'hilo' in ('h','l','s')
215 'smallBlind' int?
216 'bigBlind' int?
217 'smallBet'
218 'bigBet'
219 'currency' in ('USD', 'EUR', 'T$', <countrycode>)
220or None if we fail to get the info """
222 m = self.re_GameInfo.search(handText)
223 if not m: return None
225 self.info = {}
226 mg = m.groupdict()
227 #print "DEBUG: mg: %s" % mg
229 if 'LIMIT' in mg:
230 self.info['limitType'] = self.limits[mg['LIMIT']]
231 if 'GAME' in mg:
232 if mg['GAME'] in self.mixes:
233 self.info['mix'] = self.mixes[mg['GAME']]
234 self.mergeMultigametypes(handText)
235 else:
236 (self.info['base'], self.info['category']) = self.games[mg['GAME']]
237 if 'SB' in mg:
238 self.info['sb'] = mg['SB']
239 if 'BB' in mg:
240 self.info['bb'] = mg['BB']
241 self.info['secondGame'] = False
242 if mg['blah'] is not None:
243 if self.re_secondGame.search(mg['blah']):
244 self.info['secondGame'] = True
245 if ' Tournament' == mg['TYPE']:
246 self.info['type'] = 'tour'
247 self.info['currency'] = 'T$'
248 else:
249 self.info['type'] = 'ring'
250 if self.re_PlayMoney.search(handText):
251 self.info['currency'] = 'play'
252 else:
253 self.info['currency'] = 'USD'
255 if self.info['limitType'] == 'fl' and self.info['bb'] is not None and self.info['type'] == 'ring':
256 try:
257 self.info['sb'] = self.Lim_Blinds[mg['BB']][0]
258 self.info['bb'] = self.Lim_Blinds[mg['BB']][1]
259 except KeyError:
260 tmp = handText[0:200]
261 log.error(("MergeToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") % (mg['BB'], tmp))
262 raise FpdbParseError
264 return self.info
266 def readHandInfo(self, hand):
267 m = self.re_HandInfo.search(hand.handText)
268 if m is None:
269 tmp = hand.handText[0:200]
270 log.error(("MergeToFpdb.readHandInfo: '%s'") % tmp)
271 raise FpdbParseError
273 #print "DEBUG: mg: %s" % m.groupdict()
274 self.determineErrorType(hand, None)
276 hand.handid = m.group('HID1') + m.group('HID2')
278 m1 = self.re_DateTime.search(m.group('DATETIME'))
279 if m1:
280 mg = m1.groupdict()
281 datetimestr = "%s/%s/%s %s:%s:%s" % (mg['Y'], mg['M'],mg['D'],mg['H'],mg['MIN'],mg['S'])
282 #tz = a.group('TZ') # just assume ET??
283 hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
284 else:
285 hand.startTime = datetime.datetime.strptime(m.group('DATETIME')[:14],'%Y%m%d%H%M%S')
287 hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
288 hand.newFormat = datetime.datetime.strptime('20100908000000','%Y%m%d%H%M%S')
289 hand.newFormat = HandHistoryConverter.changeTimezone(hand.newFormat, "ET", "UTC")
291 if hand.gametype['type'] == 'tour':
292 tid_table = m.group('TDATA').split('-')
293 tid = tid_table[0]
294 if len(tid_table)>1:
295 table = tid_table[1]
296 else:
297 table = '0'
298 self.info['tablename'] = m.group('TABLENAME').replace(' - ', ' - ').strip()
299 self.info['tourNo'] = hand.tourNo
300 hand.tourNo = tid
301 hand.tablename = table
302 structure = self.Structures.lookupSnG(self.info['tablename'], hand.startTime)
303 if structure!=None:
304 hand.buyin = int(100*structure['buyIn'])
305 hand.fee = int(100*structure['fee'])
306 hand.buyinCurrency=structure['currency']
307 hand.maxseats = structure['seats']
308 hand.isSng = True
309 self.summaryInFile = True
310 else:
311 #print 'DEBUG', 'no match for tourney %s tourNo %s' % (self.info['tablename'], tid)
312 hand.buyin = 0
313 hand.fee = 0
314 hand.buyinCurrency="NA"
315 hand.maxseats = None
316 if m.group('SEATS')!=None:
317 hand.maxseats = int(m.group('SEATS'))
318 else:
319 #log.debug("HID %s-%s, Table %s" % (m.group('HID1'), m.group('HID2'), m.group('TABLENAME')))
320 hand.maxseats = None
321 if m.group('TABLENAME')!=None:
322 hand.tablename = m.group('TABLENAME')
323 else:
324 hand.tablename = self.base_name
325 if m.group('SEATS')!=None:
326 hand.maxseats = int(m.group('SEATS'))
327 # Check that the hand is complete up to the awarding of the pot; if
328 # not, the hand is unparseable
329 if self.re_EndOfHand.search(hand.handText) is None:
330 self.determineErrorType(hand, "readHandInfo")
332 def readPlayerStacks(self, hand):
333 acted = {}
334 seated = {}
335 m = self.re_PlayerInfo.finditer(hand.handText)
336 for a in m:
337 seatno = a.group('SEAT')
338 seated[seatno] = [a.group('PNAME'), a.group('CASH')]
340 if hand.gametype['type'] == "ring" :
341 # We can't 100% trust the 'dealtin' field. So read the actions and see if the players acted
342 m2 = self.re_AllActions.finditer(hand.handText)
343 fulltable = False
344 for action in m2:
345 acted[action.group('PSEAT')] = True
346 if list(acted.keys()) == list(seated.keys()): # We've faound all players
347 fulltable = True
348 break
349 if fulltable != True:
350 for seatno in list(seated.keys()):
351 if seatno not in acted:
352 del seated[seatno]
354 for seatno in list(acted.keys()):
355 if seatno not in seated:
356 log.error(("MergeToFpdb.readPlayerStacks: '%s' Seat:%s acts but not listed") % (hand.handid, seatno))
357 raise FpdbParseError
359 for seat in seated:
360 name, stack = seated[seat]
361 # Merge indexes seats from 0. Add 1 so we don't have to add corner cases everywhere else.
362 hand.addPlayer(int(seat) + 1, name, stack)
364 if hand.maxseats==None:
365 if hand.gametype['type'] == 'tour' and self.maxseats==0:
366 hand.maxseats = self.guessMaxSeats(hand)
367 self.maxseats = hand.maxseats
368 elif hand.gametype['type'] == 'tour':
369 hand.maxseats = self.maxseats
370 else:
371 hand.maxseats = None
373 # No players found at all.
374 if not hand.players:
375 self.determineErrorType(hand, "readPlayerStacks")
377 def markStreets(self, hand):
378 if hand.gametype['base'] == 'hold':
379 m = re.search(r'<round id="PREFLOP" sequence="[0-9]+"\s?>(?P<PREFLOP>.+(?=<round id="POSTFLOP")|.+)'
380 r'(<round id="POSTFLOP" sequence="[0-9]+"\s?>(?P<FLOP>.+(?=<round id="POSTTURN")|.+))?'
381 r'(<round id="POSTTURN" sequence="[0-9]+"\s?>(?P<TURN>.+(?=<round id="POSTRIVER")|.+))?'
382 r'(<round id="POSTRIVER" sequence="[0-9]+"\s?>(?P<RIVER>.+))?', hand.handText, re.DOTALL)
383 elif hand.gametype['base'] == 'draw':
384 if hand.gametype['category'] in ('27_3draw','badugi','a5_3draw'):
385 m = re.search(r'(?P<PREDEAL>.+(?=<round id="PRE_FIRST_DRAW" sequence="[0-9]+">)|.+)'
386 r'(<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DEAL>.+(?=<round id="FIRST_DRAW")|.+))?'
387 r'(<round id="FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DRAWONE>.+(?=<round id="SECOND_DRAW")|.+))?'
388 r'(<round id="SECOND_DRAW" sequence="[0-9]+"\s?>(?P<DRAWTWO>.+(?=<round id="THIRD_DRAW")|.+))?'
389 r'(<round id="THIRD_DRAW" sequence="[0-9]+"\s?>(?P<DRAWTHREE>.+))?', hand.handText,re.DOTALL)
390 else:
391 m = re.search(r'(?P<PREDEAL>.+(?=<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>)|.+)'
392 r'(<round id="PRE_FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DEAL>.+(?=<round id="FIRST_DRAW")|.+))?'
393 r'(<round id="FIRST_DRAW" sequence="[0-9]+"\s?>(?P<DRAWONE>.+(?=<round id="SECOND_DRAW")|.+))?', hand.handText,re.DOTALL)
394 elif hand.gametype['base'] == 'stud':
395 m = re.search(r'(?P<ANTES>.+(?=<round id="BRING_IN" sequence="[0-9]+"\s?>)|.+)'
396 r'(<round id="BRING_IN" sequence="[0-9]+"\s?>(?P<THIRD>.+(?=<round id="FOURTH_STREET")|.+))?'
397 r'(<round id="FOURTH_STREET" sequence="[0-9]+"\s?>(?P<FOURTH>.+(?=<round id="FIFTH_STREET")|.+))?'
398 r'(<round id="FIFTH_STREET" sequence="[0-9]+"\s?>(?P<FIFTH>.+(?=<round id="SIXTH_STREET")|.+))?'
399 r'(<round id="SIXTH_STREET" sequence="[0-9]+"\s?>(?P<SIXTH>.+(?=<round id="SEVENTH_STREET")|.+))?'
400 r'(<round id="SEVENTH_STREET" sequence="[0-9]+"\s?>(?P<SEVENTH>.+))?', hand.handText,re.DOTALL)
401 if m == None:
402 self.determineErrorType(hand, "markStreets")
403 hand.addStreets(m)
405 def readCommunityCards(self, hand, street):
406 m = self.re_Board.search(hand.streets[street])
407 if m and street in ('FLOP','TURN','RIVER'):
408 if street == 'FLOP':
409 hand.setCommunityCards(street, m.group('CARDS').split(','))
410 elif street in ('TURN','RIVER'):
411 hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]])
412 else:
413 self.determineErrorType(hand, "readCommunityCards")
415 def readAntes(self, hand):
416 for player in self.re_Antes.finditer(hand.handText):
417 pname = self.playerNameFromSeatNo(player.group('PSEAT'), hand)
418 #print "DEBUG: hand.addAnte(%s,%s)" %(pname, player.group('ANTE'))
419 self.adjustMergeTourneyStack(hand, pname, player.group('ANTE'))
420 hand.addAnte(pname, player.group('ANTE'))
422 def readBringIn(self, hand):
423 m = self.re_BringIn.search(hand.handText)
424 if m:
425 pname = self.playerNameFromSeatNo(m.group('PSEAT'), hand)
426 #print "DEBUG: hand.addBringIn(%s,%s)" %(pname, m.group('BRINGIN'))
427 self.adjustMergeTourneyStack(hand, pname, m.group('BRINGIN'))
428 hand.addBringIn(pname, m.group('BRINGIN'))
430 if hand.gametype['sb'] == None and hand.gametype['bb'] == None:
431 hand.gametype['sb'] = "1"
432 hand.gametype['bb'] = "2"
434 def readBlinds(self, hand):
435 if (hand.gametype['category'], hand.gametype['limitType']) == ("badugi", "hp"):
436 if hand.gametype['sb'] == None and hand.gametype['bb'] == None:
437 hand.gametype['sb'] = "1"
438 hand.gametype['bb'] = "2"
439 else:
440 if hand.gametype['base'] == 'hold':
441 street = 'PREFLOP'
442 elif hand.gametype['base'] == 'draw':
443 street = 'DEAL'
444 allinBlinds = {}
445 blindsantes = hand.handText.split(street)[0]
446 bb, sb = None, None
447 for a in self.re_PostSB.finditer(blindsantes):
448 #print "DEBUG: found sb: '%s' '%s'" %(self.playerNameFromSeatNo(a.group('PSEAT'), hand), a.group('SB'))
449 sb = a.group('SB')
450 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand)
451 self.adjustMergeTourneyStack(hand, player, sb)
452 hand.addBlind(player,'small blind', sb)
453 if not hand.gametype['sb'] or hand.gametype['secondGame']:
454 hand.gametype['sb'] = sb
455 for a in self.re_PostBB.finditer(blindsantes):
456 #print "DEBUG: found bb: '%s' '%s'" %(self.playerNameFromSeatNo(a.group('PSEAT'), hand), a.group('BB'))
457 bb = a.group('BB')
458 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand)
459 self.adjustMergeTourneyStack(hand, player, bb)
460 hand.addBlind(player, 'big blind', bb)
461 if not hand.gametype['bb'] or hand.gametype['secondGame']:
462 hand.gametype['bb'] = bb
463 for a in self.re_PostBoth.finditer(blindsantes):
464 bb = Decimal(self.info['bb'])
465 amount = Decimal(a.group('SBBB'))
466 player = self.playerNameFromSeatNo(a.group('PSEAT'), hand)
467 self.adjustMergeTourneyStack(hand, player, a.group('SBBB'))
468 if amount < bb:
469 hand.addBlind(player, 'small blind', a.group('SBBB'))
470 elif amount == bb:
471 hand.addBlind(player, 'big blind', a.group('SBBB'))
472 else:
473 hand.addBlind(player, 'both', a.group('SBBB'))
474 if sb is None or bb is None:
475 m = self.re_Action.finditer(blindsantes)
476 for action in m:
477 player = self.playerNameFromSeatNo(action.group('PSEAT'), hand)
478 #print "DEBUG: found: '%s' '%s'" %(self.playerNameFromSeatNo(action.group('PSEAT'), hand), action.group('BET'))
479 if sb is None:
480 if action.group('BET') and action.group('BET')!= '0.00':
481 sb = action.group('BET')
482 self.adjustMergeTourneyStack(hand, player, sb)
483 hand.addBlind(player, 'small blind', sb)
484 if not hand.gametype['sb'] or hand.gametype['secondGame']:
485 hand.gametype['sb'] = sb
486 elif action.group('BET') == '0.00':
487 allinBlinds[player] = 'small blind'
488 #log.error((("MergeToFpdb.readBlinds: Cannot calcualte tourney all-in blind for hand '%s'")) % hand.handid)
489 #raise FpdbParseError
490 elif sb and bb is None:
491 if action.group('BET') and action.group('BET')!= '0.00':
492 bb = action.group('BET')
493 self.adjustMergeTourneyStack(hand, player, bb)
494 hand.addBlind(player, 'big blind', bb)
495 if not hand.gametype['bb'] or hand.gametype['secondGame']:
496 hand.gametype['bb'] = bb
497 elif action.group('BET') == '0.00':
498 allinBlinds[player] = 'big blind'
499 #log.error((("MergeToFpdb.readBlinds: Cannot calcualte tourney all-in blind for hand '%s'")) % hand.handid)
500 #raise FpdbParseError
501 self.fixTourBlinds(hand, allinBlinds)
503 def fixTourBlinds(self, hand, allinBlinds):
504 # FIXME
505 # The following should only trigger when a small blind is missing in a tournament, or the sb/bb is ALL_IN
506 # see http://sourceforge.net/apps/mantisbt/fpdb/view.php?id=115
507 if hand.gametype['type'] == 'tour' or hand.gametype['secondGame']:
508 if hand.gametype['sb'] == None and hand.gametype['bb'] == None:
509 hand.gametype['sb'] = "1"
510 hand.gametype['bb'] = "2"
511 elif hand.gametype['sb'] == None:
512 hand.gametype['sb'] = str(old_div(int(Decimal(hand.gametype['bb'])),2))
513 elif hand.gametype['bb'] == None:
514 hand.gametype['bb'] = str(int(Decimal(hand.gametype['sb']))*2)
515 if old_div(int(Decimal(hand.gametype['bb'])),2) != int(Decimal(hand.gametype['sb'])):
516 if old_div(int(Decimal(hand.gametype['bb'])),2) < int(Decimal(hand.gametype['sb'])):
517 hand.gametype['bb'] = str(int(Decimal(hand.gametype['sb']))*2)
518 else:
519 hand.gametype['sb'] = str(old_div(int(Decimal(hand.gametype['bb'])),2))
520 hand.sb = hand.gametype['sb']
521 hand.bb = hand.gametype['bb']
522 for player, blindtype in list(allinBlinds.items()):
523 if blindtype=='big blind':
524 self.adjustMergeTourneyStack(hand, player, hand.bb)
525 hand.addBlind(player, 'big blind', hand.bb)
526 else:
527 self.adjustMergeTourneyStack(hand, player, hand.sb)
528 hand.addBlind(player, 'small blind', hand.sb)
530 def mergeMultigametypes(self, handText):
531 m2 = self.re_HandInfo.search(handText)
532 if m2 is None:
533 tmp = handText[0:200]
534 log.error(("MergeToFpdb.readHandInfo: '%s'") % tmp)
535 raise FpdbParseError
536 multigametype = m2.group('MULTIGAMETYPE1') if m2.group('MULTIGAMETYPE1') else m2.group('MULTIGAMETYPE2')
537 if multigametype:
538 try:
539 (self.info['base'], self.info['category']) = self.Multigametypes[multigametype]
540 except KeyError:
541 tmp = handText[0:200]
542 log.error(("MergeToFpdb.determineGameType: Multigametypes has no lookup for '%s'") % multigametype)
543 raise FpdbParseError
545 def adjustMergeTourneyStack(self, hand, player, amount):
546 amount = Decimal(amount)
547 if hand.gametype['type'] == 'tour':
548 for p in hand.players:
549 if p[1]==player:
550 stack = Decimal(p[2])
551 stack += amount
552 p[2] = str(stack)
553 hand.stacks[player] += amount
555 def readButton(self, hand):
556 hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
558 def readHoleCards(self, hand):
559# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
560# we need to grab hero's cards
561 herocards = []
562 for street in ('PREFLOP', 'DEAL'):
563 if street in list(hand.streets.keys()):
564 m = self.re_HeroCards.finditer(hand.streets[street])
565 for found in m:
566# if m == None:
567# hand.involved = False
568# else:
569 hand.hero = self.playerNameFromSeatNo(found.group('PSEAT'), hand)
570 cards = found.group('CARDS').split(',')
571 hand.addHoleCards(street, hand.hero, closed=cards, shown=False, mucked=False, dealt=True)
573 for street in hand.holeStreets:
574 if street in hand.streets:
575 if not hand.streets[street] or street in ('PREFLOP', 'DEAL') or hand.gametype['base'] == 'hold': continue # already done these
576 m = self.re_HeroCards.finditer(hand.streets[street])
577 for found in m:
578 player = self.playerNameFromSeatNo(found.group('PSEAT'), hand)
579 if player in hand.stacks:
580 if found.group('CARDS') is None:
581 cards = []
582 newcards = []
583 oldcards = []
584 else:
585 if hand.gametype['base'] == 'stud':
586 cards = found.group('CARDS').replace('null', '').split(',')
587 cards = [c for c in cards if c!='']
588 oldcards = cards[:-1]
589 newcards = [cards[-1]]
590 else:
591 cards = found.group('CARDS').split(',')
592 oldcards = cards
593 newcards = []
594 if street == 'THIRD' and len(cards) == 3: # hero in stud game
595 hand.hero = player
596 herocards = cards
597 hand.dealt.add(hand.hero) # need this for stud??
598 hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
599 elif (cards != herocards and hand.gametype['base'] == 'stud'):
600 if hand.hero == player:
601 herocards = cards
602 hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
603 elif (len(cards)<5):
604 if street == 'SEVENTH':
605 oldcards = []
606 newcards = []
607 hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
608 elif (len(cards)==7):
609 for street in hand.holeStreets:
610 hand.holecards[street][player] = [[], []]
611 hand.addHoleCards(street, player, closed=cards, open=[], shown=False, mucked=False, dealt=False)
612 elif (hand.gametype['base'] == 'draw'):
613 hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
615 def readAction(self, hand, street):
616 #log.debug("readAction (%s)" % street)
617 m = self.re_Action.finditer(hand.streets[street])
618 for action in m:
619 player = self.playerNameFromSeatNo(action.group('PSEAT'), hand)
620 if player in hand.stacks and player not in hand.folded:
621 if action.group('ATYPE') in ('FOLD', 'SIT_OUT'):
622 hand.addFold(street, player)
623 elif action.group('ATYPE') == 'CHECK':
624 hand.addCheck(street, player)
625 elif action.group('ATYPE') == 'CALL':
626 hand.addCall(street, player, action.group('BET'))
627 elif action.group('ATYPE') == 'RAISE':
628 if hand.startTime < hand.newFormat:
629 hand.addCallandRaise(street, player, action.group('BET'))
630 else:
631 hand.addRaiseTo(street, player, action.group('BET'))
632 elif action.group('ATYPE') == 'BET':
633 hand.addBet(street, player, action.group('BET'))
634 elif action.group('ATYPE') == 'ALL_IN' and action.group('BET') != None:
635 hand.addAllIn(street, player, action.group('BET'))
636 elif action.group('ATYPE') == 'DRAW':
637 hand.addDiscard(street, player, action.group('TXT'))
638 elif action.group('ATYPE') == 'COMPLETE':
639 if hand.gametype['base'] != 'stud':
640 hand.addRaiseTo(street, player, action.group('BET'))
641 else:
642 hand.addComplete( street, player, action.group('BET') )
643 else:
644 log.debug(("Unimplemented %s: '%s' '%s'") % ("readAction", action.group('PSEAT'), action.group('ATYPE')))
646 def readShowdownActions(self, hand):
647 pass
649 def readCollectPot(self, hand):
650 hand.setUncalledBets(True)
651 for m in self.re_CollectPot.finditer(hand.handText):
652 pname = self.playerNameFromSeatNo(m.group('PSEAT'), hand)
653 if pname!=None:
654 pot = m.group('POT')
655 hand.addCollectPot(player=pname, pot=pot)
657 def readShownCards(self, hand):
658 for m in self.re_ShownCards.finditer(hand.handText):
659 if m.group('CARDS') is not None:
660 cards = m.group('CARDS')
661 cards = m.group('CARDS').split(',')
663 (shown, mucked) = (False, False)
664 if m.group('SHOWED') == "SHOWN": shown = True
665 elif m.group('SHOWED') == "MUCKED": mucked = True
667 #print "DEBUG: hand.addShownCards(%s, %s, %s, %s)" %(cards, m.group('PNAME'), shown, mucked)
668 hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'),hand), shown=shown, mucked=mucked)
670 def determineErrorType(self, hand, function):
671 message = False
672 m = self.re_Connection.search(hand.handText)
673 if m:
674 message = ("Found %s. Hand missing information.") % m.group('TYPE')
675 m = self.re_LeaveTable.search(hand.handText)
676 if m:
677 message = ("Found LEAVE. Player left table before hand completed")
678 m = self.re_Cancelled.search(hand.handText)
679 if m:
680 message = ("Found CANCELLED")
681 if message == False and function == "markStreets":
682 message = ("Failed to identify all streets")
683 if message == False and function == "readHandInfo":
684 message = ("END_OF_HAND not found. No obvious reason")
685 if message:
686 raise FpdbHandPartial("Partial hand history: %s '%s' %s" % (function, hand.handid, message))
688 @staticmethod
689 def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
690 "Returns string to search in windows titles"
691 regex = re.escape(str(table_name))
692 if type=="tour":
693 # Ignoring table number as it doesn't appear to be in the window title
694 # "$200 Freeroll - NL Holdem - 20:00 (46302299) - Table 1" -- the table number doesn't matter, it seems to always be 1 in the HH.
695 # "Fun Step 1 (4358174) - Table 1"
696 regex = re.escape(str(tournament))
697 log.info("Merge.getTableTitleRe: table_name='%s' tournament='%s' table_number='%s'" % (table_name, tournament, table_number))
698 log.info("Merge.getTableTitleRe: returns: '%s'" % (regex))
699 return regex