Coverage for BetfairToFpdb.py: 0%
143 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-14 11:07 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-14 11:07 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2008-2011, Carl Gherardi
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
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
19########################################################################
22# import L10n
23# _ = L10n.get_translation()
25from HandHistoryConverter import HandHistoryConverter, FpdbParseError
26import re
27import logging
28import datetime
30log = logging.getLogger("parser")
32# Betfair HH format
35class Betfair(HandHistoryConverter):
36 sitename = "Betfair"
37 filetype = "text"
38 codepage = "cp1252"
39 siteId = 7 # Needs to match id entry in Sites database
41 # Static regexes
42 re_GameInfo = re.compile(
43 "^(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAME>(Texas Hold'em|Omaha Hi|Omaha|Razz))",
44 re.MULTILINE,
45 )
46 re_Identify = re.compile("\*{5}\sBetfair\sPoker\sHand\sHistory\sfor\sGame\s\d+\s")
47 re_SplitHands = re.compile(r"\n\n+")
48 re_HandInfo = re.compile(
49 "\*\*\*\*\* Betfair Poker Hand History for Game (?P<HID>[0-9]+) \*\*\*\*\*\n(?P<LIMIT>NL|PL|) (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>(Texas Hold'em|Omaha|Razz)) - (?P<DATETIME>[a-zA-Z]+, [a-zA-Z]+ \d+, \d\d:\d\d:\d\d GMT \d\d\d\d)\nTable (?P<TABLE>[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P<BUTTON>[0-9]+)",
50 re.MULTILINE,
51 )
52 re_Button = re.compile(r"^Seat (?P<BUTTON>\d+) is the button", re.MULTILINE)
53 re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)")
54 re_Board = re.compile(r"\[ (?P<CARDS>.+) \]")
56 def compilePlayerRegexs(self, hand):
57 players = set([player[1] for player in hand.players])
58 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
59 # we need to recompile the player regexs.
60 self.compiledPlayers = players
61 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
62 log.debug("player_re: " + player_re)
63 self.re_PostSB = re.compile("^%s posts small blind \[\$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
64 self.re_PostBB = re.compile("^%s posts big blind \[\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
65 self.re_Antes = re.compile("^%s antes asdf sadf sadf" % player_re, re.MULTILINE)
66 self.re_BringIn = re.compile("^%s antes asdf sadf sadf" % player_re, re.MULTILINE)
67 self.re_PostBoth = re.compile(
68 "^%s posts small \& big blinds \[\$?(?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE
69 )
70 self.re_HeroCards = re.compile("^Dealt to %s \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
71 self.re_Action = re.compile(
72 "^%s (?P<ATYPE>bets|checks|raises to|raises|calls|folds)(\s\[\$(?P<BET>[.\d]+)\])?" % player_re,
73 re.MULTILINE,
74 )
75 self.re_ShowdownAction = re.compile("^%s shows \[ (?P<CARDS>.*) \]" % player_re, re.MULTILINE)
76 self.re_CollectPot = re.compile(
77 "^%s wins \$(?P<POT>[.\d]+) (.*?\[ (?P<CARDS>.*?) \])?" % player_re, re.MULTILINE
78 )
79 self.re_SitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
80 self.re_ShownCards = re.compile(r"%s (?P<SEAT>[0-9]+) (?P<CARDS>adsfasdf)" % player_re, re.MULTILINE)
82 def readSupportedGames(self):
83 return [["ring", "hold", "nl"], ["ring", "hold", "pl"]]
85 def determineGameType(self, handText):
86 info = {"type": "ring"}
88 m = self.re_GameInfo.search(handText)
89 if not m:
90 tmp = handText[0:200]
91 log.error(("BetfairToFpdb.determineGameType: '%s'") % tmp)
92 raise FpdbParseError
94 mg = m.groupdict()
96 # translations from captured groups to our info strings
97 limits = {"NL": "nl", "PL": "pl", "Limit": "fl"}
98 games = { # base, category
99 "Texas Hold'em": ("hold", "holdem"),
100 "Omaha Hi": ("hold", "omahahi"),
101 "Omaha": ("hold", "omahahi"),
102 "Razz": ("stud", "razz"),
103 "7 Card Stud": ("stud", "studhi"),
104 }
105 currencies = {" €": "EUR", "$": "USD", "": "T$"}
106 if "LIMIT" in mg:
107 info["limitType"] = limits[mg["LIMIT"]]
108 if "GAME" in mg:
109 (info["base"], info["category"]) = games[mg["GAME"]]
110 if "SB" in mg:
111 info["sb"] = mg["SB"]
112 if "BB" in mg:
113 info["bb"] = mg["BB"]
114 if "CURRENCY" in mg:
115 info["currency"] = currencies[mg["CURRENCY"]]
117 return info
119 def readHandInfo(self, hand):
120 m = self.re_HandInfo.search(hand.handText)
121 if m is None:
122 tmp = hand.handText[0:200]
123 log.error(("BetfairToFpdb.readHandInfo: '%s'") % tmp)
124 raise FpdbParseError
125 log.debug("HID %s, Table %s" % (m.group("HID"), m.group("TABLE")))
126 hand.handid = m.group("HID")
127 hand.tablename = m.group("TABLE")
128 hand.startTime = datetime.datetime.strptime(m.group("DATETIME"), "%A, %B %d, %H:%M:%S GMT %Y")
129 # hand.buttonpos = int(m.group('BUTTON'))
131 def readPlayerStacks(self, hand):
132 m = self.re_PlayerInfo.finditer(hand.handText)
133 for a in m:
134 hand.addPlayer(int(a.group("SEAT")), a.group("PNAME"), a.group("CASH"))
136 # Shouldn't really dip into the Hand object, but i've no idea how to tell the length of iter m
137 if len(hand.players) < 2:
138 log.info(("Less than 2 players found in hand %s.") % hand.handid)
140 def markStreets(self, hand):
141 m = re.search(
142 r"\*\* Dealing down cards \*\*(?P<PREFLOP>.+(?=\*\* Dealing Flop \*\*)|.+)"
143 r"(\*\* Dealing Flop \*\*(?P<FLOP> \[ \S\S, \S\S, \S\S \].+(?=\*\* Dealing Turn \*\*)|.+))?"
144 r"(\*\* Dealing Turn \*\*(?P<TURN> \[ \S\S \].+(?=\*\* Dealing River \*\*)|.+))?"
145 r"(\*\* Dealing River \*\*(?P<RIVER> \[ \S\S \].+))?",
146 hand.handText,
147 re.DOTALL,
148 )
150 hand.addStreets(m)
152 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
153 if street in (
154 "FLOP",
155 "TURN",
156 "RIVER",
157 ): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
158 m = self.re_Board.search(hand.streets[street])
159 hand.setCommunityCards(street, m.group("CARDS").split(", "))
161 def readBlinds(self, hand):
162 try:
163 m = self.re_PostSB.search(hand.handText)
164 hand.addBlind(m.group("PNAME"), "small blind", m.group("SB"))
165 except AttributeError:
166 hand.addBlind(None, None, None)
167 for a in self.re_PostBB.finditer(hand.handText):
168 hand.addBlind(a.group("PNAME"), "big blind", a.group("BB"))
169 for a in self.re_PostBoth.finditer(hand.handText):
170 hand.addBlind(a.group("PNAME"), "small & big blinds", a.group("SBBB"))
172 def readAntes(self, hand):
173 log.debug("reading antes")
174 m = self.re_Antes.finditer(hand.handText)
175 for player in m:
176 log.debug("hand.addAnte(%s,%s)" % (player.group("PNAME"), player.group("ANTE")))
177 hand.addAnte(player.group("PNAME"), player.group("ANTE"))
179 def readBringIn(self, hand):
180 m = self.re_BringIn.search(hand.handText, re.DOTALL)
181 if m:
182 log.debug(("Player bringing in: %s for %s") % (m.group("PNAME"), m.group("BRINGIN")))
183 hand.addBringIn(m.group("PNAME"), m.group("BRINGIN"))
184 else:
185 log.warning(("No bringin found"))
187 def readButton(self, hand):
188 hand.buttonpos = int(self.re_Button.search(hand.handText).group("BUTTON"))
190 def readHoleCards(self, hand):
191 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
192 # we need to grab hero's cards
193 for street in ("PREFLOP", "DEAL"):
194 if street in list(hand.streets.keys()):
195 m = self.re_HeroCards.finditer(hand.streets[street])
196 for found in m:
197 hand.hero = found.group("PNAME")
198 newcards = [c.strip() for c in found.group("CARDS").split(",")]
199 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
201 def readStudPlayerCards(self, hand, street):
202 # balh blah blah
203 pass
205 def readAction(self, hand, street):
206 m = self.re_Action.finditer(hand.streets[street])
207 for action in m:
208 if action.group("ATYPE") == "folds":
209 hand.addFold(street, action.group("PNAME"))
210 elif action.group("ATYPE") == "checks":
211 hand.addCheck(street, action.group("PNAME"))
212 elif action.group("ATYPE") == "calls":
213 hand.addCall(street, action.group("PNAME"), action.group("BET"))
214 elif action.group("ATYPE") == "bets":
215 hand.addBet(street, action.group("PNAME"), action.group("BET"))
216 elif action.group("ATYPE") == "raises to":
217 hand.addRaiseTo(street, action.group("PNAME"), action.group("BET"))
218 else:
219 log.debug(
220 ("DEBUG:")
221 + " "
222 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE"))
223 )
225 def readShowdownActions(self, hand):
226 for shows in self.re_ShowdownAction.finditer(hand.handText):
227 cards = shows.group("CARDS")
228 cards = cards.split(", ")
229 hand.addShownCards(cards, shows.group("PNAME"))
231 def readCollectPot(self, hand):
232 for m in self.re_CollectPot.finditer(hand.handText):
233 hand.addCollectPot(player=m.group("PNAME"), pot=m.group("POT"))
235 def readShownCards(self, hand):
236 for m in self.re_ShownCards.finditer(hand.handText):
237 if m.group("CARDS") is not None:
238 cards = m.group("CARDS")
239 cards = cards.split(", ")
240 hand.addShownCards(cards=None, player=m.group("PNAME"), holeandboard=cards)