diff --git a/gfx/Table.png b/gfx/Table.png new file mode 100644 index 00000000..60053098 Binary files /dev/null and b/gfx/Table.png differ diff --git a/pyfpdb/AbsoluteToFpdb.py b/pyfpdb/AbsoluteToFpdb.py index 145a0971..d55f4710 100755 --- a/pyfpdb/AbsoluteToFpdb.py +++ b/pyfpdb/AbsoluteToFpdb.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- # # Copyright 2008-2010, Carl Gherardi -# +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -18,23 +18,16 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +#Note that this filter also supports UltimateBet, they are both owned by the same company and form the Cereus Network + +import L10n +_ = L10n.get_translation() + # TODO: I have no idea if AP has multi-currency options, i just copied the regex out of Everleaf converter for the currency symbols.. weeeeee - Eric import sys import logging from HandHistoryConverter import * -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - # Class for converting Absolute HH format. class Absolute(HandHistoryConverter): @@ -45,31 +38,55 @@ class Absolute(HandHistoryConverter): codepage = "cp1252" siteid = 8 HORSEHand = False - - # Static regexes - re_SplitHands = re.compile(r"\n\n\n+") - re_TailSplitHands = re.compile(r"(\n\n\n+)") -#Stage #1571362962: Holdem No Limit $0.02 - 2009-08-05 15:24:06 (ET) -#Table: TORONTO AVE (Real Money) Seat #6 is the dealer -#Seat 6 - FETS63 ($0.75 in chips) -#Board [10s 5d Kh Qh 8c] - re_GameInfo = re.compile(ur"""^Stage #(C?[0-9]+):\s+ - (?PHoldem|Seven\sCard\sHi\/L|HORSE) - (?:\s\(1\son\s1\)|)?\s+? - (?PNo Limit|Pot\sLimit|Normal|)?\s? - (?P\$|\s€|) - (?P[.0-9]+)/?(?:\$|\s€|)(?P[.0-9]+)? - """, re.MULTILINE|re.VERBOSE) - re_HorseGameInfo = re.compile(ur"^Game Type: (?PLimit) (?PHoldem)", re.MULTILINE) - # TODO: can set max seats via (1 on 1) to a known 2 .. - re_HandInfo = re.compile(ur"^Stage #C?(?P[0-9]+): .*(?P\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d).*\n(Table: (?P.*) \(Real Money\))?", re.MULTILINE) - re_TableFromFilename = re.compile(ur".*IHH([0-9]+) (?P
.*) -") # on HORSE STUD games, the table name isn't in the hand info! - re_Button = re.compile(ur"Seat #(?P
.*?)\ \(Real\ Money\))? + """, re.MULTILINE|re.VERBOSE|re.DOTALL) + + re_HorseGameInfo = re.compile( + ur"^Game Type: (?PLimit) (?PHoldem)", + re.MULTILINE) + + re_HandInfo = re_GameInfo + + # on HORSE STUD games, the table name isn't in the hand info! + re_RingInfoFromFilename = re.compile(ur".*IHH([0-9]+) (?P
.*) -") + re_TrnyInfoFromFilename = re.compile( + ur"IHH\s?([0-9]+) (?P.*) "\ + ur"ID (?P\d+)\s?(\((?P
\d+)\))? .* "\ + ur"(?:\$|\s€|)(?P[0-9.]+)\s*\+\s*(?:\$|\s€|)(?P[0-9.]+)" + ) + + # TODO: that's not the right way to match for "dead" dealer is it? + re_Button = re.compile(ur"Seat #(?P
[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P
[ a-zA-Z0-9]+) \d-max \(Real Money\)\nSeat (?P
[\'\w\s]+)\s\[\d+\]\s\( ( (?PNO_LIMIT|Limit|LIMIT|Pot\sLimit)\s - (?PTEXAS_HOLDEM|RAZZ)\s + (?PTEXAS_HOLDEM|OMAHA_HI|SEVEN_CARD_STUD|SEVEN_CARD_STUD_HI_LO|RAZZ|FIVE_CARD_DRAW)\s (%(LS)s)?(?P[.0-9]+)/ (%(LS)s)?(?P[.0-9]+) )? @@ -111,14 +102,12 @@ class OnGame(HandHistoryConverter): re_DateTime = re.compile(""" [a-zA-Z]{3}\s (?P[a-zA-Z]{3})\s - (?P[0-9]{2})\s + (?P[0-9]+)\s (?P[0-9]+):(?P[0-9]+):(?P[0-9]+)\s (?P\w+[-+]\d+)\s (?P[0-9]{4}) """, re.MULTILINE|re.VERBOSE) - # self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') - #Seat 1: .Lucchess ($4.17 in chips) #Seat 1: phantomaas ($27.11) #Seat 5: mleo17 ($9.37) @@ -138,10 +127,11 @@ class OnGame(HandHistoryConverter): player_re = "(?P" + "|".join(map(re.escape, players)) + ")" subst = {'PLYR': player_re, 'CUR': self.sym[hand.gametype['currency']]} self.re_PostSB = re.compile('(?P.*) posts small blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) - self.re_PostBB = re.compile('\), (?P.*) posts big blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_PostBB = re.compile('(?P.*) posts big blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) - self.re_PostBoth = re.compile('.*\n(?P.*): posts small \& big blinds \( (%(CUR)s)?(?P[\.0-9]+)\)' % subst) + self.re_PostBoth = re.compile('(?P.*): posts small \& big blind \( (%(CUR)s)?(?P[\.0-9]+)\)' % subst) + self.re_PostDead = re.compile('(?P.*) posts dead blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) self.re_HeroCards = re.compile('Dealing\sto\s%(PLYR)s:\s\[(?P.*)\]' % subst) #lopllopl checks, Eurolll checks, .Lucchess checks. @@ -165,6 +155,8 @@ class OnGame(HandHistoryConverter): return [ ["ring", "hold", "fl"], ["ring", "hold", "nl"], + ["ring", "stud", "fl"], + ["ring", "draw", "fl"], ] def determineGameType(self, handText): @@ -217,21 +209,28 @@ class OnGame(HandHistoryConverter): #hand.startTime = time.strptime(m.group('DATETIME'), "%a %b %d %H:%M:%S GMT%z %Y") # Stupid library doesn't seem to support %z (http://docs.python.org/library/time.html?highlight=strptime#time.strptime) # So we need to re-interpret te string to be useful - m1 = self.re_DateTime.finditer(info[key]) - for a in m1: + a = self.re_DateTime.search(info[key]) + if a: datetimestr = "%s/%s/%s %s:%s:%s" % (a.group('Y'),a.group('M'), a.group('D'), a.group('H'),a.group('MIN'),a.group('S')) tzoffset = a.group('OFFSET') - # TODO: Manually adjust time against OFFSET + else: + datetimestr = "2010/Jan/01 01:01:01" + log.error(_("readHandInfo: DATETIME not matched: '%s'" % info[key])) + print "DEBUG: readHandInfo: DATETIME not matched: '%s'" % info[key] + # TODO: Manually adjust time against OFFSET hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%b/%d %H:%M:%S") # also timezone at end, e.g. " ET" hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, tzoffset, "UTC") if key == 'HID': hand.handid = info[key] + # Need to remove non-alphanumerics for MySQL + hand.handid = hand.handid.replace('R','') + hand.handid = hand.handid.replace('-','') if key == 'TABLE': hand.tablename = info[key] # TODO: These hand.buttonpos = 1 - hand.maxseats = 10 + hand.maxseats = None # Set to None - Hand.py will guessMaxSeats() hand.mixed = None def readPlayerStacks(self, hand): @@ -241,18 +240,24 @@ class OnGame(HandHistoryConverter): hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) def markStreets(self, hand): - # PREFLOP = ** Dealing down cards ** - # This re fails if, say, river is missing; then we don't get the ** that starts the river. - #m = re.search('(\*\* Dealing down cards \*\*\n)(?P.*?\n\*\*)?( Dealing Flop \*\* \[ (?P\S\S), (?P\S\S), (?P\S\S) \])?(?P.*?\*\*)?( Dealing Turn \*\* \[ (?P\S\S) \])?(?P.*?\*\*)?( Dealing River \*\* \[ (?P\S\S) \])?(?P.*)', hand.string,re.DOTALL) - - #if hand.gametype['base'] in ("hold"): - #elif hand.gametype['base'] in ("stud"): - #elif hand.gametype['base'] in ("draw"): - # only holdem so far: - m = re.search(r"pocket cards(?P.+(?= Dealing flop )|.+(?=Summary))" + if hand.gametype['base'] in ("hold"): + m = re.search(r"pocket cards(?P.+(?= Dealing flop )|.+(?=Summary))" r"( Dealing flop (?P\[\S\S, \S\S, \S\S\].+(?= Dealing turn)|.+(?=Summary)))?" r"( Dealing turn (?P\[\S\S\].+(?= Dealing river)|.+(?=Summary)))?" r"( Dealing river (?P\[\S\S\].+(?=Summary)))?", hand.handText, re.DOTALL) + elif hand.gametype['base'] in ("stud"): + m = re.search(r"(?P.+(?=Dealing pocket cards)|.+)" + r"(Dealing pocket cards(?P.+(?=Dealing 4th street)|.+))?" + r"(Dealing 4th street(?P.+(?=Dealing 5th street)|.+))?" + r"(Dealing 5th street(?P.+(?=Dealing 6th street)|.+))?" + r"(Dealing 6th street(?P.+(?=Dealing river)|.+))?" + r"(Dealing river(?P.+))?", hand.handText,re.DOTALL) + elif hand.gametype['base'] in ("draw"): + m = re.search(r"(?P.+(?=Dealing pocket cards)|.+)" + r"(Dealing pocket cards(?P.+(?=\*\*\* FIRST DRAW \*\*\*)|.+))?" + r"(\*\*\* FIRST DRAW \*\*\*(?P.+(?=\*\*\* SECOND DRAW \*\*\*)|.+))?" + r"(\*\*\* SECOND DRAW \*\*\*(?P.+(?=\*\*\* THIRD DRAW \*\*\*)|.+))?" + r"(\*\*\* THIRD DRAW \*\*\*(?P.+))?", hand.handText,re.DOTALL) hand.addStreets(m) @@ -280,7 +285,6 @@ class OnGame(HandHistoryConverter): hand.setCommunityCards(street, m.group('CARDS').split(', ')) def readBlinds(self, hand): - #log.debug( _("readBlinds starting, hand=") + "\n["+hand.handText+"]" ) try: m = self.re_PostSB.search(hand.handText) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) @@ -289,6 +293,9 @@ class OnGame(HandHistoryConverter): #hand.addBlind(None, None, None) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) + for a in self.re_PostDead.finditer(hand.handText): + #print "DEBUG: Found dead blind: addBlind(%s, 'secondsb', %s)" %(a.group('PNAME'), a.group('DEAD')) + hand.addBlind(a.group('PNAME'), 'secondsb', a.group('DEAD')) for a in self.re_PostBoth.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) @@ -311,10 +318,10 @@ class OnGame(HandHistoryConverter): for street in ('PREFLOP', 'DEAL'): if street in hand.streets.keys(): m = self.re_HeroCards.finditer(hand.streets[street]) - for found in m: - hand.hero = found.group('PNAME') - newcards = found.group('CARDS').split(', ') - hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) + for found in m: + hand.hero = found.group('PNAME') + newcards = found.group('CARDS').split(', ') + hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) def readAction(self, hand, street): m = self.re_Action.finditer(hand.streets[street]) diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index 19139278..b8be3d2a 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -15,22 +15,13 @@ #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. +import L10n +_ = L10n.get_translation() + import sys from optparse import OptionParser # http://docs.python.org/library/optparse.html -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - def fpdb_options(): """Process command line options for fpdb and HUD_main.""" @@ -47,9 +38,6 @@ def fpdb_options(): parser.add_option("-r", "--rerunPython", action="store_true", help=_("Indicates program was restarted with a different path (only allowed once).")) - parser.add_option("-i", "--infile", - dest="infile", default="Slartibartfast", - help=_("Input file")) parser.add_option("-k", "--konverter", dest="hhc", default="PokerStarsToFpdb", help=_("Module name for Hand History Converter")) @@ -62,6 +50,15 @@ def fpdb_options(): help = _("Print version information and exit.")) parser.add_option("-u", "--usage", action="store_true", dest="usage", default=False, help=_("Print some useful one liners")) + # The following options are used for SplitHandHistory.py + parser.add_option("-f", "--file", dest="filename", metavar="FILE", default=None, + help=_("Input file in quiet mode")) + parser.add_option("-o", "--outpath", dest="outpath", metavar="FILE", default=None, + help=_("Input out path in quiet mode")) + parser.add_option("-a", "--archive", action="store_true", dest="archive", default=False, + help=_("File to be split is a PokerStars or Full Tilt Poker archive file")) + parser.add_option("-n", "--numhands", dest="hands", default="100", type="int", + help=_("How many hands do you want saved to each file. Default is 100")) (options, argv) = parser.parse_args() diff --git a/pyfpdb/PartyPokerToFpdb.py b/pyfpdb/PartyPokerToFpdb.py index f61b5b7b..51eb3881 100755 --- a/pyfpdb/PartyPokerToFpdb.py +++ b/pyfpdb/PartyPokerToFpdb.py @@ -18,21 +18,13 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() + import sys from collections import defaultdict -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - +from Configuration import LOCALE_ENCODING from Exceptions import FpdbParseError from HandHistoryConverter import * @@ -50,7 +42,7 @@ class FpdbParseError(FpdbParseError): class PartyPoker(HandHistoryConverter): sitename = "PartyPoker" - codepage = "cp1252" + codepage = "utf8" siteId = 9 filetype = "text" sym = {'USD': "\$", } @@ -61,10 +53,10 @@ class PartyPoker(HandHistoryConverter): re_GameInfoRing = re.compile(""" (?P\$|)\s*(?P[.,0-9]+)([.,0-9/$]+)?\s*(?:USD)?\s* (?P(NL|PL|))\s* - (?P(Texas\ Hold\'em|Omaha)) + (?P(Texas\ Hold\'em|Omaha|7 Card Stud Hi-Lo)) \s*\-\s* (?P.+) - """, re.VERBOSE) + """, re.VERBOSE | re.UNICODE) re_GameInfoTrny = re.compile(""" (?P(NL|PL|))\s* (?P(Texas\ Hold\'em|Omaha))\s+ @@ -78,7 +70,7 @@ class PartyPoker(HandHistoryConverter): \) \s*\-\s* (?P.+) - """, re.VERBOSE) + """, re.VERBOSE | re.UNICODE) re_Hid = re.compile("^Game \#(?P\d+) starts.") re_PlayerInfo = re.compile(""" @@ -189,6 +181,10 @@ class PartyPoker(HandHistoryConverter): return self._gameType return self._gameType + @staticmethod + def decode_hand_text(handText): + return handText.encode("latin1").decode(LOCALE_ENCODING) + def determineGameType(self, handText): """inspect the handText and return the gametype dict @@ -196,9 +192,14 @@ class PartyPoker(HandHistoryConverter): {'limitType': xxx, 'base': xxx, 'category': xxx}""" info = {} + handText = self.decode_hand_text(handText) m = self._getGameType(handText) m_20BBmin = self.re_20BBmin.search(handText) if m is None: + tmp = handText[0:100] + log.error(_("determineGameType: Unable to recognise gametype from: '%s'") % tmp) + log.error(_("determineGameType: Raising FpdbParseError")) + raise FpdbParseError(_("Unable to recognise gametype from: '%s'") % tmp) return None mg = m.groupdict() @@ -207,6 +208,7 @@ class PartyPoker(HandHistoryConverter): games = { # base, category "Texas Hold'em" : ('hold','holdem'), 'Omaha' : ('hold','omahahi'), + "7 Card Stud Hi-Lo" : ('stud','studhi'), } currencies = { '$':'USD', '':'T$' } @@ -251,6 +253,10 @@ class PartyPoker(HandHistoryConverter): def readHandInfo(self, hand): + # we should redecode handtext here (as it imposible to it above) + # if you know more accurate way to do it - tell me + hand.handText = self.decode_hand_text(hand.handText) + info = {} try: info.update(self.re_Hid.search(hand.handText).groupdict()) @@ -299,10 +305,15 @@ class PartyPoker(HandHistoryConverter): #Saturday, July 25, 07:53:52 EDT 2009 #Thursday, July 30, 21:40:41 MSKS 2009 #Sunday, October 25, 13:39:07 MSK 2009 - m2 = re.search("\w+, (?P\w+) (?P\d+), (?P\d+):(?P\d+):(?P\d+) (?P[A-Z]+) (?P\d+)", info[key]) - # we cant use '%B' due to locale problems + m2 = re.search( + r"\w+,\s+(?P\w+)\s+(?P\d+),\s+(?P\d+):(?P\d+):(?P\d+)\s+(?P[A-Z]+)\s+(?P\d+)", + info[key], + re.UNICODE + ) months = ['January', 'February', 'March', 'April','May', 'June', 'July','August','September','October','November','December'] + if m2.group('M') not in months: + raise FpdbParseError("Only english hh is supported", hid=info["HID"]) month = months.index(m2.group('M')) + 1 datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S')) hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") @@ -353,10 +364,54 @@ class PartyPoker(HandHistoryConverter): def readPlayerStacks(self, hand): log.debug("readPlayerStacks") m = self.re_PlayerInfo.finditer(hand.handText) - players = [] + maxKnownStack = 0 + zeroStackPlayers = [] for a in m: - hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), - clearMoneyString(a.group('CASH'))) + if a.group('CASH') > '0': + #record max known stack for use with players with unknown stack + maxKnownStack = max(a.group('CASH'),maxKnownStack) + hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), clearMoneyString(a.group('CASH'))) + else: + #zero stacked players are added later + zeroStackPlayers.append([int(a.group('SEAT')), a.group('PNAME'), clearMoneyString(a.group('CASH'))]) + + if hand.gametype['type'] == 'ring': + #finds first vacant seat after an exact seat + def findFirstEmptySeat(startSeat): + while startSeat in occupiedSeats: + if startSeat >= hand.maxseats: + startSeat = 0 + startSeat += 1 + return startSeat + + re_JoiningPlayers = re.compile(r"(?P.*) has joined the table") + re_BBPostingPlayers = re.compile(r"(?P.*) posts big blind") + + match_JoiningPlayers = re_JoiningPlayers.findall(hand.handText) + match_BBPostingPlayers = re_BBPostingPlayers.findall(hand.handText) + + #add every player with zero stack, but: + #if a zero stacked player is just joined the table in this very hand then set his stack to maxKnownStack + for p in zeroStackPlayers: + if p[1] in match_JoiningPlayers: + p[2] = clearMoneyString(maxKnownStack) + hand.addPlayer(p[0],p[1],p[2]) + + seatedPlayers = list([(f[1]) for f in hand.players]) + + #it works for all known cases as of 2010-09-28 + #should be refined with using match_ActivePlayers instead of match_BBPostingPlayers + #as a leaving and rejoining player could be active without posting a BB (sample HH needed) + unseatedActivePlayers = list(set(match_BBPostingPlayers) - set(seatedPlayers)) + + if unseatedActivePlayers: + for player in unseatedActivePlayers: + previousBBPoster = match_BBPostingPlayers[match_BBPostingPlayers.index(player)-1] + previousBBPosterSeat = dict([(f[1], f[0]) for f in hand.players])[previousBBPoster] + occupiedSeats = list([(f[0]) for f in hand.players]) + occupiedSeats.sort() + newPlayerSeat = findFirstEmptySeat(previousBBPosterSeat) + hand.addPlayer(newPlayerSeat,player,clearMoneyString(maxKnownStack)) def markStreets(self, hand): m = re.search( @@ -507,7 +562,6 @@ class PartyPoker(HandHistoryConverter): else: return "%s.+Table\s#%s" % (TableName[0], table_number) else: - print 'party', 'getTableTitleRe', table_number return table_name def clearMoneyString(money): diff --git a/pyfpdb/PkrToFpdb.py b/pyfpdb/PkrToFpdb.py index c13dd284..ecb43ae8 100755 --- a/pyfpdb/PkrToFpdb.py +++ b/pyfpdb/PkrToFpdb.py @@ -18,22 +18,12 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() import sys from HandHistoryConverter import * -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - class Pkr(HandHistoryConverter): @@ -115,7 +105,7 @@ class Pkr(HandHistoryConverter): ^%(PLYR)s(?P\sbets|\schecks|\sraises|\scalls|\sfolds)(\sto)? (\s(%(CUR)s)?(?P[.\d]+))? """ % subst, re.MULTILINE|re.VERBOSE) - self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P.*)\]" % player_re, re.MULTILINE) + self.re_ShowdownAction = re.compile(r"^%(PLYR)s shows \[(?P.*)\]" % subst, re.MULTILINE) self.re_CollectPot = re.compile(r"^%(PLYR)s wins %(CUR)s(?P[.\d]+)" % subst, re.MULTILINE) self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE) self.re_ShownCards = re.compile("^Seat (?P[0-9]+): %s (\(.*\) )?(?Pshowed|mucked) \[(?P.*)\].*" % player_re, re.MULTILINE) @@ -239,6 +229,7 @@ class Pkr(HandHistoryConverter): if players.has_key(a.group('PNAME')): pass # Ignore else: + #print "DEBUG: addPlayer(%s, %s, %s)" % (a.group('SEAT'), a.group('PNAME'), a.group('CASH')) hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) players[a.group('PNAME')] = True @@ -335,9 +326,16 @@ class Pkr(HandHistoryConverter): m = self.re_Action.finditer(hand.streets[street]) for action in m: acts = action.groupdict() + #print "DEBUG: readAction: acts: %s" % acts if action.group('ATYPE') == ' raises': hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' calls': + # Amount in hand history is not cumulative + # ie. Player3 calls 0.08 + # Player5 raises to 0.16 + # Player3 calls 0.16 (Doh! he's only calling 0.08 + # TODO: Going to have to write an addCallStoopid() + #print "DEBUG: addCall( %s, %s, None)" %(street,action.group('PNAME')) hand.addCall( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' bets': hand.addBet( street, action.group('PNAME'), action.group('BET') ) @@ -354,9 +352,10 @@ class Pkr(HandHistoryConverter): def readShowdownActions(self, hand): -# TODO: pick up mucks also?? - for shows in self.re_ShowdownAction.finditer(hand.handText): + # TODO: pick up mucks also?? + for shows in self.re_ShowdownAction.finditer(hand.handText): cards = shows.group('CARDS').split(' ') + #print "DEBUG: addShownCards(%s, %s)" %(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME')) def readCollectPot(self,hand): diff --git a/pyfpdb/PokerStarsSummary.py b/pyfpdb/PokerStarsSummary.py index 36ceae48..8ee68712 100644 --- a/pyfpdb/PokerStarsSummary.py +++ b/pyfpdb/PokerStarsSummary.py @@ -17,6 +17,9 @@ """pokerstars-specific summary parsing code""" +import L10n +_ = L10n.get_translation() + from decimal import Decimal import datetime @@ -25,18 +28,6 @@ from HandHistoryConverter import * import PokerStarsToFpdb from TourneySummary import * -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - class PokerStarsSummary(TourneySummary): limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl', 'LIMIT':'fl' } games = { # base, category @@ -57,147 +48,38 @@ class PokerStarsSummary(TourneySummary): 'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8) } - re_SplitGames = re.compile("^PokerStars") + re_SplitTourneys = re.compile("PokerStars Tournament ") re_TourNo = re.compile("\#(?P[0-9]+),") re_TourneyInfo = re.compile(u""" \#(?P[0-9]+),\s (?PNo\sLimit|Limit|LIMIT|Pot\sLimit)\s - (?PHold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s - (?P[ a-zA-Z]+\s)? - (Buy-In:\s\$(?P[.0-9]+)(\/\$(?P[.0-9]+))?\s)? - (?P[0-9]+)\splayers\s - (\$?(?P[.\d]+)\sadded\sto\sthe\sprize\spool\sby\sPokerStars\.com\s)? - (Total\sPrize\sPool:\s\$?(?P[.0-9]+)\s)? + (?PHold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s+ + (?P[ a-zA-Z]+\s+)? + (Buy-In:\s\$(?P[.0-9]+)(\/\$(?P[.0-9]+))?(?P\s%(LEGAL_ISO)s)?\s+)? + (?P[0-9]+)\splayers\s+ + (\$?(?P[.\d]+)\sadded\sto\sthe\sprize\spool\sby\sPokerStars\.com\s+)? + (Total\sPrize\sPool:\s\$?(?P[.0-9]+)(\s%(LEGAL_ISO)s)?\s+)? (Target\sTournament\s.*)? - Tournament\sstarted\s-\s - (?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\-\s]+(?P[0-9]+):(?P[0-9]+):(?P[0-9]+)\s?\(?(?P[A-Z]+)\)\s + Tournament\sstarted\s+(-\s)? + (?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\-\s]+(?P[0-9]+):(?P[0-9]+):(?P[0-9]+)\s?\(?(?P[A-Z]+)\)?\s """ % substitutions ,re.VERBOSE|re.MULTILINE|re.DOTALL) re_Currency = re.compile(u"""(?P[%(LS)s]|FPP)""" % substitutions) - re_Player = re.compile(u"""(?P[0-9]+):\s(?P.*)\s\(.*\),(\s)?(\$(?P[0-9]+\.[0-9]+))?(?Pstill\splaying)?""") + re_Player = re.compile(u"""(?P[0-9]+):\s(?P.*)\s\(.*\),(\s)?(\$(?P[0-9]+\.[0-9]+))?(?Pstill\splaying)?((?PTournament\sTicket)\s\(WSOP\sStep\s(?P\d)\))?(\s+)?""") re_DateTime = re.compile("\[(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P[0-9]+):(?P[0-9]+):(?P[0-9]+)") - re_Entries = re.compile("[0-9]+") - re_Prizepool = re.compile("\$[0-9]+\.[0-9]+") - re_BuyInFee = re.compile("(?P[0-9]+\.[0-9]+).*(?P[0-9]+\.[0-9]+)") - re_FPP = re.compile("(?P[0-9]+)\sFPP") - #note: the dollar and cent in the below line are currency-agnostic - re_Added = re.compile("(?P[0-9]+)\.(?P[0-9]+)\s(?P[A-Z]+)(\sadded\sto\sthe\sprize\spool\sby\sPokerStars)") - re_DateTimeET = re.compile("(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P[0-9]+):(?P[0-9]+):(?P[0-9]+)") - re_GameInfo = re.compile(u""".+(?PNo\sLimit|Limit|LIMIT|Pot\sLimit)\s(?PHold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)""") + codepage = ["utf-8"] def parseSummary(self): - lines=self.summaryText.splitlines() - - self.tourNo = self.re_TourNo.findall(lines[0])[0][1:-1] #ignore game and limit type as thats not recorded - - result=self.re_GameInfo.search(lines[0]) - result=result.groupdict() - self.gametype['limitType']=self.limits[result['LIMIT']] - self.gametype['category']=self.games[result['GAME']][1] - - if lines[1].find("$")!=-1: #TODO: move this into a method and call that from PokerStarsToFpdb.py:269 if hand.buyinCurrency=="USD" etc. - self.currency="USD" - elif lines[1].find(u"€")!=-1: - self.currency="EUR" - elif lines[1].find("FPP")!=-1: - self.currency="PSFP" - else: - raise FpdbParseError(_("didn't recognise buyin currency in:")+lines[1]) - - if self.currency=="USD" or self.currency=="EUR": - result=self.re_BuyInFee.search(lines[1]) - result=result.groupdict() - self.buyin=int(100*Decimal(result['BUYIN'])) - self.fee=int(100*Decimal(result['FEE'])) - elif self.currency=="PSFP": - result=self.re_FPP.search(lines[1]) - result=result.groupdict() - self.buyin=int(Decimal(result['FPP'])) - self.fee=0 - - currentLine=2 - self.entries = self.re_Entries.findall(lines[currentLine])[0] - currentLine+=1 #note that I chose to make the code keep state (the current line number) - #as that means it'll fail rather than silently skip potentially valuable information - #print "after entries lines[currentLine]", lines[currentLine] - - result=self.re_Added.search(lines[currentLine]) - if result: - result=result.groupdict() - self.added=100*int(Decimal(result['DOLLAR']))+int(Decimal(result['CENT'])) - self.addedCurrency=result['CURRENCY'] - currentLine+=1 - else: - self.added=0 - self.addedCurrency="NA" - #print "after added/entries lines[currentLine]", lines[currentLine] - - result=self.re_Prizepool.findall(lines[currentLine]) - if result: - self.prizepool = result[0] - self.prizepool = self.prizepool[1:-3]+self.prizepool[-2:] - currentLine+=1 - #print "after prizepool lines[currentLine]", lines[currentLine] - - useET=False - result=self.re_DateTime.search(lines[currentLine]) - if not result: - print _("in not result starttime") - useET=True - result=self.re_DateTimeET.search(lines[currentLine]) - result=result.groupdict() - datetimestr = "%s/%s/%s %s:%s:%s" % (result['Y'], result['M'],result['D'],result['H'],result['MIN'],result['S']) - self.startTime= datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET" - self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC") - currentLine+=1 - - if useET: - result=self.re_DateTimeET.search(lines[currentLine]) - else: - result=self.re_DateTime.search(lines[currentLine]) - if result: - result=result.groupdict() - datetimestr = "%s/%s/%s %s:%s:%s" % (result['Y'], result['M'],result['D'],result['H'],result['MIN'],result['S']) - self.endTime= datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET" - self.endTime = HandHistoryConverter.changeTimezone(self.endTime, "ET", "UTC") - currentLine+=1 - - if lines[currentLine].find("Tournament is still in progress")!=-1: - currentLine+=1 - - for i in range(currentLine,len(lines)-2): #lines with rank and winnings info - if lines[i].find(":")==-1: - break - result=self.re_Player.search(lines[i]) - result=result.groupdict() - rank=result['RANK'] - name=result['NAME'] - winnings=result['WINNINGS'] - - if winnings: - winnings=int(100*Decimal(winnings)) - else: - winnings=0 - - if result['STILLPLAYING']: - #print "stillplaying" - rank=None - winnings=None - - self.addPlayer(rank, name, winnings, self.currency, None, None, None)#TODO: currency, ko/addon/rebuy count -> need examples! - #end def parseSummary - - def parseSummaryFile(self): m = self.re_TourneyInfo.search(self.summaryText) if m == None: tmp = self.summaryText[0:200] - log.error(_("parseSummaryFile: Unable to recognise Tourney Info: '%s'") % tmp) - log.error(_("parseSummaryFile: Raising FpdbParseError")) + log.error(_("parseSummary: Unable to recognise Tourney Info: '%s'") % tmp) + log.error(_("parseSummary: Raising FpdbParseError")) raise FpdbParseError(_("Unable to recognise Tourney Info: '%s'") % tmp) #print "DEBUG: m.groupdict(): %s" % m.groupdict() @@ -222,8 +104,8 @@ class PokerStarsSummary(TourneySummary): m = self.re_Currency.search(self.summaryText) if m == None: - log.error(_("parseSummaryFile: Unable to locate currency")) - log.error(_("parseSummaryFile: Raising FpdbParseError")) + log.error(_("parseSummary: Unable to locate currency")) + log.error(_("parseSummary: Raising FpdbParseError")) raise FpdbParseError(_("Unable to locate currency")) #print "DEBUG: m.groupdict(): %s" % m.groupdict() @@ -248,6 +130,18 @@ class PokerStarsSummary(TourneySummary): rank=None winnings=None + if 'TICKET' and mg['TICKET'] != None: + #print "Tournament Ticket Level %s" % mg['LEVEL'] + step_values = { + '1' : '750', # Step 1 - $7.50 USD + '2' : '2750', # Step 2 - $27.00 USD + '3' : '8200', # Step 3 - $82.00 USD + '4' : '21500', # Step 4 - $215.00 USD + '5' : '70000', # Step 5 - $700.00 USD + '6' : '210000', # Step 6 - $2100.00 USD + } + winnings = step_values[mg['LEVEL']] + #TODO: currency, ko/addon/rebuy count -> need examples! #print "DEBUG: addPlayer(%s, %s, %s, %s, None, None, None)" %(rank, name, winnings, self.currency) #print "DEBUG: self.buyin: %s self.fee %s" %(self.buyin, self.fee) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 98abcaf0..16c4091e 100644 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -18,24 +18,15 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################## +import L10n +_ = L10n.get_translation() + # TODO: straighten out discards for draw games import sys from HandHistoryConverter import * from decimal import Decimal -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - # PokerStars HH Format class PokerStars(HandHistoryConverter): @@ -48,7 +39,7 @@ class PokerStars(HandHistoryConverter): siteId = 2 # Needs to match id entry in Sites database mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games - sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\xe2\x82\xac", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE + sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\xe2\x82\xac", "GBP": "\xa3", "play": ""} # ADD Euro, Sterling, etc HERE substitutions = { 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes 'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8) @@ -106,10 +97,13 @@ class PokerStars(HandHistoryConverter): (?P%(LS)s|)? (?P[.0-9]+)/(%(LS)s)? (?P[.0-9]+) + (?P\s-\s[%(LS)s\d\.]+\sCap\s-\s)? # Optional Cap part \s?(?P%(LEGAL_ISO)s)? - \)\s-\s # close paren of the stakes - (?P.*$)""" % substitutions, - re.MULTILINE|re.VERBOSE) + \) # close paren of the stakes + (?P\s\[AAMS\sID:\s[A-Z0-9]+\])? # AAMS ID: in .it HH's + \s-\s + (?P.*$) + """ % substitutions, re.MULTILINE|re.VERBOSE) re_PlayerInfo = re.compile(u""" ^Seat\s(?P[0-9]+):\s @@ -155,6 +149,7 @@ class PokerStars(HandHistoryConverter): ^%(PLYR)s:(?P\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat) (\s(%(CUR)s)?(?P[.\d]+))?(\sto\s%(CUR)s(?P[.\d]+))? # the number discarded goes in \s*(and\sis\sall.in)? + (and\shas\sreached\sthe\s[%(CUR)s\d\.]+\scap)? (\scards?(\s\[(?P.+?)\])?)?\s*$""" % subst, re.MULTILINE|re.VERBOSE) self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P.*)\]" % player_re, re.MULTILINE) @@ -170,6 +165,7 @@ class PokerStars(HandHistoryConverter): ["ring", "stud", "fl"], ["ring", "draw", "fl"], + ["ring", "draw", "pl"], ["ring", "draw", "nl"], ["tour", "hold", "nl"], @@ -177,7 +173,11 @@ class PokerStars(HandHistoryConverter): ["tour", "hold", "fl"], ["tour", "stud", "fl"], - ] + + ["tour", "draw", "fl"], + ["tour", "draw", "pl"], + ["tour", "draw", "nl"], + ] def determineGameType(self, handText): info = {} @@ -221,9 +221,8 @@ class PokerStars(HandHistoryConverter): m = self.re_HandInfo.search(hand.handText,re.DOTALL) m2 = self.re_GameInfo.search(hand.handText) if m is None or m2 is None: - logging.info("Didn't match re_HandInfo") - logging.info(hand.handText) - raise FpdbParseError("No match in readHandInfo.") + log.error("Didn't match re_HandInfo") + raise FpdbParseError(_("No match in readHandInfo.")) info.update(m.groupdict()) info.update(m2.groupdict()) @@ -455,6 +454,7 @@ class PokerStars(HandHistoryConverter): if m.group('SHOWED') == "showed": shown = True elif m.group('SHOWED') == "mucked": mucked = True + #print "DEBUG: hand.addShownCards(%s, %s, %s, %s)" %(cards, m.group('PNAME'), shown, mucked) hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked) if __name__ == "__main__": diff --git a/pyfpdb/RazzStartHandGenerator.py b/pyfpdb/RazzStartHandGenerator.py new file mode 100644 index 00000000..edc6e0b9 --- /dev/null +++ b/pyfpdb/RazzStartHandGenerator.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Generate Razz startCards encoding and decoding for Card.py""" + +import re + +re_space = re.compile("([\(\)AKQJT0-9]+)\s+", re.MULTILINE) + +razzlist = """(32)A (3A)2 (2A)3 (42)A (4A)2 (2A)4 (43)A (4A)3 (3A)4 (43)2 + (42)3 (32)4 (52)A (5A)2 (2A)5 (53)A (5A)3 (3A)5 (53)2 (52)3 + (32)5 (54)A (5A)4 (4A)5 (54)2 (52)4 (42)5 (54)3 (53)4 (43)5 + (62)A (6A)2 (2A)6 (63)A (6A)3 (3A)6 (63)2 (62)3 (32)6 (64)A + (6A)4 (4A)6 (64)2 (62)4 (42)6 (64)3 (63)4 (43)6 (65)A (6A)5 + (5A)6 (65)2 (62)5 (52)6 (65)3 (63)5 (53)6 (65)4 (64)5 (54)6 + (72)A (7A)2 (2A)7 (73)A (7A)3 (3A)7 (73)2 (72)3 (32)7 (74)A + (7A)4 (4A)7 (74)2 (72)4 (42)7 (74)3 (73)4 (43)7 (75)A (7A)5 + (5A)7 (75)2 (72)5 (52)7 (75)3 (73)5 (53)7 (75)4 (74)5 (54)7 + (76)A (7A)6 (6A)7 (76)2 (72)6 (62)7 (76)3 (73)6 (63)7 (76)4 + (74)6 (64)7 (76)5 (75)6 (65)7 (82)A (8A)2 (2A)8 (83)A (8A)3 + (3A)8 (83)2 (82)3 (32)8 (84)A (8A)4 (4A)8 (84)2 (82)4 (42)8 + (84)3 (83)4 (43)8 (85)A (8A)5 (5A)8 (85)2 (82)5 (52)8 (85)3 + (83)5 (53)8 (85)4 (84)5 (54)8 (86)A (8A)6 (6A)8 (86)2 (82)6 + (62)8 (86)3 (83)6 (63)8 (86)4 (84)6 (64)8 (86)5 (85)6 (65)8 + (87)A (8A)7 (7A)8 (87)2 (82)7 (72)8 (87)3 (83)7 (73)8 (87)4 + (84)7 (74)8 (87)5 (85)7 (75)8 (87)6 (86)7 (76)8 (92)A (9A)2 + (2A)9 (93)A (9A)3 (3A)9 (93)2 (92)3 (32)9 (94)A (9A)4 (4A)9 + (94)2 (92)4 (42)9 (94)3 (93)4 (43)9 (95)A (9A)5 (5A)9 (95)2 + (92)5 (52)9 (95)3 (93)5 (53)9 (95)4 (94)5 (54)9 (96)A (9A)6 + (6A)9 (96)2 (92)6 (62)9 (96)3 (93)6 (63)9 (96)4 (94)6 (64)9 + (96)5 (95)6 (65)9 (97)A (9A)7 (7A)9 (97)2 (92)7 (72)9 (97)3 + (93)7 (73)9 (97)4 (94)7 (74)9 (97)5 (95)7 (75)9 (97)6 (96)7 + (76)9 (98)A (9A)8 (8A)9 (98)2 (92)8 (82)9 (98)3 (93)8 (83)9 + (98)4 (94)8 (84)9 (98)5 (95)8 (85)9 (98)6 (96)8 (86)9 (98)7 + (97)8 (87)9 (T2)A (TA)2 (2A)T (T3)A (TA)3 (3A)T (T3)2 (T2)3 + (32)T (T4)A (TA)4 (4A)T (T4)2 (T2)4 (42)T (T4)3 (T3)4 (43)T + (T5)A (TA)5 (5A)T (T5)2 (T2)5 (52)T (T5)3 (T3)5 (53)T (T5)4 + (T4)5 (54)T (T6)A (TA)6 (6A)T (T6)2 (T2)6 (62)T (T6)3 (T3)6 + (63)T (T6)4 (T4)6 (64)T (T6)5 (T5)6 (65)T (T7)A (TA)7 (7A)T + (T7)2 (T2)7 (72)T (T7)3 (T3)7 (73)T (T7)4 (T4)7 (74)T (T7)5 + (T5)7 (75)T (T7)6 (T6)7 (76)T (T8)A (TA)8 (8A)T (T8)2 (T2)8 + (82)T (T8)3 (T3)8 (83)T (T8)4 (T4)8 (84)T (T8)5 (T5)8 (85)T + (T8)6 (T6)8 (86)T (T8)7 (T7)8 (87)T (T9)A (TA)9 (9A)T (T9)2 + (T2)9 (92)T (T9)3 (T3)9 (93)T (T9)4 (T4)9 (94)T (T9)5 (T5)9 + (95)T (T9)6 (T6)9 (96)T (T9)7 (T7)9 (97)T (T9)8 (T8)9 (98)T + (J2)A (JA)2 (2A)J (J3)A (JA)3 (3A)J (J3)2 (J2)3 (32)J (J4)A + (JA)4 (4A)J (J4)2 (J2)4 (42)J (J4)3 (J3)4 (43)J (J5)A (JA)5 + (5A)J (J5)2 (J2)5 (52)J (J5)3 (J3)5 (53)J (J5)4 (J4)5 (54)J + (J6)A (JA)6 (6A)J (J6)2 (J2)6 (62)J (J6)3 (J3)6 (63)J (J6)4 + (J4)6 (64)J (J6)5 (J5)6 (65)J (J7)A (JA)7 (7A)J (J7)2 (J2)7 + (72)J (J7)3 (J3)7 (73)J (J7)4 (J4)7 (74)J (J7)5 (J5)7 (75)J + (J7)6 (J6)7 (76)J (J8)A (JA)8 (8A)J (J8)2 (J2)8 (82)J (J8)3 + (J3)8 (83)J (J8)4 (J4)8 (84)J (J8)5 (J5)8 (85)J (J8)6 (J6)8 + (86)J (J8)7 (J7)8 (87)J (J9)A (JA)9 (9A)J (J9)2 (J2)9 (92)J + (J9)3 (J3)9 (93)J (J9)4 (J4)9 (94)J (J9)5 (J5)9 (95)J (J9)6 + (J6)9 (96)J (J9)7 (J7)9 (97)J (J9)8 (J8)9 (98)J (JT)A (JA)T + (TA)J (JT)2 (J2)T (T2)J (JT)3 (J3)T (T3)J (JT)4 (J4)T (T4)J + (JT)5 (J5)T (T5)J (JT)6 (J6)T (T6)J (JT)7 (J7)T (T7)J (JT)8 + (J8)T (T8)J (JT)9 (J9)T (T9)J (Q2)A (QA)2 (2A)Q (Q3)A (QA)3 + (3A)Q (Q3)2 (Q2)3 (32)Q (Q4)A (QA)4 (4A)Q (Q4)2 (Q2)4 (42)Q + (Q4)3 (Q3)4 (43)Q (Q5)A (QA)5 (5A)Q (Q5)2 (Q2)5 (52)Q (Q5)3 + (Q3)5 (53)Q (Q5)4 (Q4)5 (54)Q (Q6)A (QA)6 (6A)Q (Q6)2 (Q2)6 + (62)Q (Q6)3 (Q3)6 (63)Q (Q6)4 (Q4)6 (64)Q (Q6)5 (Q5)6 (65)Q + (Q7)A (QA)7 (7A)Q (Q7)2 (Q2)7 (72)Q (Q7)3 (Q3)7 (73)Q (Q7)4 + (Q4)7 (74)Q (Q7)5 (Q5)7 (75)Q (Q7)6 (Q6)7 (76)Q (Q8)A (QA)8 + (8A)Q (Q8)2 (Q2)8 (82)Q (Q8)3 (Q3)8 (83)Q (Q8)4 (Q4)8 (84)Q + (Q8)5 (Q5)8 (85)Q (Q8)6 (Q6)8 (86)Q (Q8)7 (Q7)8 (87)Q (Q9)A + (QA)9 (9A)Q (Q9)2 (Q2)9 (92)Q (Q9)3 (Q3)9 (93)Q (Q9)4 (Q4)9 + (94)Q (Q9)5 (Q5)9 (95)Q (Q9)6 (Q6)9 (96)Q (Q9)7 (Q7)9 (97)Q + (Q9)8 (Q8)9 (98)Q (QT)A (QA)T (TA)Q (QT)2 (Q2)T (T2)Q (QT)3 + (Q3)T (T3)Q (QT)4 (Q4)T (T4)Q (QT)5 (Q5)T (T5)Q (QT)6 (Q6)T + (T6)Q (QT)7 (Q7)T (T7)Q (QT)8 (Q8)T (T8)Q (QT)9 (Q9)T (T9)Q + (QJ)A (QA)J (JA)Q (QJ)2 (Q2)J (J2)Q (QJ)3 (Q3)J (J3)Q (QJ)4 + (Q4)J (J4)Q (QJ)5 (Q5)J (J5)Q (QJ)6 (Q6)J (J6)Q (QJ)7 (Q7)J + (J7)Q (QJ)8 (Q8)J (J8)Q (QJ)9 (Q9)J (J9)Q (QJ)T (QT)J (JT)Q + (K2)A (KA)2 (2A)K (K3)A (KA)3 (3A)K (K3)2 (K2)3 (32)K (K4)A + (KA)4 (4A)K (K4)2 (K2)4 (42)K (K4)3 (K3)4 (43)K (K5)A (KA)5 + (5A)K (K5)2 (K2)5 (52)K (K5)3 (K3)5 (53)K (K5)4 (K4)5 (54)K + (K6)A (KA)6 (6A)K (K6)2 (K2)6 (62)K (K6)3 (K3)6 (63)K (K6)4 + (K4)6 (64)K (K6)5 (K5)6 (65)K (K7)A (KA)7 (7A)K (K7)2 (K2)7 + (72)K (K7)3 (K3)7 (73)K (K7)4 (K4)7 (74)K (K7)5 (K5)7 (75)K + (K7)6 (K6)7 (76)K (K8)A (KA)8 (8A)K (K8)2 (K2)8 (82)K (K8)3 + (K3)8 (83)K (K8)4 (K4)8 (84)K (K8)5 (K5)8 (85)K (K8)6 (K6)8 + (86)K (K8)7 (K7)8 (87)K (K9)A (KA)9 (9A)K (K9)2 (K2)9 (92)K + (K9)3 (K3)9 (93)K (K9)4 (K4)9 (94)K (K9)5 (K5)9 (95)K (K9)6 + (K6)9 (96)K (K9)7 (K7)9 (97)K (K9)8 (K8)9 (98)K (KT)A (KA)T + (TA)K (KT)2 (K2)T (T2)K (KT)3 (K3)T (T3)K (KT)4 (K4)T (T4)K + (KT)5 (K5)T (T5)K (KT)6 (K6)T (T6)K (KT)7 (K7)T (T7)K (KT)8 + (K8)T (T8)K (KT)9 (K9)T (T9)K (KJ)A (KA)J (JA)K (KJ)2 (K2)J + (J2)K (KJ)3 (K3)J (J3)K (KJ)4 (K4)J (J4)K (KJ)5 (K5)J (J5)K + (KJ)6 (K6)J (J6)K (KJ)7 (K7)J (J7)K (KJ)8 (K8)J (J8)K (KJ)9 + (K9)J (J9)K (KJ)T (KT)J (JT)K (KQ)A (KA)Q (QA)K (KQ)2 (K2)Q + (Q2)K (KQ)3 (K3)Q (Q3)K (KQ)4 (K4)Q (Q4)K (KQ)5 (K5)Q (Q5)K + (KQ)6 (K6)Q (Q6)K (KQ)7 (K7)Q (Q7)K (KQ)8 (K8)Q (Q8)K (KQ)9 + (K9)Q (Q9)K (KQ)T (KT)Q (QT)K (KQ)J (KJ)Q (QJ)K (2A)A (22)A + (AA)2 (2A)2 (3A)A (33)A (AA)3 (3A)3 (32)2 (33)2 (22)3 (32)3 + (4A)A (44)A (AA)4 (4A)4 (42)2 (44)2 (22)4 (42)4 (43)3 (44)3 + (33)4 (43)4 (5A)A (55)A (AA)5 (5A)5 (52)2 (55)2 (22)5 (52)5 + (53)3 (55)3 (33)5 (53)5 (54)4 (55)4 (44)5 (54)5 (6A)A (66)A + (AA)6 (6A)6 (62)2 (66)2 (22)6 (62)6 (63)3 (66)3 (33)6 (63)6 + (64)4 (66)4 (44)6 (64)6 (65)5 (66)5 (55)6 (65)6 (7A)A (77)A + (AA)7 (7A)7 (72)2 (77)2 (22)7 (72)7 (73)3 (77)3 (33)7 (73)7 + (74)4 (77)4 (44)7 (74)7 (75)5 (77)5 (55)7 (75)7 (76)6 (77)6 + (66)7 (76)7 (8A)A (88)A (AA)8 (8A)8 (82)2 (88)2 (22)8 (82)8 + (83)3 (88)3 (33)8 (83)8 (84)4 (88)4 (44)8 (84)8 (85)5 (88)5 + (55)8 (85)8 (86)6 (88)6 (66)8 (86)8 (87)7 (88)7 (77)8 (87)8 + (9A)A (99)A (AA)9 (9A)9 (92)2 (99)2 (22)9 (92)9 (93)3 (99)3 + (33)9 (93)9 (94)4 (99)4 (44)9 (94)9 (95)5 (99)5 (55)9 (95)9 + (96)6 (99)6 (66)9 (96)9 (97)7 (99)7 (77)9 (97)9 (98)8 (99)8 + (88)9 (98)9 (TA)A (TT)A (AA)T (TA)T (T2)2 (TT)2 (22)T (T2)T + (T3)3 (TT)3 (33)T (T3)T (T4)4 (TT)4 (44)T (T4)T (T5)5 (TT)5 + (55)T (T5)T (T6)6 (TT)6 (66)T (T6)T (T7)7 (TT)7 (77)T (T7)T + (T8)8 (TT)8 (88)T (T8)T (T9)9 (TT)9 (99)T (T9)T (JA)A (JJ)A + (AA)J (JA)J (J2)2 (JJ)2 (22)J (J2)J (J3)3 (JJ)3 (33)J (J3)J + (J4)4 (JJ)4 (44)J (J4)J (J5)5 (JJ)5 (55)J (J5)J (J6)6 (JJ)6 + (66)J (J6)J (J7)7 (JJ)7 (77)J (J7)J (J8)8 (JJ)8 (88)J (J8)J + (J9)9 (JJ)9 (99)J (J9)J (JT)T (JJ)T (TT)J (JT)J (QA)A (QQ)A + (AA)Q (QA)Q (Q2)2 (QQ)2 (22)Q (Q2)Q (Q3)3 (QQ)3 (33)Q (Q3)Q + (Q4)4 (QQ)4 (44)Q (Q4)Q (Q5)5 (QQ)5 (55)Q (Q5)Q (Q6)6 (QQ)6 + (66)Q (Q6)Q (Q7)7 (QQ)7 (77)Q (Q7)Q (Q8)8 (QQ)8 (88)Q (Q8)Q + (Q9)9 (QQ)9 (99)Q (Q9)Q (QT)T (QQ)T (TT)Q (QT)Q (QJ)J (QQ)J + (JJ)Q (QJ)Q (KA)A (KK)A (AA)K (KA)K (K2)2 (KK)2 (22)K (K2)K + (K3)3 (KK)3 (33)K (K3)K (K4)4 (KK)4 (44)K (K4)K (K5)5 (KK)5 + (55)K (K5)K (K6)6 (KK)6 (66)K (K6)K (K7)7 (KK)7 (77)K (K7)K + (K8)8 (KK)8 (88)K (K8)K (K9)9 (KK)9 (99)K (K9)K (KT)T (KK)T + (TT)K (KT)K (KJ)J (KK)J (JJ)K (KJ)K (KQ)Q (KK)Q (QQ)K (KQ)K + (AA)A (22)2 (33)3 (44)4 (55)5 (66)6 (77)7 (88)8 (99)9 (TT)T + (JJ)J (QQ)Q (KK)K +""" + +count = 1 +string = "" + +for a in re_space.finditer(razzlist): + string = string + ("'%s':%s," %(a.group(1), count)) + count+=1 + if count%10 == 0: + string = string + "\n" +print "-------------------------" +print "Razz encode list" +print "------------------------ " +print string + +string = "" +count = 1 + +for a in re_space.finditer(razzlist): + string = string + ("%s:'%s'," %(count, a.group(1))) + count+=1 + if count%10 == 0: + string = string + "\n" + + +print "-------------------------" +print "Razz decode list" +print "------------------------ " +print string + diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index fff3f07e..d45ccd38 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -153,7 +153,28 @@ class Sql: tourneyId BIGINT NOT NULL, rawTourney TEXT NOT NULL, complain BOOLEAN NOT NULL DEFAULT FALSE)""" - + + ################################ + # Create Actions + ################################ + + if db_server == 'mysql': + self.query['createActionsTable'] = """CREATE TABLE Actions ( + id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), + name varchar(32) NOT NULL, + code char(4) NOT NULL) + ENGINE=INNODB""" + elif db_server == 'postgresql': + self.query['createActionsTable'] = """CREATE TABLE Actions ( + id SERIAL, PRIMARY KEY (id), + name varchar(32), + code char(4))""" + elif db_server == 'sqlite': + self.query['createActionsTable'] = """CREATE TABLE Actions ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + code TEXT NOT NULL)""" + ################################ # Create Sites ################################ @@ -989,11 +1010,14 @@ class Sql: handsPlayerId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handsPlayerId) REFERENCES HandsPlayers(id), street SMALLINT NOT NULL, actionNo SMALLINT NOT NULL, - action CHAR(5) NOT NULL, - allIn BOOLEAN NOT NULL, + streetActionNo SMALLINT NOT NULL, + actionId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (actionId) REFERENCES Actions(id), amount INT NOT NULL, - comment TEXT, - commentTs DATETIME) + raiseTo INT NOT NULL, + amountCalled INT NOT NULL, + numDiscarded SMALLINT NOT NULL, + cardsDiscarded varchar(14), + allIn BOOLEAN NOT NULL) ENGINE=INNODB""" elif db_server == 'postgresql': self.query['createHandsActionsTable'] = """CREATE TABLE HandsActions ( @@ -1001,24 +1025,31 @@ class Sql: handsPlayerId BIGINT, FOREIGN KEY (handsPlayerId) REFERENCES HandsPlayers(id), street SMALLINT, actionNo SMALLINT, - action CHAR(5), - allIn BOOLEAN, + streetActionNo SMALLINT, + actionId SMALLINT, FOREIGN KEY (actionId) REFERENCES Actions(id), amount INT, - comment TEXT, - commentTs timestamp without time zone)""" + raiseTo INT, + amountCalled INT, + numDiscarded SMALLINT, + cardsDiscarded varchar(14), + allIn BOOLEAN)""" elif db_server == 'sqlite': self.query['createHandsActionsTable'] = """CREATE TABLE HandsActions ( id INTEGER PRIMARY KEY, handsPlayerId BIGINT, street SMALLINT, actionNo SMALLINT, - action CHAR(5), - allIn INT, + streetActionNo SMALLINT, + actionId SMALLINT, amount INT, - comment TEXT, - commentTs timestamp without time zone, - FOREIGN KEY (handsPlayerId) REFERENCES HandsPlayers(id) - )""" + raiseTo INT, + amountCalled INT, + numDiscarded SMALLINT, + cardsDiscarded TEXT, + allIn BOOLEAN, + FOREIGN KEY (handsPlayerId) REFERENCES HandsPlayers(id), + FOREIGN KEY (actionId) REFERENCES Actions(id) ON DELETE CASCADE + )""" ################################ @@ -1365,6 +1396,10 @@ class Sql: , maxSeats, knockout, rebuy, addOn, speed, shootout, matrix, sng)""" self.query['get_last_hand'] = "select max(id) from Hands" + + self.query['get_last_date'] = "SELECT MAX(startTime) FROM Hands" + + self.query['get_first_date'] = "SELECT MIN(startTime) FROM Hands" self.query['get_player_id'] = """ select Players.id AS player_id @@ -3024,8 +3059,6 @@ class Sql: order by stats.category, stats.limitType, stats.bigBlindDesc desc , cast(stats.PlPosition as smallint) """ - #elif db_server == 'sqlite': - # self.query['playerStatsByPosition'] = """ """ #################################### # Cash Game Graph query @@ -3046,11 +3079,45 @@ class Sql: GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit ORDER BY h.startTime""" + self.query['getRingProfitAllHandsPlayerIdSiteInBB'] = """ + SELECT hp.handId, ( hp.totalProfit / ( gt.bigBlind * 2 ) ) * 100 , hp.sawShowdown + FROM HandsPlayers hp + INNER JOIN Players pl ON (pl.id = hp.playerId) + INNER JOIN Hands h ON (h.id = hp.handId) + INNER JOIN Gametypes gt ON (gt.id = h.gametypeId) + WHERE pl.id in + AND pl.siteId in + AND h.startTime > '' + AND h.startTime < '' + + + AND hp.tourneysPlayersId IS NULL + GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit + ORDER BY h.startTime""" + + self.query['getRingProfitAllHandsPlayerIdSiteInDollars'] = """ + SELECT hp.handId, hp.totalProfit, hp.sawShowdown + FROM HandsPlayers hp + INNER JOIN Players pl ON (pl.id = hp.playerId) + INNER JOIN Hands h ON (h.id = hp.handId) + INNER JOIN Gametypes gt ON (gt.id = h.gametypeId) + WHERE pl.id in + AND pl.siteId in + AND h.startTime > '' + AND h.startTime < '' + + + AND hp.tourneysPlayersId IS NULL + GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit + ORDER BY h.startTime""" + + + #################################### # Tourney Results query #################################### self.query['tourneyResults'] = """ - SELECT tp.tourneyId, (tp.winnings - tt.buyIn - tt.fee) as profit, tp.koCount, tp.rebuyCount, tp.addOnCount, tt.buyIn, tt.fee, t.siteTourneyNo + SELECT tp.tourneyId, (coalesce(tp.winnings,0) - coalesce(tt.buyIn,0) - coalesce(tt.fee,0)) as profit, tp.koCount, tp.rebuyCount, tp.addOnCount, tt.buyIn, tt.fee, t.siteTourneyNo FROM TourneysPlayers tp INNER JOIN Players pl ON (pl.id = tp.playerId) INNER JOIN Tourneys t ON (t.id = tp.tourneyId) @@ -4251,11 +4318,17 @@ class Sql: handsPlayerId, street, actionNo, - action, - allIn, - amount + streetActionNo, + actionId, + amount, + raiseTo, + amountCalled, + numDiscarded, + cardsDiscarded, + allIn ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )""" @@ -4263,9 +4336,9 @@ class Sql: ################################ # Counts for DB stats window ################################ - self.query['getHandCount'] = "SELECT COUNT(id) FROM Hands" - self.query['getTourneyCount'] = "SELECT COUNT(id) FROM Tourneys" - self.query['getTourneyTypeCount'] = "SELECT COUNT(id) FROM TourneyTypes" + self.query['getHandCount'] = "SELECT COUNT(*) FROM Hands" + self.query['getTourneyCount'] = "SELECT COUNT(*) FROM Tourneys" + self.query['getTourneyTypeCount'] = "SELECT COUNT(*) FROM TourneyTypes" ################################ # queries for dumpDatabase diff --git a/pyfpdb/SplitHandHistory.py b/pyfpdb/SplitHandHistory.py new file mode 100644 index 00000000..52c1d340 --- /dev/null +++ b/pyfpdb/SplitHandHistory.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2010 Chaz Littlejohn +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in agpl-3.0.txt. + +import L10n +_ = L10n.get_translation() + +# This code is based heavily on stars-support-hh-split.py by Mika Boström + +import os +import sys +import re +import codecs +import Options +import Configuration +from Exceptions import * +from cStringIO import StringIO + +(options, argv) = Options.fpdb_options() + +__ARCHIVE_PRE_HEADER_REGEX='^Hand #(\d+)\s*$|\*{20}\s#\s\d+\s\*+\s+' +re_SplitArchive = re.compile(__ARCHIVE_PRE_HEADER_REGEX) +codepage = ["utf-16", "utf-8", "cp1252"] + + +class SplitHandHistory: + def __init__(self, config, in_path = '-', out_path = None, hands = 100, filter = "PokerStarsToFpdb", archive = False): + self.config = config + self.in_path = in_path + self.out_path = out_path + if not self.out_path: + self.out_path = os.path.dirname(self.in_path) + self.hands = hands + self.archive = archive + self.re_SplitHands = None + self.line_delimiter = None + self.line_addendum = None + self.filedone = False + + #Acquire re_SplitHands for this hh + filter_name = filter.replace("ToFpdb", "") + mod = __import__(filter) + obj = getattr(mod, filter_name, None) + self.re_SplitHands = obj.re_SplitHands + + #Determine line delimiter type if any + if self.re_SplitHands.match('\n\n'): + self.line_delimiter = '\n\n' + if self.re_SplitHands.match('\n\n\n'): + self.line_delimiter = '\n\n\n' + + #Add new line addendum for sites which match SplitHand to next line as well + if filter_name == 'OnGame': + self.line_addendum = '*' + if filter_name == 'Carbon': + self.line_addendum = '= self.hands: + break + outfile.close() + + def new_file(self, fileno=-1): + if fileno < 1: + print _('Nope, will not work (fileno=%d)' % fileno) + sys.exit(2) + basename = os.path.splitext(os.path.basename(self.in_path))[0] + name = os.path.join(self.out_path, basename+'-%06d.txt' % fileno) + print '-> %s' % name + newfile = file(name, 'w') + return newfile + + #Archive Hand Splitter + def do_hands_per_file(self, infile, num=-1): + done = False + n = 0 + outfile = self.new_file(num) + while n < self.hands: + try: + infile = self.next_hand(infile) + infile = self.process_hand(infile, outfile) + except FpdbEndOfFile: + done = True + break + except: + print _("Unexpected error processing file") + sys.exit(2) + n += 1 + outfile.close() + if not done: + return infile + else: + return None + + #Non-Archive Hand Splitter + def paragraphs(self, file, separator=None, addendum=None): + if not callable(separator) and self.line_delimiter: + def separator(line): return line == '\n' + else: + def separator(line): return self.re_SplitHands.search(line) + file_str = StringIO() + print file_str.getvalue() + for line in file: + if separator(line+addendum): + if file_str.getvalue(): + if not self.line_delimiter: + file_str.write(line) + yield file_str.getvalue() + file_str = None + file_str = StringIO() + else: + file_str.write(line) + if file_str.getvalue(): yield file_str.getvalue() + self.filedone = True + + + # Finds pre-hand header (Hand #) + def next_hand(self, infile): + m = None + while not m: + l = infile.readline() + #print l, len(l) + # Catch EOF + if len(l) == 0: + raise FpdbEndOfFile(_("End of file reached")) + m = re_SplitArchive.search(l) + # There is an empty line after pre-hand header and actual HH entry + l = infile.readline() + + return infile + + # Each individual hand is written separately + def process_hand(self, infile=None, outfile=None): + l = infile.readline() + l = l.replace('\r\n', '\n') + outfile.write(l) + l = infile.readline() + + while len(l) < 3: + l = infile.readline() + + while len(l) > 2: + l = l.replace('\r\n', '\n') + outfile.write(l) + l = infile.readline() + outfile.write(self.line_delimiter) + return infile + + def __listof(self, x): + if isinstance(x, list) or isinstance(x, tuple): + return x + else: + return [x] + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + if not options.config: + options.config = Configuration.Config(file = "HUD_config.test.xml") + + if options.filename: + SplitHH = SplitHandHistory(options.config, options.filename, options.outpath, options.hands, + options.hhc, options.archive) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py old mode 100755 new mode 100644 index a9dd137f..bc0396dc --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -47,6 +47,9 @@ # other stuff. # 6 For each stat you make add a line to the __main__ function to test it. +import L10n +_ = L10n.get_translation() + # Standard Library modules import sys @@ -55,18 +58,6 @@ import pygtk import gtk import re -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - # FreePokerTools modules import Configuration import Database diff --git a/pyfpdb/Stove.py b/pyfpdb/Stove.py new file mode 100755 index 00000000..eb939c0f --- /dev/null +++ b/pyfpdb/Stove.py @@ -0,0 +1,309 @@ +#!/usr/bin/python +# -*- coding: iso-8859-15 +# +# stove.py +# Simple Hold'em equity calculator +# Copyright (C) 2007-2008 Mika Bostrm +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +#TODO: gettextify + +import sys, random +import pokereval + +SUITS = ['h', 'd', 's', 'c'] + +ANY = 0 +SUITED = 1 +OFFSUIT = 2 + +ev = pokereval.PokerEval() + +holder = None + +class Holder: + def __init__(self): + self.hand = None + self.board = None + self.range = None + + +class Cards: + def __init__(self, c1, c2): + self.c1 = c1 + self.c2 = c2 + + def get(self): + return [c1, c2] + +class Board: + def __init__(self, b1=None, b2=None, b3=None, b4=None, b5=None): + self.b1 = b1 + self.b2 = b2 + self.b3 = b3 + self.b4 = b4 + self.b5 = b5 + + def get(self): + b = [] + if self.b3 is not None: + b.append(self.b1) + b.append(self.b2) + b.append(self.b3) + else: + b.extend(["__", "__", "__"]) + + if self.b4 is not None: + b.append(self.b4) + else: + b.append("__") + + if self.b5 is not None: + b.append(self.b5) + else: + b.append("__") + + return b + +class Range: + def __init__(self): + self.__hands = set() + + def add(self, hand): + self.__hands.add(hand) + + def expand(self, hands): + self.__hands.update(set(hands)) + + def get(self): + return sorted(self.__hands) + + + +class EV: + def __init__(self, plays, win, tie, lose): + self.n_hands = plays + self.n_wins = win + self.n_ties = tie + self.n_losses = lose + + +class SumEV: + def __init__(self): + self.n_hands = 0 + self.n_wins = 0 + self.n_ties = 0 + self.n_losses = 0 + + def add(self, ev): + self.n_hands += ev.n_hands + self.n_wins += ev.n_wins + self.n_ties += ev.n_ties + self.n_losses += ev.n_losses + + def show(self, hand, range): + win_pct = 100 * (float(self.n_wins) / float(self.n_hands)) + lose_pct = 100 * (float(self.n_losses) / float(self.n_hands)) + tie_pct = 100 * (float(self.n_ties) / float(self.n_hands)) + print 'Enumerated %d possible plays.' % self.n_hands + print 'Your hand: (%s %s)' % (hand.c1, hand.c2) + print 'Against the range: %s\n' % cards_from_range(range) + print ' Win Lose Tie' + print ' %5.2f%% %5.2f%% %5.2f%%' % (win_pct, lose_pct, tie_pct) + + +def usage(me): + print """Texas Hold'Em odds calculator +Calculates odds against a range of hands. + +To use: %s '' '' '' [...] + +Separate cards with space. +Separate hands in range with commas. +""" % me + +def cards_from_range(range): + s = '{' + for h in range: + if h.c1 == '__' and h.c2 == '__': + s += 'random, ' + else: + s += '%s%s, ' % (h.c1, h.c2) + s = s.rstrip(', ') + s += '}' + return s + + +# Expands hand abbreviations such as JJ and AK to full hand ranges. +# Takes into account cards already known to be in player's hand and/or +# board. +def expand_hands(abbrev, hand, board): + selection = -1 + known_cards = set() + known_cards.update(set([hand.c2, hand.c2])) + known_cards.update(set([board.b1, board.b2, board.b3, board.b4, board.b5])) + + # Card ranks may be different + r1 = abbrev[0] + r2 = abbrev[1] + # There may be a specifier: 's' for 'suited'; 'o' for 'off-suit' + if len(abbrev) == 3: + ltr = abbrev[2] + if ltr == 'o': + selection = OFFSUIT + elif ltr == 's': + selection = SUITED + else: + selection = ANY + + range = [] + considered = set() + for s1 in SUITS: + c1 = r1 + s1 + if c1 in known_cards: + continue + considered.add(c1) + for s2 in SUITS: + c2 = r2 + s2 + if selection == SUITED and s1 != s2: + continue + elif selection == OFFSUIT and s1 == s2: + continue + if c2 not in considered and c2 not in known_cards: + range.append(Cards(c1, c2)) + return range + + + + + +def parse_args(args, container): + # args[0] is the path being executed; need 3 more args + if len(args) < 4: + return False + + board = Board() + + # Board + b = args[1].strip().split() + if len(b) > 4: + board.b5 = b[4] + if len(b) > 3: + board.b4 = b[3] + if len(b) > 2: + board.b1 = b[0] + board.b2 = b[1] + board.b3 = b[2] + + # Our pocket cards + cc = args[2].strip().split() + c1 = cc[0] + c2 = cc[1] + pocket_cards = Cards(c1, c2) + + # Villain's range + range = Range() + hands_in_range = args[3].strip().split(',') + for h in hands_in_range: + _h = h.strip() + if len(_h) > 3: + cc = _h.split() + r1 = cc[0] + r2 = cc[1] + vp = Cards(r1, r2) + range.add(vp) + else: + range.expand(expand_hands(_h, pocket_cards, board)) + + holder.hand = pocket_cards + holder.range = range + holder.board = board + + return True + + +def odds_for_hand(hand1, hand2, board, iterations): + res = ev.poker_eval(game='holdem', + pockets = [ + hand1, + hand2 + ], + dead = [], + board = board, + iterations = iterations + ) + + plays = int(res['info'][0]) + eval = res['eval'][0] + + win = int(eval['winhi']) + lose = int(eval['losehi']) + tie = int(eval['tiehi']) + + _ev = EV(plays, win, tie, lose) + return _ev + + +def odds_for_range(holder): + sev = SumEV() + monte_carlo = False + + # Construct board list + b = [] + board = holder.board + if board.b3 is not None: + b.extend([board.b1, board.b2, board.b3]) + else: + b.extend(['__', '__', '__']) + monte_carlo = True + if board.b4 is not None: + b.append(board.b4) + else: + b.append("__") + if board.b5 is not None: + b.append(board.b5) + else: + b.append("__") + + if monte_carlo: + print 'No board given. Using Monte-Carlo simulation...' + iters = random.randint(25000, 125000) + else: + iters = -1 + for h in holder.range.get(): + e = odds_for_hand( + [holder.hand.c1, holder.hand.c2], + [h.c1, h.c2], + b, + iterations=iters + ) + sev.add(e) + + sev.show(holder.hand, holder.range.get()) + + + +holder = Holder() +if not parse_args(sys.argv, holder): + usage(sys.argv[0]) + sys.exit(2) +odds_for_range(holder) + +# debugs +#print '%s, %s' % ( holder.hand.c1, holder.hand.c2) +#print '%s %s %s %s %s' % (holder.board.b1, holder.board.b2, +# holder.board.b3, holder.board.b4, holder.board.b5) +#while True: +# try: +# vl = holder.range.get() +# v = vl.pop() +# print '\t%s %s' % (v.c1, v.c2) +# except IndexError: +# break + + + + diff --git a/pyfpdb/TableWindow.py b/pyfpdb/TableWindow.py index 5aed1f04..d780bdbc 100644 --- a/pyfpdb/TableWindow.py +++ b/pyfpdb/TableWindow.py @@ -1,12 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Discover_TableWindow.py +"""Base class for interacting with poker client windows. -Inspects the currently open windows and finds those of interest to us--that is -poker table windows from supported sites. Returns a list -of Table_Window objects representing the windows found. +There are currently subclasses for X and Windows. + +The class queries the poker client window for data of interest, such as +size and location. It also controls the signals to alert the HUD when the +client has been resized, destroyed, etc. """ -# Copyright 2008-2010, Ray E. Barker +# Copyright 2008 - 2010, Ray E. Barker # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,37 +27,38 @@ of Table_Window objects representing the windows found. ######################################################################## # Standard Library modules -import os -import sys +import re # pyGTK modules -import pygtk import gtk import gobject # FreePokerTools modules -import Configuration -#if os.name == "posix": -# import XTables -#elif os.name == "nt": -# import WinTables +from HandHistoryConverter import getTableTitleRe +from HandHistoryConverter import getTableNoRe -# Global used for figuring out the current game being played from the title -# The dict key is the fpdb name for the game +# Global used for figuring out the current game being played from the title. +# The dict key is a tuple of (limit type, category) for the game. # The list is the names for those games used by the supported poker sites -# This is currently only used for HORSE, so it only needs to support those +# This is currently only used for mixed games, so it only needs to support those # games on PokerStars and Full Tilt. -game_names = { #fpdb name Stars Name FTP Name - "holdem" : ("Hold\'em" , ), - "omahahilo" : ("Omaha H/L" , ), - "studhilo" : ("Stud H/L" , ), - "razz" : ("Razz" , ), - "studhi" : ("Stud" , "Stud Hi") +nlpl_game_names = { #fpdb name Stars Name FTP Name (if different) + ("nl", "holdem" ) : ("No Limit Hold\'em" , ), + ("pl", "holdem" ) : ("Pot Limit Hold\'em" , ), + ("pl", "omahahi" ) : ("Pot Limit Omaha" ,"Pot Limit Omaha Hi" ), + } +limit_game_names = { #fpdb name Stars Name FTP Name + ("fl", "holdem" ) : ("Limit Hold\'em" , ), + ("fl", "omahahilo" ) : ("Limit Omaha H/L" , ), + ("fl", "studhilo" ) : ("Limit Stud H/L" , ), + ("fl", "razz" ) : ("Limit Razz" , ), + ("fl", "studhi" ) : ("Limit Stud" , "Stud Hi"), + ("fl", "27_3draw" ) : ("Limit Triple Draw 2-7 Lowball", ) } -# A window title might have our table name + one of theses words/ +# A window title might have our table name + one of these words/ # phrases. If it has this word in the title, it is not a table. -bad_words = ('History for table:', 'HUD:', 'Chat:') +bad_words = ('History for table:', 'HUD:', 'Chat:', 'FPDBHUD') # Here are the custom signals we define for allowing the 'client watcher' # thread to communicate with the gui thread. Any time a poker client is @@ -76,11 +79,19 @@ gobject.signal_new("client_destroyed", gtk.Window, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) +gobject.signal_new("game_changed", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + +gobject.signal_new("table_changed", gtk.Window, + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + # Each TableWindow object must have the following attributes correctly populated: # tw.name = the table name from the title bar, which must to match the table name -# from the corresponding hand history. -# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site -# name specified in the config file. +# from the corresponding hand record in the db. # tw.number = This is the system id number for the client table window in the # format that the system presents it. This is Xid in Xwindows and # hwnd in Microsoft Windows. @@ -92,60 +103,161 @@ gobject.signal_new("client_destroyed", gtk.Window, # to the top left of the display screen. This also does not include the # title bar and window borders. To put it another way, this is the # screen location of (0, 0) in the working window. +# tournament = Tournament number for a tournament or None for a cash game. +# table = Table number for a tournament. +# gdkhandle = +# window = +# parent = +# game = +# search_string = class Table_Window(object): - def __init__(self, search_string, table_name = None, tournament = None, table_number = None): + def __init__(self, config, site, table_name = None, tournament = None, table_number = None): + self.config = config + self.site = site if tournament is not None and table_number is not None: - print "tournament %s, table %s" % (tournament, table_number) self.tournament = int(tournament) self.table = int(table_number) self.name = "%s - %s" % (self.tournament, self.table) + self.type = "tour" + table_kwargs = dict(tournament = self.tournament, table_number = self.table) + self.tableno_re = getTableNoRe(self.config, self.site, tournament = self.tournament) elif table_name is not None: - # search_string = table_name self.name = table_name + self.type = "cash" self.tournament = None + table_kwargs = dict(table_name = table_name) + else: return None - self.find_table_parameters(search_string) + self.search_string = getTableTitleRe(self.config, self.site, self.type, **table_kwargs) + self.find_table_parameters() + + geo = self.get_geometry() + if geo is None: return None + self.width = geo['width'] + self.height = geo['height'] + self.x = geo['x'] + self.y = geo['y'] + self.oldx = self.x # attn ray: remove these two lines and update Hud.py::update_table_position() + self.oldy = self.y + + self.game = self.get_game() def __str__(self): # __str__ method for testing - likely_attrs = ("site", "number", "title", "width", "height", "x", "y", - "tournament", "table", "gdkhandle") + likely_attrs = ("number", "title", "site", "width", "height", "x", "y", + "tournament", "table", "gdkhandle", "window", "parent", + "game", "search_string", "tableno_re") temp = 'TableWindow object\n' for a in likely_attrs: if getattr(self, a, 0): temp += " %s = %s\n" % (a, getattr(self, a)) return temp +#################################################################### +# "get" methods. These query the table and return the info to get. +# They don't change the data in the table and are generally used +# by the "check" methods. Most of the get methods are in the +# subclass because they are specific to X, Windows, etc. def get_game(self): - title = self.get_window_title() - print title - for game, names in game_names.iteritems(): +# title = self.get_window_title() +# if title is None: +# return False + title = self.title + +# check for nl and pl games first, to avoid bad matches + for game, names in nlpl_game_names.iteritems(): for name in names: if name in title: return game - return None + for game, names in limit_game_names.iteritems(): + for name in names: + if name in title: + return game + return False - def check_geometry(self): + def get_table_no(self): + new_title = self.get_window_title() + if new_title is None: + return False + + try: + mo = re.search(self.tableno_re, new_title) + except AttributeError: #'Table' object has no attribute 'tableno_re' + return False + + if mo is not None: + #print "get_table_no: mo=",mo.groups() + return mo.group(1) + return False + +#################################################################### +# check_table() is meant to be called by the hud periodically to +# determine if the client has been moved or resized. check_table() +# also checks and signals if the client has been closed. + def check_table(self, hud): + result = self.check_size() + if result != False: + hud.parent.main_window.emit(result, hud) + if result == "client_destroyed": + return True + + result = self.check_loc() + if result != False: + hud.parent.main_window.emit(result, hud) + if result == "client_destroyed": + return True + return True + +#################################################################### +# "check" methods. They use the corresponding get method, update the +# table object and return the name of the signal to be emitted or +# False if unchanged. These do not signal for destroyed +# clients to prevent a race condition. + +# These might be called by a Window.timeout, so they must not +# return False, or the timeout will be cancelled. + def check_game(self, hud): + new_game = self.get_game() + if new_game is not None and self.game != new_game: + self.game = new_game + hud.main_window.emit("game_changed", hud) + return "game_changed" + return True + + def check_size(self): new_geo = self.get_geometry() - if new_geo is None: # window destroyed return "client_destroyed" - elif self.x != new_geo['x'] or self.y != new_geo['y']: # window moved - self.x = new_geo['x'] - self.y = new_geo['y'] - return "client_moved" - elif self.width != new_geo['width'] or self.height != new_geo['height']: # window resized self.width = new_geo['width'] self.height = new_geo['height'] return "client_resized" + return False # no change - else: return False # window not changed + def check_loc(self): + new_geo = self.get_geometry() + if new_geo is None: # window destroyed + return "client_destroyed" + + if self.x != new_geo['x'] or self.y != new_geo['y']: # window moved +# print self.x, self.y, new_geo['x'], new_geo['y'] + self.x = new_geo['x'] + self.y = new_geo['y'] + return "client_moved" + return False # no change + + def check_table_no(self, hud): + result = self.get_table_no() + if result != False and result != self.table: + self.table = result + if hud is not None: + hud.main_window.emit("table_changed", hud) + return True def check_bad_words(self, title): for word in bad_words: diff --git a/pyfpdb/Tables_Demo.py b/pyfpdb/Tables_Demo.py index f583ae48..3e633204 100755 --- a/pyfpdb/Tables_Demo.py +++ b/pyfpdb/Tables_Demo.py @@ -22,31 +22,19 @@ Main program module to test/demo the Tables subclasses. ######################################################################## +import L10n +_ = L10n.get_translation() + # Standard Library modules import sys import os -import re # pyGTK modules -import pygtk import gtk import gobject # fpdb/free poker tools modules import Configuration -from HandHistoryConverter import getTableTitleRe - -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string # get the correct module for the current os if os.name == 'posix': @@ -73,24 +61,31 @@ if __name__=="__main__": self.main_window.move(table.x + dx, table.y + dy) self.main_window.show_all() table.topify(self) + +# These are the currently defined signals. Do this in the HUD. self.main_window.connect("client_moved", self.client_moved) self.main_window.connect("client_resized", self.client_resized) self.main_window.connect("client_destroyed", self.client_destroyed) + self.main_window.connect("game_changed", self.game_changed) + self.main_window.connect("table_changed", self.table_changed) +# And these of the handlers that go with those signals. +# These would live inside the HUD code. def client_moved(self, widget, hud): self.main_window.move(self.table.x + self.dx, self.table.y + self.dy) def client_resized(self, *args): - print "client resized" + print "Client resized" def client_destroyed(self, *args): # call back for terminating the main eventloop + print "Client destroyed." gtk.main_quit() - def check_on_table(table, hud): - result = table.check_geometry() - if result != False: - hud.main_window.emit(result, hud) - return True + def game_changed(self, *args): + print "Game Changed." + + def table_changed(self, *args): + print "Table Changed." print _("enter table name to find: "), table_name = sys.stdin.readline() @@ -107,16 +102,12 @@ if __name__=="__main__": type = "cash" table_kwargs = dict(table_name = table_name) - search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs) - table = Tables.Table(search_string, **table_kwargs) - table.gdk_handle = gtk.gdk.window_foreign_new(table.number) - - print "table =", table -# print "game =", table.get_game() + table = Tables.Table(config, "Full Tilt Poker", **table_kwargs) + print table fake = fake_hud(table) - print "fake =", fake -# gobject.timeout_add(100, check_on_table, table, fake) - print _("calling main") + gobject.timeout_add(1000, table.check_game, fake) + gobject.timeout_add(100, table.check_table, fake) + print "calling main" gtk.main() diff --git a/pyfpdb/TestHandsPlayers.py b/pyfpdb/TestHandsPlayers.py index 944499ed..ee3be1e9 100755 --- a/pyfpdb/TestHandsPlayers.py +++ b/pyfpdb/TestHandsPlayers.py @@ -129,7 +129,7 @@ def main(argv=None): settings.update(config.get_import_parameters()) settings.update(config.get_default_paths()) db.recreate_tables() - importer = fpdb_import.Importer(False, settings, config) + importer = fpdb_import.Importer(False, settings, config, None) importer.setDropIndexes("don't drop") importer.setFailOnError(True) importer.setThreads(-1) @@ -142,28 +142,67 @@ def main(argv=None): BetfairErrors = FpdbError('Betfair') OnGameErrors = FpdbError('OnGame') AbsoluteErrors = FpdbError('Absolute Poker') + UltimateBetErrors = FpdbError('Ultimate Bet') EverleafErrors = FpdbError('Everleaf Poker') CarbonErrors = FpdbError('Carbon') PKRErrors = FpdbError('PKR') + iPokerErrors = FpdbError('iPoker') + Win2dayErrors = FpdbError('Win2day') + WinamaxErrors = FpdbError('Winamax') ErrorsList = [ PokerStarsErrors, FTPErrors, PartyPokerErrors, BetfairErrors, OnGameErrors, AbsoluteErrors, - EverleafErrors, CarbonErrors, PKRErrors + EverleafErrors, CarbonErrors, PKRErrors, + iPokerErrors, WinamaxErrors, UltimateBetErrors, + Win2dayErrors, ] - - walk_testfiles("regression-test-files/cash/Stars/", compare, importer, PokerStarsErrors, "PokerStars") - walk_testfiles("regression-test-files/tour/Stars/", compare, importer, PokerStarsErrors, "PokerStars") - walk_testfiles("regression-test-files/cash/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") - walk_testfiles("regression-test-files/tour/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") - walk_testfiles("regression-test-files/cash/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") - walk_testfiles("regression-test-files/tour/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") - walk_testfiles("regression-test-files/cash/Betfair/", compare, importer, BetfairErrors, "Betfair") - walk_testfiles("regression-test-files/cash/OnGame/", compare, importer, OnGameErrors, "OnGame") - walk_testfiles("regression-test-files/cash/Absolute/", compare, importer, AbsoluteErrors, "Absolute") - walk_testfiles("regression-test-files/cash/Everleaf/", compare, importer, EverleafErrors, "Everleaf") - walk_testfiles("regression-test-files/cash/Carbon/", compare, importer, CarbonErrors, "Carbon") - walk_testfiles("regression-test-files/cash/PKR/", compare, importer, PKRErrors, "PKR") + + sites = { + 'PokerStars' : True, + 'Full Tilt Poker' : True, + 'PartyPoker' : True, + 'Betfair' : True, + 'OnGame' : True, + 'Absolute' : True, + 'UltimateBet' : True, + 'Everleaf' : True, + 'Carbon' : True, + 'PKR' : False, + 'iPoker' : True, + 'Win2day' : True, + 'Winamax' : True, + } + + if sites['PokerStars'] == True: + walk_testfiles("regression-test-files/cash/Stars/", compare, importer, PokerStarsErrors, "PokerStars") + walk_testfiles("regression-test-files/tour/Stars/", compare, importer, PokerStarsErrors, "PokerStars") + if sites['Full Tilt Poker'] == True: + walk_testfiles("regression-test-files/cash/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") + walk_testfiles("regression-test-files/tour/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") + if sites['PartyPoker'] == True: + walk_testfiles("regression-test-files/cash/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") + walk_testfiles("regression-test-files/tour/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") + if sites['Betfair'] == True: + walk_testfiles("regression-test-files/cash/Betfair/", compare, importer, BetfairErrors, "Betfair") + if sites['OnGame'] == True: + walk_testfiles("regression-test-files/cash/OnGame/", compare, importer, OnGameErrors, "OnGame") + if sites['Absolute'] == True: + walk_testfiles("regression-test-files/cash/Absolute/", compare, importer, AbsoluteErrors, "Absolute") + if sites['UltimateBet'] == True: + walk_testfiles("regression-test-files/cash/UltimateBet/", compare, importer, UltimateBetErrors, "Absolute") + if sites['Everleaf'] == True: + walk_testfiles("regression-test-files/cash/Everleaf/", compare, importer, EverleafErrors, "Everleaf") + if sites['Carbon'] == True: + walk_testfiles("regression-test-files/cash/Carbon/", compare, importer, CarbonErrors, "Carbon") + if sites['PKR'] == True: + walk_testfiles("regression-test-files/cash/PKR/", compare, importer, PKRErrors, "PKR") + if sites['iPoker'] == True: + walk_testfiles("regression-test-files/cash/iPoker/", compare, importer, iPokerErrors, "iPoker") + if sites['Winamax'] == True: + walk_testfiles("regression-test-files/cash/Winamax/", compare, importer, WinamaxErrors, "Winamax") + if sites['Win2day'] == True: + walk_testfiles("regression-test-files/cash/Win2day/", compare, importer, Win2dayErrors, "Win2day") totalerrors = 0 diff --git a/pyfpdb/TestSummaryImport.py b/pyfpdb/TestSummaryImport.py new file mode 100755 index 00000000..2aa2a81b --- /dev/null +++ b/pyfpdb/TestSummaryImport.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2010, Carl Gherardi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +######################################################################## + +import sys +import os +import codecs +import pprint +import Configuration +import Database +import SQL +from GuiTourneyImport import SummaryImporter + + +class FpdbError: + def __init__(self, sitename): + self.site = sitename + self.errorcount = 0 + self.histogram = {} + self.statcount = {} + + def error_report(self, filename, hand, stat, ghash, testhash, player): + print "Regression Test Error:" + print "\tFile: %s" % filename + print "\tStat: %s" % stat + print "\tPlayer: %s" % player + if filename in self.histogram: + self.histogram[filename] += 1 + else: + self.histogram[filename] = 1 + + if stat in self.statcount: + self.statcount[stat] += 1 + else: + self.statcount[stat] = 1 + self.errorcount += 1 + + def print_histogram(self): + print "%s:" % self.site + for f in self.histogram: + idx = f.find('regression') + print "(%3d) : %s" %(self.histogram[f], f[idx:]) + +def compare(leaf, importer, errors, site): + filename = leaf + #print "DEBUG: fileanme: %s" % filename + + if filename.endswith('.txt'): + # test if there is a .hp version of the file + importer.addImportFileOrDir(filename, site = site) + (stored, errs) = importer.runImport() + +# if os.path.isfile(filename + '.hp') and errs < 1: +# # Compare them +# hashfilename = filename + '.hp' +# +# in_fh = codecs.open(hashfilename, 'r', 'utf8') +# whole_file = in_fh.read() +# in_fh.close() +# +# testhash = eval(whole_file) +# +# hhc = importer.getCachedHHC() +# handlist = hhc.getProcessedHands() +# #We _really_ only want to deal with a single hand here. +# for hand in handlist: +# ghash = hand.stats.getHandsPlayers() +# for p in ghash: +# #print "DEBUG: player: '%s'" % p +# pstat = ghash[p] +# teststat = testhash[p] +# +# for stat in pstat: +# #print "pstat[%s][%s]: %s == %s" % (p, stat, pstat[stat], teststat[stat]) +# try: +# if pstat[stat] == teststat[stat]: +# # The stats match - continue +# pass +# else: +# # Stats don't match - Doh! +# errors.error_report(filename, hand, stat, ghash, testhash, p) +# except KeyError, e: +# errors.error_report(filename, False, "KeyError: '%s'" % stat, False, False, p) + if errs > 0: + errors.error_report(filename, False, "Parse", False, False, False) + + importer.clearFileList() + + + +def walk_testfiles(dir, function, importer, errors, site): + """Walks a directory, and executes a callback on each file """ + dir = os.path.abspath(dir) + for file in [file for file in os.listdir(dir) if not file in [".",".."]]: + nfile = os.path.join(dir,file) + if os.path.isdir(nfile): + walk_testfiles(nfile, compare, importer, errors, site) + else: + print "***********************************" + compare(nfile, importer, errors, site) + print "***********************************" + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + config = Configuration.Config(file = "HUD_config.test.xml") + db = Database.Database(config) + sql = SQL.Sql(db_server = 'sqlite') + db.recreate_tables() + + importer = SummaryImporter(config, sql, None) + + PokerStarsErrors = FpdbError('PokerStars') + FTPErrors = FpdbError('Full Tilt Poker') + #PartyPokerErrors = FpdbError('Party Poker') + #BetfairErrors = FpdbError('Betfair') + #OnGameErrors = FpdbError('OnGame') + #AbsoluteErrors = FpdbError('Absolute Poker') + #UltimateBetErrors = FpdbError('Ultimate Bet') + #EverleafErrors = FpdbError('Everleaf Poker') + #CarbonErrors = FpdbError('Carbon') + #PKRErrors = FpdbError('PKR') + #iPokerErrors = FpdbError('iPoker') + #WinamaxErrors = FpdbError('Winamax') + + ErrorsList = [ + PokerStarsErrors, + FTPErrors, #PartyPokerErrors, + #BetfairErrors, OnGameErrors, AbsoluteErrors, + #EverleafErrors, CarbonErrors, PKRErrors, + #iPokerErrors, WinamaxErrors, UltimateBetErrors, + ] + + sites = { + 'PokerStars' : True, + 'Full Tilt Poker' : True, + #'PartyPoker' : True, + #'Betfair' : True, + #'OnGame' : True, + #'Absolute' : True, + #'UltimateBet' : True, + #'Everleaf' : True, + #'Carbon' : True, + #'PKR' : False, + #'iPoker' : True, + #'Winamax' : True, + } + + if sites['PokerStars'] == True: + walk_testfiles("regression-test-files/summaries/Stars/", compare, importer, PokerStarsErrors, "PokerStars") + if sites['Full Tilt Poker'] == True: + walk_testfiles("regression-test-files/summaries/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") + # walk_testfiles("regression-test-files/tour/FTP/", compare, importer, FTPErrors, "Full Tilt Poker") + #if sites['PartyPoker'] == True: + # walk_testfiles("regression-test-files/cash/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") + # walk_testfiles("regression-test-files/tour/PartyPoker/", compare, importer, PartyPokerErrors, "PartyPoker") + #if sites['Betfair'] == True: + # walk_testfiles("regression-test-files/cash/Betfair/", compare, importer, BetfairErrors, "Betfair") + #if sites['OnGame'] == True: + # walk_testfiles("regression-test-files/cash/OnGame/", compare, importer, OnGameErrors, "OnGame") + #if sites['Absolute'] == True: + # walk_testfiles("regression-test-files/cash/Absolute/", compare, importer, AbsoluteErrors, "Absolute") + #if sites['UltimateBet'] == True: + # walk_testfiles("regression-test-files/cash/UltimateBet/", compare, importer, UltimateBetErrors, "Absolute") + #if sites['Everleaf'] == True: + # walk_testfiles("regression-test-files/cash/Everleaf/", compare, importer, EverleafErrors, "Everleaf") + #if sites['Carbon'] == True: + # walk_testfiles("regression-test-files/cash/Carbon/", compare, importer, CarbonErrors, "Carbon") + #if sites['PKR'] == True: + # walk_testfiles("regression-test-files/cash/PKR/", compare, importer, PKRErrors, "PKR") + #if sites['iPoker'] == True: + # walk_testfiles("regression-test-files/cash/iPoker/", compare, importer, iPokerErrors, "iPoker") + #if sites['Winamax'] == True: + # walk_testfiles("regression-test-files/cash/Winamax/", compare, importer, WinamaxErrors, "Winamax") + + totalerrors = 0 + + for i, site in enumerate(ErrorsList): + totalerrors += ErrorsList[i].errorcount + + print "---------------------" + print "Total Errors: %d" % totalerrors + print "---------------------" + for i, site in enumerate(ErrorsList): + ErrorsList[i].print_histogram() + + # Merge the dicts of stats from the various error objects + statdict = {} + for i, site in enumerate(ErrorsList): + tmp = ErrorsList[i].statcount + for stat in tmp: + if stat in statdict: + statdict[stat] += tmp[stat] + else: + statdict[stat] = tmp[stat] + + print "\n" + print "---------------------" + print "Errors by stat:" + print "---------------------" + #for stat in statdict: + # print "(%3d) : %s" %(statdict[stat], stat) + + sortedstats = sorted([(value,key) for (key,value) in statdict.items()]) + for num, stat in sortedstats: + print "(%3d) : %s" %(num, stat) + + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/pyfpdb/TournamentTracker.py b/pyfpdb/TournamentTracker.py index da7135b9..455626ec 100644 --- a/pyfpdb/TournamentTracker.py +++ b/pyfpdb/TournamentTracker.py @@ -21,6 +21,9 @@ ######################################################################## +import L10n +_ = L10n.get_translation() + # to do allow window resizing # to do hud to echo, but ignore non numbers # to do no stat window for hero @@ -34,18 +37,6 @@ import traceback (options, argv) = Options.fpdb_options() -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - if not options.errorsToConsole: print _("Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_.") errorFile = open('tourneyerror.txt', 'w', 0) diff --git a/pyfpdb/TourneyFilters.py b/pyfpdb/TourneyFilters.py index ddb98166..7b3f235a 100644 --- a/pyfpdb/TourneyFilters.py +++ b/pyfpdb/TourneyFilters.py @@ -15,6 +15,9 @@ #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. +import L10n +_ = L10n.get_translation() + import threading import pygtk pygtk.require('2.0') @@ -29,18 +32,6 @@ from time import gmtime, mktime, strftime, strptime import logging #logging has been set up in fpdb.py or HUD_main.py, use their settings: log = logging.getLogger("filter") -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - #import Configuration #import Database #import SQL diff --git a/pyfpdb/TourneySummary.py b/pyfpdb/TourneySummary.py index f78d96de..4ed91b65 100644 --- a/pyfpdb/TourneySummary.py +++ b/pyfpdb/TourneySummary.py @@ -17,6 +17,9 @@ """parses and stores summary sections from e.g. eMail or summary files""" +import L10n +_ = L10n.get_translation() + # TODO: check to keep only the needed modules import re @@ -31,21 +34,10 @@ import time,datetime from copy import deepcopy from Exceptions import * -import locale -lang=locale.getdefaultlocale()[0][0:2] -if lang=="en": - def _(string): return string -else: - import gettext - try: - trans = gettext.translation("fpdb", localedir="locale", languages=[lang]) - trans.install() - except IOError: - def _(string): return string - import pprint import DerivedStats import Card +import Database log = logging.getLogger("parser") @@ -57,7 +49,7 @@ class TourneySummary(object): LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} # SAL- TO KEEP ?? SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} - SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 } + SITEIDS = {'Fulltilt':1, 'Full Tilt Poker':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 } def __init__(self, db, config, siteName, summaryText, builtFrom = "HHC"): @@ -125,11 +117,13 @@ class TourneySummary(object): self.sym = None if builtFrom=="IMAP": - self.parseSummary() - self.insertOrUpdate() - elif builtFrom == "file": - self.parseSummaryFile() - self.insertOrUpdate() + # Fix line endings? + pass + if self.db == None: + self.db = Database.Database(config) + + self.parseSummary() + self.insertOrUpdate() #end def __init__ def __str__(self): @@ -300,148 +294,3 @@ winnings (decimal) the money the player ended the tourney with (can be 0, or def printSummary(self): self.writeSummary(sys.stdout) - -def assemble(cnxn, tourneyId): #TODO: move this method to Hand or Database - # TODO !! - c = cnxn.cursor() - - # We need at least siteName, gametype, handid - # for the Hand.__init__ - c.execute(""" -select - s.name, - g.category, - g.base, - g.type, - g.limitType, - g.hilo, - round(g.smallBlind / 100.0,2), - round(g.bigBlind / 100.0,2), - round(g.smallBet / 100.0,2), - round(g.bigBet / 100.0,2), - s.currency, - h.boardcard1, - h.boardcard2, - h.boardcard3, - h.boardcard4, - h.boardcard5 -from - hands as h, - sites as s, - gametypes as g, - handsplayers as hp, - players as p -where - h.id = %(handid)s -and g.id = h.gametypeid -and hp.handid = h.id -and p.id = hp.playerid -and s.id = p.siteid -limit 1""", {'handid':handid}) - #TODO: siteid should be in hands table - we took the scenic route through players here. - res = c.fetchone() - gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]} - h = HoldemOmahaHand(hhc = None, siteName=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid) - cards = map(Card.valueSuitFromCard, res[11:16] ) - if cards[0]: - h.setCommunityCards('FLOP', cards[0:3]) - if cards[3]: - h.setCommunityCards('TURN', [cards[3]]) - if cards[4]: - h.setCommunityCards('RIVER', [cards[4]]) - #[Card.valueSuitFromCard(x) for x in cards] - - # HandInfo : HID, TABLE - # BUTTON - why is this treated specially in Hand? - # answer: it is written out in hand histories - # still, I think we should record all the active seat positions in a seat_order array - c.execute(""" -SELECT - h.sitehandno as hid, - h.tablename as table, - h.startTime as startTime -FROM - Hands as h -WHERE h.id = %(handid)s -""", {'handid':handid}) - res = c.fetchone() - h.handid = res[0] - h.tablename = res[1] - h.startTime = res[2] # automatically a datetime - - # PlayerStacks - c.execute(""" -SELECT - hp.seatno, - round(hp.winnings / 100.0,2) as winnings, - p.name, - round(hp.startcash / 100.0,2) as chips, - hp.card1,hp.card2, - hp.position -FROM - handsplayers as hp, - players as p -WHERE - hp.handid = %(handid)s -and p.id = hp.playerid -""", {'handid':handid}) - for (seat, winnings, name, chips, card1,card2, position) in c.fetchall(): - h.addPlayer(seat,name,chips) - if card1 and card2: - h.addHoleCards(map(Card.valueSuitFromCard, (card1,card2)), name, dealt=True) - if winnings > 0: - h.addCollectPot(name, winnings) - if position == 'B': - h.buttonpos = seat - - - # actions - c.execute(""" -SELECT - (ha.street,ha.actionno) as actnum, - p.name, - ha.street, - ha.action, - ha.allin, - round(ha.amount / 100.0,2) -FROM - handsplayers as hp, - handsactions as ha, - players as p -WHERE - hp.handid = %(handid)s -and ha.handsplayerid = hp.id -and p.id = hp.playerid -ORDER BY - ha.street,ha.actionno -""", {'handid':handid}) - res = c.fetchall() - for (actnum,player, streetnum, act, allin, amount) in res: - act=act.strip() - street = h.allStreets[streetnum+1] - if act==u'blind': - h.addBlind(player, 'big blind', amount) - # TODO: The type of blind is not recorded in the DB. - # TODO: preflop street name anomalies in Hand - elif act==u'fold': - h.addFold(street,player) - elif act==u'call': - h.addCall(street,player,amount) - elif act==u'bet': - h.addBet(street,player,amount) - elif act==u'check': - h.addCheck(street,player) - elif act==u'unbet': - pass - else: - print act, player, streetnum, allin, amount - # TODO : other actions - - #hhc.readShowdownActions(self) - #hc.readShownCards(self) - h.totalPot() - h.rake = h.totalpot - h.totalcollected - - - return h - diff --git a/pyfpdb/TreeViewTooltips.py b/pyfpdb/TreeViewTooltips.py new file mode 100644 index 00000000..9b891708 --- /dev/null +++ b/pyfpdb/TreeViewTooltips.py @@ -0,0 +1,424 @@ +# Copyright (c) 2006, Daniel J. Popowich +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Send bug reports and contributions to: +# +# dpopowich AT astro dot umass dot edu +# +# This version of the file is part of fpdb, contact: fpdb-main@lists.sourceforge.net + +''' +TreeViewTooltips.py + +Provides TreeViewTooltips, a class which presents tooltips for cells, +columns and rows in a gtk.TreeView. + +------------------------------------------------------------ + This file includes a demo. Just execute the file: + + python TreeViewTooltips.py +------------------------------------------------------------ + +To use, first subclass TreeViewTooltips and implement the get_tooltip() +method; see below. Then add any number of gtk.TreeVew widgets to a +TreeViewTooltips instance by calling the add_view() method. Overview +of the steps: + + # 1. subclass TreeViewTooltips + class MyTooltips(TreeViewTooltips): + + # 2. overriding get_tooltip() + def get_tooltip(...): + ... + + # 3. create an instance + mytips = MyTooltips() + + # 4. Build up your gtk.TreeView. + myview = gtk.TreeView() + ...# create columns, set the model, etc. + + # 5. Add the view to the tooltips + mytips.add_view(myview) + +How it works: the add_view() method connects the TreeView to the +"motion-notify" event with the callback set to a private method. +Whenever the mouse moves across the TreeView the callback will call +get_tooltip() with the following arguments: + + get_tooltip(view, column, path) + +where, + + view: the gtk.TreeView instance. + column: the gtk.TreeViewColumn instance that the mouse is + currently over. + path: the path to the row that the mouse is currently over. + +Based on whether or not column and path are checked for specific +values, get_tooltip can return tooltips for a cell, column, row or the +whole view: + + Column Checked Path Checked Tooltip For... + Y Y cell + Y N column + N Y row + N N view + +get_tooltip() should return None if no tooltip should be displayed. +Otherwise the return value will be coerced to a string (with the str() +builtin) and stripped; if non-empty, the result will be displayed as +the tooltip. By default, the tooltip popup window will be displayed +centered and just below the pointer and will remain shown until the +pointer leaves the cell (or column, or row, or view, depending on how +get_tooltip() is implemented). + +''' + + +import pygtk +pygtk.require('2.0') + +import gtk +import gtk.gdk +import gobject + +if gtk.gtk_version < (2, 8): + import warnings + + msg = (_('''This module was developed and tested with version 2.8.18 of gtk. You are using version %d.%d.%d. Your milage may vary.''') + % gtk.gtk_version) + warnings.warn(msg) + + +# major, minor, patch +version = 1, 0, 0 + +class TreeViewTooltips: + + def __init__(self): + + ''' + Initialize the tooltip. After initialization there are two + attributes available for advanced control: + + window: the popup window that holds the tooltip text, an + instance of gtk.Window. + label: a gtk.Label that is packed into the window. The + tooltip text is set in the label with the + set_label() method, so the text can be plain or + markup text. + + Be default, the tooltip is enabled. See the enabled/disabled + methods. + ''' + + # create the window + self.window = window = gtk.Window(gtk.WINDOW_POPUP) + window.set_name('gtk-tooltips') + window.set_resizable(False) + window.set_border_width(4) + window.set_app_paintable(True) + window.connect("expose-event", self.__on_expose_event) + + + # create the label + self.label = label = gtk.Label() + label.set_line_wrap(True) + label.set_alignment(0.5, 0.5) + label.set_use_markup(True) + label.show() + window.add(label) + + # by default, the tooltip is enabled + self.__enabled = True + # saves the current cell + self.__save = None + # the timer id for the next tooltip to be shown + self.__next = None + # flag on whether the tooltip window is shown + self.__shown = False + + def enable(self): + 'Enable the tooltip' + + self.__enabled = True + + def disable(self): + 'Disable the tooltip' + + self.__enabled = False + + def __show(self, tooltip, x, y): + + '''show the tooltip popup with the text/markup given by + tooltip. + + tooltip: the text/markup for the tooltip. + x, y: the coord. (root window based) of the pointer. + ''' + + window = self.window + + # set label + self.label.set_label(tooltip) + # resize window + w, h = window.size_request() + # move the window + window.move(*self.location(x,y,w,h)) + # show it + window.show() + self.__shown = True + + def __hide(self): + 'hide the tooltip' + + self.__queue_next() + self.window.hide() + self.__shown = False + + def __leave_handler(self, view, event): + 'when the pointer leaves the view, hide the tooltip' + + self.__hide() + + def __motion_handler(self, view, event): + 'As the pointer moves across the view, show a tooltip.' + + path = view.get_path_at_pos(int(event.x), int(event.y)) + + if self.__enabled and path: + path, col, x, y = path + tooltip = self.get_tooltip(view, col, path) + if tooltip is not None: + tooltip = str(tooltip).strip() + if tooltip: + self.__queue_next((path, col), tooltip, + int(event.x_root), + int(event.y_root)) + return + + self.__hide() + + def __queue_next(self, *args): + + 'queue next request to show a tooltip' + + # if args is non-empty it means a request was made to show a + # tooltip. if empty, no request is being made, but any + # pending requests should be cancelled anyway. + + cell = None + + # if called with args, break them out + if args: + cell, tooltip, x, y = args + + # if it's the same cell as previously shown, just return + if self.__save == cell: + return + + # if we have something queued up, cancel it + if self.__next: + gobject.source_remove(self.__next) + self.__next = None + + # if there was a request... + if cell: + # if the tooltip is already shown, show the new one + # immediately + if self.__shown: + self.__show(tooltip, x, y) + # else queue it up in 1/2 second + else: + self.__next = gobject.timeout_add(500, self.__show, + tooltip, x, y) + + # save this cell + self.__save = cell + + + def __on_expose_event(self, window, event): + + # this magic is required so the window appears with a 1-pixel + # black border (default gtk Style). This code is a + # transliteration of the C implementation of gtk.Tooltips. + w, h = window.size_request() + window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, + gtk.SHADOW_OUT, None, window, + 'tooltip', 0, 0, w, h) + + def location(self, x, y, w, h): + + '''Given the x,y coordinates of the pointer and the width and + height (w,h) demensions of the tooltip window, return the x, y + coordinates of the tooltip window. + + The default location is to center the window on the pointer + and 4 pixels below it. + ''' + + return x - w/2, y + 4 + + def add_view(self, view): + + 'add a gtk.TreeView to the tooltip' + + assert isinstance(view, gtk.TreeView), \ + ('This handler should only be connected to ' + 'instances of gtk.TreeView') + + view.connect("motion-notify-event", self.__motion_handler) + view.connect("leave-notify-event", self.__leave_handler) + + def get_tooltip(self, view, column, path): + 'See the module doc string for a description of this method' + + raise NotImplemented, 'Subclass must implement get_tooltip()' + + +if __name__ == '__main__': + + ############################################################ + # DEMO + ############################################################ + + # First, subclass TreeViewTooltips + + class DemoTips(TreeViewTooltips): + + def __init__(self, customer_column): + # customer_column is an instance of gtk.TreeViewColumn and + # is being used in the gtk.TreeView to show customer names. + self.cust_col = customer_column + + # call base class init + TreeViewTooltips.__init__(self) + + def get_tooltip(self, view, column, path): + + # we have a two column view: customer, phone; we'll make + # tooltips cell-based for the customer column, but generic + # column-based for the phone column. + + # customer + if column is self.cust_col: + + # By checking both column and path we have a + # cell-based tooltip. + model = view.get_model() + customer = model[path][2] + return '%s %s\n%s' % (customer.fname, + customer.lname, + customer.notes) + # phone + else: + return ('Generic Column Tooltip\n' + 'Unless otherwise noted, all\narea codes are 888') + + def XX_location(self, x, y, w, h): + # rename me to "location" so I override the base class + # method. This will demonstrate being able to change + # where the tooltip window popups, relative to the + # pointer. + + # this will place the tooltip above and to the right + return x + 10, y - (h + 10) + + # Here's our customer + class Customer: + + def __init__(self, fname, lname, phone, notes): + self.fname = fname + self.lname = lname + self.phone = phone + self.notes = notes + + # create a bunch of customers + customers = [] + for fname, lname, phone, notes in [ + ('Joe', 'Schmoe', '555-1212', 'Likes to Morris dance.'), + ('Jane', 'Doe', '555-2323', + 'Wonders what the hell\nMorris dancing is.'), + ('Phred', 'Phantastic', '900-555-1212', 'Dreams of Betty.'), + ('Betty', 'Boop', '555-3434', 'Dreams in b&w.'), + ('Red Sox', 'Fan', '555-4545', + "Still livin' 2004!\nEspecially after 2006.")]: + customers.append(Customer(fname, lname, phone, notes)) + + # Build our model and view + model = gtk.ListStore(str, str, object) + for c in customers: + model.append(['%s %s' % (c.fname, c.lname), c.phone, c]) + + view = gtk.TreeView(model) + view.get_selection().set_mode(gtk.SELECTION_NONE) + + # two columns, name and phone + cell = gtk.CellRendererText() + cell.set_property('xpad', 20) + namecol = gtk.TreeViewColumn('Customer Name', cell, text=0) + namecol.set_min_width(200) + view.append_column(namecol) + + cell = gtk.CellRendererText() + phonecol = gtk.TreeViewColumn('Phone', cell, text=1) + view.append_column(phonecol) + + # finally, connect the tooltip, specifying the name column as the + # column we want the tooltip to popup over. + tips = DemoTips(namecol) + tips.add_view(view) + + # We're going to demonstrate enable/disable. First we need a + # callback function to connect to the toggled signal. + def toggle(button): + if button.get_active(): + tips.disable() + else: + tips.enable() + + # create a checkbutton and connect our handler + check = gtk.CheckButton('Check to disable view tooltips') + check.connect('toggled', toggle) + + # a standard gtk.Tooltips to compare to + tt = gtk.Tooltips() + tt.set_tip(check, ('This is a standard gtk tooltip.\n' + 'Compare me to the tooltips above.')) + + # create a VBox to pack the view and checkbutton + vbox = gtk.VBox() + vbox.pack_start(view) + vbox.pack_start(check, False) + vbox.show_all() + + # pack the vbox into a simple dialog and run it + dialog = gtk.Dialog('TreeViewTooltips Demo') + close = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_NONE) + + # add a tooltip for the close button + tt.set_tip(close, 'Click to end the demo.') + + dialog.set_default_size(400,400) + dialog.vbox.pack_start(vbox) + dialog.run() diff --git a/pyfpdb/UltimateBetToFpdb.py b/pyfpdb/UltimateBetToFpdb.py deleted file mode 100755 index 580244bb..00000000 --- a/pyfpdb/UltimateBetToFpdb.py +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2008-2010 Carl Gherardi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -######################################################################## - -import sys -from HandHistoryConverter import * - - -class UltimateBet(HandHistoryConverter): - - # Static regexes - re_GameInfo = re.compile("Stage #(?P[0-9]+):\s+\(?(?PHold\'em|Razz|Seven Card|Omaha|Omaha Hi/Lo|Badugi) (?PNo Limit|Normal|Pot Limit),? \(?(?P\$|)?(?P[.0-9]+)/\$?(?P[.0-9]+)\) - (?P.*$)", re.MULTILINE) - re_SplitHands = re.compile('(\n\n\n+)') - re_HandInfo = re.compile("^Table \'(?P
[- a-zA-Z]+)\'(?P.+?$)?", re.MULTILINE) - re_Button = re.compile('Seat #(?P
[ a-zA-Z]+) - \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P.*) - (?P
[0-9]+):(?P[0-9]+) ET - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)Table (?P
[ a-zA-Z]+)\nSeat (?P
[^']+)\'\s(?P\d+)\-max + """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) + + re_TailSplitHands = re.compile(r'\n\s*\n') + re_Button = re.compile(r'Seat\s#(?P