2 # -*- coding: utf-8 -*-
4 # Copyright 2010-2011, Carl Gherardi
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
20 ########################################################################
23 _
= L10n
.get_translation()
25 # This code is based on CarbonToFpdb.py by Matthew Boss
29 # -- No support for tournaments (see also the last item below)
30 # -- Assumes that the currency of ring games is USD
31 # -- No support for a bring-in or for antes (is the latter in fact unnecessary
32 # for hold 'em on Carbon?)
33 # -- hand.maxseats can only be guessed at
34 # -- The last hand in a history file will often be incomplete and is therefore
36 # -- Is behaviour currently correct when someone shows an uncalled hand?
37 # -- Information may be lost when the hand ID is converted from the native form
38 # xxxxxxxx-yyy(y*) to xxxxxxxxyyy(y*) (in principle this should be stored as
39 # a string, but the database does not support this). Is there a possibility
40 # of collision between hand IDs that ought to be distinct?
41 # -- Cannot parse tables that run it twice (nor is this likely ever to be
43 # -- Cannot parse hands in which someone is all in in one of the blinds. Until
44 # this is corrected tournaments will be unparseable
48 from HandHistoryConverter
import *
49 from decimal_wrapper
import Decimal
52 class iPoker(HandHistoryConverter
):
60 re_SplitHands
= re
.compile(r
'</game>')
61 re_TailSplitHands
= re
.compile(r
'(</game>)')
62 re_GameInfo
= re
.compile(r
'<gametype>(?P<GAME>[a-zA-Z0-9 ]+) \$(?P<SB>[.0-9]+)/\$(?P<BB>[.0-9]+)</gametype>', re
.MULTILINE
)
63 re_HandInfo
= re
.compile(r
'gamecode="(?P<HID>[0-9]+)">\s+<general>\s+<startdate>(?P<DATETIME>[-: 0-9]+)</startdate>', re
.MULTILINE
)
64 re_Button
= re
.compile(r
'<players dealer="(?P<BUTTON>[0-9]+)">')
65 re_PlayerInfo
= re
.compile(r
'<player seat="(?P<SEAT>[0-9]+)" name="(?P<PNAME>[^"]+)" chips="\$(?P<CASH>[.0-9]+)" dealer="(?P<DEALTIN>(0|1))" (?P<WIN>win="\$[^"]+") (bet="\$(?P<BET>[^"]+))?', re
.MULTILINE
)
66 re_Board
= re
.compile(r
'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re
.MULTILINE
)
67 re_EndOfHand
= re
.compile(r
'<round id="END_OF_GAME"', re
.MULTILINE
)
69 re_PostSB
= re
.compile(r
'<event sequence="[0-9]+" type="(SMALL_BLIND|RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"/>', re
.MULTILINE
)
70 re_PostBB
= re
.compile(r
'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"/>', re
.MULTILINE
)
71 re_PostBoth
= re
.compile(r
'<event sequence="[0-9]+" type="(RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"/>', re
.MULTILINE
)
72 re_HeroCards
= re
.compile(r
'<cards type="HOLE" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re
.MULTILINE
)
73 re_Action
= re
.compile(r
'<action no="[0-9]+" player="(?P<PNAME>[^"]+)" type="(?P<ATYPE>\d+)" sum="\$(?P<BET>[.0-9]+)"', re
.MULTILINE
)
74 re_Ante
= re
.compile(r
'<action no="[0-9]+" player="(?P<PNAME>[^"]+)" type="(?P<ATYPE>15)" sum="\$(?P<BET>[.0-9]+)" cards="', re
.MULTILINE
)
75 re_ShowdownAction
= re
.compile(r
'<cards type="SHOWN" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re
.MULTILINE
)
76 re_CollectPot
= re
.compile(r
'<winner amount="(?P<POT>[.0-9]+)" uncalled="(true|false)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re
.MULTILINE
)
77 re_SitsOut
= re
.compile(r
'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"/>', re
.MULTILINE
)
78 re_ShownCards
= re
.compile(r
'<cards type="(SHOWN|MUCKED)" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re
.MULTILINE
)
80 def compilePlayerRegexs(self
, hand
):
83 def playerNameFromSeatNo(self
, seatNo
, hand
):
84 # This special function is required because Carbon Poker records
85 # actions by seat number, not by the player's name
86 for p
in hand
.players
:
87 if p
[0] == int(seatNo
):
90 def readSupportedGames(self
):
92 ["ring", "stud", "fl"],
93 #["ring", "hold", "nl"],
94 #["tour", "hold", "nl"]
97 def determineGameType(self
, handText
):
98 """return dict with keys/values:
99 'type' in ('ring', 'tour')
100 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl')
101 'base' in ('hold', 'stud', 'draw')
102 'category' in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi')
103 'hilo' in ('h','l','s')
108 'currency' in ('USD', 'EUR', 'T$', <countrycode>)
109 or None if we fail to get the info """
111 m
= self
.re_GameInfo
.search(handText
)
113 # Information about the game type appears only at the beginning of
114 # a hand history file; hence it is not supplied with the second
115 # and subsequent hands. In these cases we use the value previously
120 except AttributeError:
121 tmp
= handText
[0:100]
122 log
.error(_("Unable to recognise gametype from: '%s'") % tmp
)
123 log
.error("determineGameType: " + _("Raising FpdbParseError"))
124 raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp
)
128 #print "DEBUG: m.groupdict(): %s" % mg
130 limits
= { 'No Limit':'nl', 'Limit':'fl' }
131 games
= { # base, category
132 '7 Card Stud L' : ('stud','studhilo'),
136 self
.info
['limitType'] = limits
[mg
['LIMIT']]
137 self
.info
['limitType'] = 'fl'
139 (self
.info
['base'], self
.info
['category']) = games
[mg
['GAME']]
141 self
.info
['sb'] = mg
['SB']
143 self
.info
['bb'] = mg
['BB']
144 if mg
['GAME'] == 'Holdem Tournament':
145 self
.info
['type'] = 'tour'
146 self
.info
['currency'] = 'T$'
148 self
.info
['type'] = 'ring'
149 self
.info
['currency'] = 'USD'
153 def readHandInfo(self
, hand
):
154 m
= self
.re_HandInfo
.search(hand
.handText
)
156 logging
.error(_("No match in readHandInfo: '%s'") % hand
.handText
[0:100])
157 logging
.info(hand
.handText
)
158 raise FpdbParseError(_("No match in readHandInfo: '%s'") % hand
.handText
[0:100])
160 #print "DEBUG: m.groupdict(): %s" % mg
161 hand
.handid
= m
.group('HID')
162 #hand.tablename = m.group('TABLE')[:-1]
164 hand
.startTime
= datetime
.datetime
.strptime(m
.group('DATETIME'), '%Y-%m-%d %H:%M:%S')
166 def readPlayerStacks(self
, hand
):
167 print "DEBUG: readPlayerStacks"
168 m
= self
.re_PlayerInfo
.finditer(hand
.handText
)
171 #print "DEBUG: re_PlayerInfo: %s" %ag
172 seatno
= int(a
.group('SEAT'))
173 # It may be necessary to adjust 'hand.maxseats', which is an
174 # educated guess, starting with 2 (indicating a heads-up table) and
175 # adjusted upwards in steps to 6, then 9, then 10. An adjustment is
176 # made whenever a player is discovered whose seat number is
177 # currently above the maximum allowable for the table.
178 if seatno
>= hand
.maxseats
:
186 hand
.addPlayer(seatno
, a
.group('PNAME'), a
.group('CASH'))
188 def markStreets(self
, hand
):
189 if hand
.gametype
['base'] in ('stud'):
190 m
= re
.search(r
'(?P<ANTES>.+(?=<round no="2">)|.+)'
191 r
'(<round no="2">(?P<THIRD>.+(?=<round no="3">)|.+))?'
192 r
'(<round no="3">(?P<FOURTH>.+(?=<round no="4">)|.+))?'
193 r
'(<round no="4">(?P<FIFTH>.+(?=<round no="5">)|.+))?'
194 r
'(<round no="5">(?P<SIXTH>.+(?=<round no="6">)|.+))?'
195 r
'(<round no="6">(?P<SEVENTH>.+))?', hand
.handText
,re
.DOTALL
)
199 def readCommunityCards(self
, hand
, street
):
200 m
= self
.re_Board
.search(hand
.streets
[street
])
202 hand
.setCommunityCards(street
, m
.group('CARDS').split(','))
203 elif street
in ('TURN','RIVER'):
204 hand
.setCommunityCards(street
, [m
.group('CARDS').split(',')[-1]])
206 def readAntes(self
, hand
):
207 m
= self
.re_Ante
.finditer(hand
.handText
)
209 #print "DEBUG: addAnte(%s, %s)" %(a.group('PNAME'), a.group('BET'))
210 hand
.addAnte(a
.group('PNAME'), a
.group('BET'))
212 def readBringIn(self
, hand
):
215 def readBlinds(self
, hand
):
216 m
= self
.re_PostSB
.search(hand
.handText
)
217 hand
.addBlind(m
.group('PNAME'), 'small blind', m
.group('SB'))
218 for a
in self
.re_PostBB
.finditer(hand
.handText
):
219 hand
.addBlind(m
.group('PNAME'), 'big blind', a
.group('BB'))
220 #for a in self.re_PostBoth.finditer(hand.handText):
222 def readButton(self
, hand
):
223 hand
.buttonpos
= int(self
.re_Button
.search(hand
.handText
).group('BUTTON'))
225 def readHeroCards(self
, hand
):
226 m
= self
.re_HeroCards
.search(hand
.handText
)
228 hand
.hero
= self
.playerNameFromSeatNo(m
.group('PSEAT'), hand
)
229 cards
= m
.group('CARDS').split(',')
230 hand
.addHoleCards('PREFLOP', hand
.hero
, closed
=cards
, shown
=False,
231 mucked
=False, dealt
=True)
233 def readAction(self
, hand
, street
):
234 logging
.debug("readAction (%s)" % street
)
235 m
= self
.re_Action
.finditer(hand
.streets
[street
])
237 ag
= action
.groupdict()
238 #print "DEBUG: action.groupdict: %s" % ag
239 logging
.debug("%s %s" % (action
.group('ATYPE'),
241 if action
.group('ATYPE') == 'RAISE': # Still no example for raise (i think?)
242 hand
.addCallandRaise(street
, player
, action
.group('BET'))
243 elif action
.group('ATYPE') == '3': # Believe this is 'call'
244 #print "DEBUG: addCall(%s, %s, %s)" %(street, action.group('PNAME'), action.group('BET'))
245 hand
.addCall(street
, action
.group('PNAME'), action
.group('BET'))
246 elif action
.group('ATYPE') == '5':
247 #print "DEBUG: addBet(%s, %s, %s)" %(street, action.group('PNAME'), action.group('BET'))
248 hand
.addBet(street
, action
.group('PNAME'), action
.group('BET'))
249 elif action
.group('ATYPE') == '0': # Belive this is 'fold'
250 #print "DEBUG: addFold(%s, %s)" %(street, action.group('PNAME'))
251 hand
.addFold(street
, action
.group('PNAME'))
252 elif action
.group('ATYPE') == '4':
253 #print "DEBUG: addCheck(%s, %s)" %(street, action.group('PNAME'))
254 hand
.addCheck(street
, action
.group('PNAME'))
255 #elif action.group('ATYPE') == 'ALL_IN':
256 # hand.addAllIn(street, player, action.group('BET'))
257 elif action
.group('ATYPE') == '16': #BringIn
258 #print "DEBUG: addBringIn(%s, %s)" %(action.group('PNAME'), action.group('BET'))
259 hand
.addBringIn(action
.group('PNAME'), action
.group('BET'))
261 logging
.error(_("DEBUG:") + " " + _("Unimplemented %s: '%s' '%s'") % ("readAction", action
.group('PNAME'), action
.group('ATYPE')))
263 def readShowdownActions(self
, hand
):
264 for shows
in self
.re_ShowdownAction
.finditer(hand
.handText
):
265 cards
= shows
.group('CARDS').split(',')
266 hand
.addShownCards(cards
,
267 self
.playerNameFromSeatNo(shows
.group('PSEAT'),
270 def readCollectPot(self
, hand
):
271 pots
= [Decimal(0) for n
in range(hand
.maxseats
)]
272 for m
in self
.re_CollectPot
.finditer(hand
.handText
):
273 pots
[int(m
.group('PSEAT'))] += Decimal(m
.group('POT'))
274 # Regarding the processing logic for "committed", see Pot.end() in
276 committed
= sorted([(v
,k
) for (k
,v
) in hand
.pot
.committed
.items()])
277 for p
in range(hand
.maxseats
):
278 pname
= self
.playerNameFromSeatNo(p
, hand
)
279 if committed
[-1][1] == pname
:
280 pots
[p
] -= committed
[-1][0] - committed
[-2][0]
282 hand
.addCollectPot(player
=pname
, pot
=pots
[p
])
284 def readShownCards(self
, hand
):
285 for m
in self
.re_ShownCards
.finditer(hand
.handText
):
286 cards
= m
.group('CARDS').split(',')
287 hand
.addShownCards(cards
=cards
, player
=self
.playerNameFromSeatNo(m
.group('PSEAT'), hand
))