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