diff --git a/packaging/windows/py2exeWalkthroughPython26.txt b/packaging/windows/py2exeWalkthroughPython26.txt index 68a74c81..7ad865c0 100644 --- a/packaging/windows/py2exeWalkthroughPython26.txt +++ b/packaging/windows/py2exeWalkthroughPython26.txt @@ -30,14 +30,17 @@ py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2ex psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe (Note: stickpeople is the offical repository, not a community build) +(py)pokereval v138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pypoker-eval-win32/pokereval-138.win32.exe/download +(Note: There are no official windows builds, this installer is built from source. A walkthrough is in the same directory as this walkthrough. 1.2/ MySQL -MySQL-python-1.2.3.win32-py2.6-fpdb0.20.exe ... http://www.mediafire.com/file/iodnnnznmj1/MySQL-python-1.2.3.win32-py2.6-fpdb0.20.exe +Install the following file: -This is an intaller built from source by gimick. There are no official mysql-python2.6 builds for windows. +MySQL-python-1.2.3.win32-py2.6-fpdb0.20.exe ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3-win32-py2.6/MySQL-python-1.2.3.win32-py2.6-fpdb0.20.exe/download -Community builds are also available from some developers. see www.codegood.com for example. +Note: This is an intaller built from source by gimick. A walkthrough to build this installer is in the same directory as this walkthrough. +Note: There is no official mysql-python2.6 build for windows. Community builds are available from some developers. see www.codegood.com for example. 1.3/ pytz fixup to work in an executable package diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 8bf723a4..84b75886 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -68,7 +68,6 @@ import matplotlib import shutil #from datetime import date - def isSystemDLL(pathname): #dwmapi appears to be vista-specific file, not XP if os.path.basename(pathname).lower() in ("dwmapi.dll"): @@ -112,7 +111,7 @@ def copy_file(source,destination): shutil.copy( source, destination ) -fpdbver = '0.20.906' +fpdbver = '0.21.rc1' distdir = r'fpdb-' + fpdbver rootdir = r'../../' #cwd is normally /packaging/windows @@ -120,6 +119,7 @@ pydir = rootdir+'pyfpdb/' gfxdir = rootdir+'gfx/' sys.path.append( pydir ) # allows fpdb modules to be found by options/includes below + print "\n" + r"Output will be created in "+distdir print "*** Cleaning working folders ***" @@ -142,15 +142,19 @@ setup( {'script': pydir+'Configuration.py', } ], + console = [ {'script': pydir+'Stove.py', } + ], + options = {'py2exe': { 'packages' : ['encodings', 'matplotlib'], 'includes' : ['gio', 'cairo', 'pango', 'pangocairo', 'atk', 'gobject' ,'matplotlib.numerix.random_array' ,'AbsoluteToFpdb', 'BetfairToFpdb' ,'CarbonToFpdb', 'EverleafToFpdb' - ,'FulltiltToFpdb', 'OnGameToFpdb' - ,'PartyPokerToFpdb', 'PokerStarsToFpdb' - ,'UltimateBetToFpdb', 'Win2dayToFpdb' + ,'FulltiltToFpdb', 'iPokerToFpdb' + ,'OnGameToFpdb', 'PartyPokerToFpdb' + ,'PkrToFpdb', 'PokerStarsToFpdb' + ,'Win2dayToFpdb', 'WinamaxToFpdb' ], 'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], 'dll_excludes': ['libglade-2.0-0.dll', 'libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll' diff --git a/packaging/windows/pypoker138walkthrough.txt b/packaging/windows/pypoker138walkthrough.txt new file mode 100644 index 00000000..1c7ee0f4 --- /dev/null +++ b/packaging/windows/pypoker138walkthrough.txt @@ -0,0 +1,191 @@ +pypokereval build for windows stepbystep guide +---------------------------------------------- + +Created by Gimick on 3rd December 2010 + +This walkthrough is derived with the assistance of EricBlade and the build notes +supplied by Loic Dachary http://dachary.org/ + +Content is available under the the GNU Affero General Public License version 3 + + +0. Build environ +---------------- + +We are building against the 2008 runtime because Python 2.6 + has the same dependency (msvcr90.dll version 9.0.21022.8) + +Using winXPhome 32 bit + +1 Visual studio +--------------- + +1.1/ Get the ISO CD from here ... http://www.microsoft.com/express/Downloads/#2008-All + +1.2/ Run and install Visual C++ only, don't bother with the additional packages offered + +This package will run 30 days before registration is needed + +2. Python runtime +----------------- + +2.1/ Install python runtime from here ... + +Python 2.6.5 ... http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi + +3. Source install +----------------- + +3.1/ grab sources from here + +pypoker-eval v138 ... http://download.gna.org/pokersource/sources/pypoker-eval-138.0.tar.gz +poker-eval v138 ... http://download.gna.org/pokersource/sources/poker-eval-138.0.tar.gz + +3.2/ unpack and place the pypoker-eval-138 directory in c:\ +3.2.1/ rename to pypoker-eval + +3.3/ unpack and place the poker-eval-138 directory in c:\ +3.3.1/ rename to poker-eval + +Important: the build will fail with bizarre missing header files if the project is placed + in a directory containing a space character - you have been warned! + +4. Update source file +--------------------- + +4.1/ dos> write c:/pypoker-eval/pypokereval.c + +change this: + +#define VERSION_NAME(W) W##2_4 +#define PYTHON_VERSION "2_4" + +to be this: + +#define VERSION_NAME(W) W##2_6 +#define PYTHON_VERSION "2_6" + +4.2/ save and exit + +4.3/ dos> write c:/pypoker-eval/pokereval.py + +Comment-out this line: + +_pokereval = __import__('_pokereval_' + sys.version[0] + '_' + sys.version[2]) + +Insert this one in its' place: + +import _pokereval_2_6 as _pokereval + + +4.4/ save and exit + + +5. Build pre-preparation +------------------------ + +(Here we are converting the two project definition files to 2008) + +5.1 navigate to directory c:/poker-eval +5.1.1 double click poker-eval.vcproj +5.1.2 Visual studio will launch and make a conversion - accept all defaults +5.1.3 exit and save + +5.2 navigate to directory c:/pypoker-eval +5.2.1 double click pypoker-eval.vcproj +5.2.2 Visual studio will launch and make a conversion - accept all defaults +5.2.3 exit + +6. build preparation +-------------------- + +6.2 navigate to directory c:/pypoker-eval +6.2.1 double click pypoker-eval.vcproj - visual studio should launch + +6.2.3 Select Build...configuration manager... + Select "active solution configuration" to "Release" + (The configuration for both projects will change to "Release") + +6.2.3 Close the configuration manager + +6.2.4 In the solution explorer window, hilight pythonpoker-eval / right mouse / properties... + +6.2.5 In the pythonpoker-eval properties dialog, + +change references to "python24" to "python26" in the following: + + = C/C++/Additional Include Directories/ + = linker/general/Additional library directories + = linker/input/Additional Dependencies + +Change the following + + = linker/generate debug info - set to No + = linker/debugging/Generate debug info - set to No + +6.2.6 Apply all changes to the properties dialog and close + +6.3 Exit from visual studio + +7. Build poker eval +------------------- + +7.1 navigate to directory c:/poker-eval +7.1.1 double click poker-eval.vcproj +7.1.2 Visual studio will launch + +7.2 In the solution explorer window, hilight poker-eval / right mouse / build + +7.3 There should be no errors + +7.4 Exit from visual studio + + +8. Build pypoker eval +--------------------- + +8.1 navigate to directory c:/pypoker-eval +8.1.1 double click pypoker-eval.vcproj +8.1.2 Visual studio will launch + +8.2 In the solution explorer window, hilight pythonpoker-eval / right mouse / build + +8.3 There should be no errors (but a few warnings) + +8.4 Exit from visual studio + +9. packaging +------------ + +9.1 Navigate to c:/pypoker-eval/release +9.2 the output file is pypokereval.dll +9.3 rename this file to _pokereval_2_6.pyd + +9.4 create a zip file containing : + +_pokereval_2_6.pyd from releases +test.py from pypoker-eval-138.0 +pokereval.py from pypoker-eval-138.0 +poker-eval.vcproj from c:\poker-eval +pypoker-eval.vcproj from c:\pypoker-eval +pypokereval.c from c:\pypoker-eval + +Remember to include the version (138), python 265 and win32 in the package filename + +10. Installation and Testing +---------------------------- + +Python 2.6.5 must be installed + +10.1 Extract this package to directory +10.2 Change directory to the directory in 10.1 +10.3 execute dos> c:\Python26\python.exe test.py +10.4 hand-output should scroll down the screen +10.5 start the python interpreter +10.6 >>> import pokereval +10.7 No errors should be seen + + + + + diff --git a/packaging/windows/pypokereval-win32-packaging-walkthrough.txt b/packaging/windows/pypokereval-win32-packaging-walkthrough.txt new file mode 100644 index 00000000..b0fbe5f9 --- /dev/null +++ b/packaging/windows/pypokereval-win32-packaging-walkthrough.txt @@ -0,0 +1,159 @@ +pypokereval packaging for fpdb project +-------------------------------------- + +Created by Gimick on 11th December 2010 +Content is available under the the GNU Affero General Public License version 3 + +Background +---------- + +The walktrough builds an unoffical python installer package for pypokereval. + +In a previous walkthrough, the pypokereval dll(pyd) was built from source. +In this walkthrough, we are going to generate a windows package which will allow +the sources to be installed through distutils onto a client computer + +The current situation is that there is no windows build or windows installer provided +by the package authors. + +Until a package is available, the fpdb project needs some method of providing a windows +installation for the pypokereval module. This walkthrough will therefore be obsolete once +an official package becomes available. + +Actually, for windows fpdb users, the majority will use a pre-built fpdb executable (which contains the +pypokereval package) Therefore, pypokereval installation will only be needed for two groups of people: + i) anyone wanting to build an fpdb package using py2exe + ii) anyone wanting to run fpdb on windows from source + +Credits +------- + +To loic@dachary.org at pokersource http://pokersource.sourceforge.net/ +To donn.ingle@gmail.com for the tutorial here ... http://wiki.python.org/moin/Distutils/Tutorial +Official python reference here ... http://docs.python.org/distutils/index.html + +Assumptions +----------- + +The underlying dll(pyd) and pokereval library is built for win32 x86 platform only, so we will assume +32bit only in this walkthrough. Contributions for a x86-64 build are most welcome. + +Similarly, the underlying package is built against the python 2-6 library and is valid only for that version. +The underlying package was not built with sse enabled, and therefore should work for legacy systems. + + +1. Install pre-requisites +------------------------- + +System used for building is winXP home + +1.1/ Install python runtime from here ... + +Python 2.6.5 ... http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi + +1.2/ Grab pypokereval stuff from fpdb project here ... + +pypokereval ... http://sourceforge.net/projects/fpdb/files/fpdb/pypoker-eval-win32/dev/pypokereval-138-win32-py265-fpdb-1.1.exe/download + +1.3/ Double click the pypokereval-138-win32-py265-fpdb-1.1.exe and extract the folder to the desktop + + +2. Prepare a folder containing the items needing packaging +---------------------------------------------------------- + +dos>cd desktop +dos>mkdir temp +dos>cd temp +dos>mkdir pokereval +dos>cd pokereval + +dos>copy ..\..\pypokereval-138-win32-py265-fpdb-1.1\_pokereval_2_6.pyd +dos>copy ..\..\pypokereval-138-win32-py265-fpdb-1.1\pokereval.py + +dos>mkdir utils +dos>cd utils +dos>copy ..\..\..\pypokereval-138-win32-py265-fpdb-1.1\test.py +dos>cd .. + +The next step is to rename the pokereval.py file to __init__.py. The reason for doing this +is that site-packages require an __init__ file to be found when the module is imported at runtime +Inserting a dummy __init__.py which simply imports pokereval does not work, the reason being that the +PokerEval class is not seen by the caller. Syntax such as "from pokereval import PokerEval" +does not work unless the pokereval.py (which defines the PokerEval class) is renamed to __init__.py. + +dos> rename pokereval.py __init__.py + +3. Prepare the additional packaging files +----------------------------------------- + +dos>cd desktop +dos>cd temp + +3.1/ setup.py +------------- + +dos> edit setup.py + +Include the following python code: + +#================================================== + +from distutils.core import setup + +filelist = ["utils/*", "_pokereval_2_6.pyd"] + +setup(name = "pokereval", +version = "138", +description = "pypokereval installer (unofficial)", +author = "project fpdb", +author_email = "Fpdb-main@lists.sourceforge.net", +url = "fpdb.sourceforge.net", +packages = ['pokereval'], +package_data = {'pokereval' : filelist }, +long_description = """An unofficial and experimental installer for pypokereval v138 +Built for 32bit windows and python v2.6.5 +pokereval official project page is at http://www.pokersource.info/""" +) + +#================================================== + +3.2 / review directory tree +--------------------------- + +The following structure should now exist. + +temp + |--setup.py + |--pokereval + |--_pokereval_2_6.pyd + |--__init__.py + |--utils + |--test.py + + +3.3 Build +--------- + +Navigate to the desktop/temp directory + +dos>c:\Python26\python.exe setup.py bdist_wininst --plat-name=win32 --user-access-control force + +Note: UAC auto seems to cause lockup on my win7 system + +3.4 Complete +------------ + +The executable file pokereval-138.win32.exe will be in the newly-created dist folder. + +3.5 Install and test +-------------------- + +Double click to install +Navigate to c:\python26\lib\site-packages\pokereval\test + +execute dos>c:\Python26\python.exe test.py + +Output should scroll down the screen + + + diff --git a/pyfpdb/AlchemyMappings.py b/pyfpdb/AlchemyMappings.py index 5e93a259..b5891c25 100644 --- a/pyfpdb/AlchemyMappings.py +++ b/pyfpdb/AlchemyMappings.py @@ -211,7 +211,7 @@ class HandInternal(DerivedStats): def isDuplicate(self, session): """Checks if current hand already exists in db - siteHandNo ans gameTypeId have to be setted + siteHandNo ans gametypeId have to be setted """ return session.query(HandInternal).filter_by( siteHandNo=self.siteHandNo, gametypeId=self.gametypeId).count()!=0 diff --git a/pyfpdb/Anonymise.py b/pyfpdb/Anonymise.py old mode 100644 new mode 100755 index 166651e8..04f4185d --- a/pyfpdb/Anonymise.py +++ b/pyfpdb/Anonymise.py @@ -26,6 +26,8 @@ import HandHistoryConverter import Configuration import sys +# command line is: +# ./Anonymise.py -f -k (options, argv) = Options.fpdb_options() config = Configuration.Config() diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py index a2d2f56f..b9d80791 100644 --- a/pyfpdb/CarbonToFpdb.py +++ b/pyfpdb/CarbonToFpdb.py @@ -66,7 +66,7 @@ class Carbon(HandHistoryConverter): # Static regexes re_SplitHands = re.compile(r'\n+(?=)') - re_GameInfo = re.compile(r'', re.MULTILINE) + re_GameInfo = re.compile(r'', re.MULTILINE) re_HandInfo = re.compile(r'[0-9]+)">') re_PlayerInfo = re.compile(r'', re.MULTILINE) diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index b3ce09ff..8d426a0d 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -303,6 +303,7 @@ def encodeRazzStartHand(cards): encodeRazzList = { '(00)A':-13,'(00)2':-12,'(00)3':-11,'(00)4':-10,'(00)5':-9,'(00)6':-8,'(00)7':-7,'(00)8':-6,'(00)9':-5,'(00)T':-4, '(00)J':-3,'(00)Q':-2,'(00)K':-1, + '(00)0':0, '(32)A':1,'(3A)2':2,'(2A)3':3,'(42)A':4,'(4A)2':5,'(2A)4':6,'(43)A':7,'(4A)3':8,'(3A)4':9, '(43)2':10,'(42)3':11,'(32)4':12,'(52)A':13,'(5A)2':14,'(2A)5':15,'(53)A':16,'(5A)3':17,'(3A)5':18,'(53)2':19, '(52)3':20,'(32)5':21,'(54)A':22,'(5A)4':23,'(4A)5':24,'(54)2':25,'(52)4':26,'(42)5':27,'(54)3':28,'(53)4':29, diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 4e01b796..a0ad4bad 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -479,12 +479,14 @@ class Import: self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhBulkPath = node.getAttribute("hhBulkPath") self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=False) + self.cacheSessions = string_to_bool(node.getAttribute("cacheSessions"), default=False) + self.sessionTimeout = string_to_bool(node.getAttribute("sessionTimeout"), default=30) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False) self.saveStarsHH = string_to_bool(node.getAttribute("saveStarsHH"), default=False) def __str__(self): return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ - % (self.interval, self.callFpdbHud, self.hhArchiveBase, self.saveActions, self.fastStoreHudCache) + % (self.interval, self.callFpdbHud, self.hhArchiveBase, self.saveActions, self.cacheSessions, self.sessionTimeout, self.fastStoreHudCache) class HudUI: def __init__(self, node): @@ -1259,6 +1261,12 @@ class Config: try: imp['saveActions'] = self.imp.saveActions except: imp['saveActions'] = False + + try: imp['cacheSessions'] = self.imp.cacheSessions + except: imp['cacheSessions'] = False + + try: imp['sessionTimeout'] = self.imp.sessionTimeout + except: imp['sessionTimeout'] = 30 try: imp['saveStarsHH'] = self.imp.saveStarsHH except: imp['saveStarsHH'] = False diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 0647baf2..943479cd 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -73,7 +73,7 @@ except ImportError: use_numpy = False -DB_VERSION = 144 +DB_VERSION = 147 # Variance created as sqlite has a bunch of undefined aggregate functions. @@ -255,6 +255,13 @@ class Database: self.database = db_params['db-databaseName'] self.host = db_params['db-host'] self.db_path = '' + gen = c.get_general_params() + self.day_start = 0 + + if 'day_start' in gen: + self.day_start = float(gen['day_start']) + + self.sessionTimeout = float(self.import_options['sessionTimeout']) # where possible avoid creating new SQL instance by using the global one passed in if sql is None: @@ -307,7 +314,7 @@ class Database: tables=self.cursor.execute(self.sql.query['list_tables']) tables=self.cursor.fetchall() - for table in (u'Actions', u'Autorates', u'Backings', u'Gametypes', u'Hands', u'HandsActions', u'HandsPlayers', u'HudCache', u'Players', u'RawHands', u'RawTourneys', u'Settings', u'Sites', u'TourneyTypes', u'Tourneys', u'TourneysPlayers'): + for table in (u'Actions', u'Autorates', u'Backings', u'Gametypes', u'Hands', u'HandsActions', u'HandsPlayers', u'HudCache', u'SessionsCache', u'Players', u'RawHands', u'RawTourneys', u'Settings', u'Sites', u'TourneyTypes', u'Tourneys', u'TourneysPlayers'): print "table:", table result+="###################\nTable "+table+"\n###################\n" rows=self.cursor.execute(self.sql.query['get'+table]) @@ -689,12 +696,16 @@ class Database: else: if row and row[0]: self.hand_1day_ago = int(row[0]) - - d = timedelta(days=hud_days) + + tz = datetime.utcnow() - datetime.today() + tz_offset = tz.seconds/3600 + tz_day_start_offset = self.day_start + tz_offset + + d = timedelta(days=hud_days, hours=tz_day_start_offset) now = datetime.utcnow() - d self.date_ndays_ago = "d%02d%02d%02d" % (now.year - 2000, now.month, now.day) - - d = timedelta(days=h_hud_days) + + d = timedelta(days=h_hud_days, hours=tz_day_start_offset) now = datetime.utcnow() - d self.h_date_ndays_ago = "d%02d%02d%02d" % (now.year - 2000, now.month, now.day) @@ -788,7 +799,7 @@ class Database: elif h_hud_style == 'S': h_stylekey = 'zzzzzzz' # all stylekey values should be lower than this else: - h_stylekey = '000000' + h_stylekey = '00000000' log.info('h_hud_style: %s' % h_hud_style) #elif h_hud_style == 'H': @@ -904,6 +915,56 @@ class Database: result = c.fetchall() return result + def resetPlayerIDs(self): + self.pcache = None + + def getSqlPlayerIDs(self, pnames, siteid): + result = {} + if(self.pcache == None): + self.pcache = LambdaDict(lambda key:self.insertPlayer(key[0], key[1])) + + for player in pnames: + result[player] = self.pcache[(player,siteid)] + # NOTE: Using the LambdaDict does the same thing as: + #if player in self.pcache: + # #print "DEBUG: cachehit" + # pass + #else: + # self.pcache[player] = self.insertPlayer(player, siteid) + #result[player] = self.pcache[player] + + return result + + def insertPlayer(self, name, site_id): + result = None + _name = Charset.to_db_utf8(name) + c = self.get_cursor() + q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s" + q = q.replace('%s', self.sql.query['placeholder']) + + #NOTE/FIXME?: MySQL has ON DUPLICATE KEY UPDATE + #Usage: + # INSERT INTO `tags` (`tag`, `count`) + # VALUES ($tag, 1) + # ON DUPLICATE KEY UPDATE `count`=`count`+1; + + + #print "DEBUG: name: %s site: %s" %(name, site_id) + + c.execute (q, (site_id, _name)) + + tmp = c.fetchone() + if (tmp == None): #new player + c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder']) + ,(_name, site_id)) + #Get last id might be faster here. + #c.execute ("SELECT id FROM Players WHERE name=%s", (name,)) + result = self.get_last_insert_id(c) + else: + result = tmp[1] + return result + + def get_last_insert_id(self, cursor=None): ret = None try: @@ -1168,6 +1229,7 @@ class Database: c.execute(self.sql.query['createHandsPlayersTable']) c.execute(self.sql.query['createHandsActionsTable']) c.execute(self.sql.query['createHudCacheTable']) + c.execute(self.sql.query['createSessionsCacheTable']) c.execute(self.sql.query['createBackingsTable']) c.execute(self.sql.query['createRawHands']) c.execute(self.sql.query['createRawTourneys']) @@ -1534,6 +1596,11 @@ class Database: print _("Error rebuilding hudcache:"), str(sys.exc_value) print err #end def rebuild_hudcache + + def rebuild_sessionscache(self, h_start=None, v_start=None): + """clears sessionscache and rebuilds from the individual handsplayers records""" + #Will get to this soon + pass def get_hero_hudcache_start(self): """fetches earliest stylekey from hudcache for one of hero's player ids""" @@ -1625,7 +1692,13 @@ class Database: # NEWIMPORT CODE ########################### - def storeHand(self, p): + def storeHand(self, p, printdata = False): + if printdata: + print _("######## Hands ##########") + import pprint + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(p) + print _("###### End Hands ########") #stores into table hands: q = self.sql.query['store_hand'] @@ -1635,7 +1708,7 @@ class Database: c.execute(q, ( p['tableName'], - p['gameTypeId'], + p['gametypeId'], p['siteHandNo'], p['tourneyId'], p['startTime'], @@ -1793,10 +1866,12 @@ class Database: def storeHandsActions(self, hid, pids, hpid, adata, printdata = False): #print "DEBUG: %s %s %s" %(hid, pids, adata) - if printdata: - import pprint - pp = pprint.PrettyPrinter(indent=4) - pp.pprint(adata) + + # This can be used to generate test data. Currently unused + #if printdata: + # import pprint + # pp = pprint.PrettyPrinter(indent=4) + # pp.pprint(adata) inserts = [] for a in adata: @@ -1823,8 +1898,15 @@ class Database: def storeHudCache(self, gid, pids, starttime, pdata): """Update cached statistics. If update fails because no record exists, do an insert.""" + tz = datetime.utcnow() - datetime.today() + tz_offset = tz.seconds/3600 + tz_day_start_offset = self.day_start + tz_offset + + d = timedelta(hours=tz_day_start_offset) + starttime_offset = starttime - d + if self.use_date_in_hudcache: - styleKey = datetime.strftime(starttime, 'd%y%m%d') + styleKey = datetime.strftime(starttime_offset, 'd%y%m%d') #styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day) else: # hard-code styleKey as 'A000000' (all-time cache, no key) for now @@ -1950,6 +2032,127 @@ class Database: else: #print "DEBUG: Successfully updated HudCacho using UPDATE" pass + + def storeSessionsCache(self, pids, startTime, game, pdata): + """Update cached sessions. If update fails because no record exists, do an insert""" + + THRESHOLD = timedelta(seconds=int(self.sessionTimeout * 60)) + bigBet = int(Decimal(game['bb'])*200) + + select_sessionscache = self.sql.query['select_sessionscache'] + select_sessionscache = select_sessionscache.replace('%s', self.sql.query['placeholder']) + select_sessionscache_mid = self.sql.query['select_sessionscache_mid'] + select_sessionscache_mid = select_sessionscache_mid.replace('%s', self.sql.query['placeholder']) + select_sessionscache_start = self.sql.query['select_sessionscache_start'] + select_sessionscache_start = select_sessionscache_start.replace('%s', self.sql.query['placeholder']) + + update_sessionscache_mid = self.sql.query['update_sessionscache_mid'] + update_sessionscache_mid = update_sessionscache_mid.replace('%s', self.sql.query['placeholder']) + update_sessionscache_start = self.sql.query['update_sessionscache_start'] + update_sessionscache_start = update_sessionscache_start.replace('%s', self.sql.query['placeholder']) + update_sessionscache_end = self.sql.query['update_sessionscache_end'] + update_sessionscache_end = update_sessionscache_end.replace('%s', self.sql.query['placeholder']) + + insert_sessionscache = self.sql.query['insert_sessionscache'] + insert_sessionscache = insert_sessionscache.replace('%s', self.sql.query['placeholder']) + merge_sessionscache = self.sql.query['merge_sessionscache'] + merge_sessionscache = merge_sessionscache.replace('%s', self.sql.query['placeholder']) + delete_sessions = self.sql.query['delete_sessions'] + delete_sessions = delete_sessions.replace('%s', self.sql.query['placeholder']) + + #Grab playerIds using hero names in HUD_Config.xml + try: + # derive list of program owner's player ids + self.hero = {} # name of program owner indexed by site id + self.hero_ids = [] + # make sure at least two values in list + # so that tuple generation creates doesn't use + # () or (1,) style + for site in self.config.get_supported_sites(): + result = self.get_site_id(site) + if result: + site_id = result[0][0] + self.hero[site_id] = self.config.supported_sites[site].screen_name + p_id = self.get_player_id(self.config, site, self.hero[site_id]) + if p_id: + self.hero_ids.append(int(p_id)) + + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print _("Error aquiring hero ids:"), str(sys.exc_value) + print err + + inserts = [] + for p in pdata: + if pids[p] in self.hero_ids: + line = [0]*5 + + if (game['type']=='ring'): line[0] = 1 # count ring hands + if (game['type']=='tour'): line[1] = 1 # count tour hands + if (game['type']=='ring'): line[2] = pdata[p]['totalProfit'] #sum of profit + if (game['type']=='ring'): line[3] = float(Decimal(pdata[p]['totalProfit'])/Decimal(bigBet)) #sum of big bets won + line[4] = startTime + inserts.append(line) + + cursor = self.get_cursor() + + for row in inserts: + threshold = [] + threshold.append(row[-1]-THRESHOLD) + threshold.append(row[-1]+THRESHOLD) + cursor.execute(select_sessionscache, threshold) + num = cursor.rowcount + if (num == 1): + # Try to do the update first: + #print "DEBUG: found 1 record to update" + update_mid = row + row[-1:] + cursor.execute(select_sessionscache_mid, update_mid[-2:]) + mid = cursor.rowcount + if (mid == 0): + update_startend = row[-1:] + row + threshold + cursor.execute(select_sessionscache_start, update_startend[-3:]) + start = cursor.rowcount + if (start == 0): + #print "DEBUG:", start, " start record found. Update stats and start time" + cursor.execute(update_sessionscache_end, update_startend) + else: + #print "DEBUG: 1 end record found. Update stats and end time time" + cursor.execute(update_sessionscache_start, update_startend) + else: + #print "DEBUG: update stats mid-session" + cursor.execute(update_sessionscache_mid, update_mid) + elif (num > 1): + # Multiple matches found - merge them into one session and update: + #print "DEBUG:", num, "matches found" + cursor.execute(merge_sessionscache, threshold) + merge = cursor.fetchone() + cursor.execute(delete_sessions, threshold) + cursor.execute(insert_sessionscache, merge) + update_mid = row + row[-1:] + cursor.execute(select_sessionscache_mid, update_mid[-2:]) + mid = cursor.rowcount + if (mid == 0): + update_startend = row[-1:] + row + threshold + cursor.execute(select_sessionscache_start, update_startend[-3:]) + start = cursor.rowcount + if (start == 0): + #print "DEBUG:", start, " start record found. Update stats and start time" + cursor.execute(update_sessionscache_end, update_startend) + else: + #print "DEBUG: 1 end record found. Update stats and end time time" + cursor.execute(update_sessionscache_start, update_startend) + else: + #print "DEBUG: update stats mid-session" + cursor.execute(update_sessionscache_mid, update_mid) + elif (num == 0): + # No matches found, insert new session: + insert = row + row[-1:] + insert = insert[-2:] + insert[:-2] + #print "DEBUG: No matches found. Insert record", insert + cursor.execute(insert_sessionscache, insert) + else: + # Something bad happened + pass def isDuplicate(self, gametypeID, siteHandNo): dup = False @@ -1978,54 +2181,6 @@ class Database: #FIXME: recognise currency return tmp[0] - def resetPlayerIDs(self): - self.pcache = None - - def getSqlPlayerIDs(self, pnames, siteid): - result = {} - if(self.pcache == None): - self.pcache = LambdaDict(lambda key:self.insertPlayer(key[0], key[1])) - - for player in pnames: - result[player] = self.pcache[(player,siteid)] - # NOTE: Using the LambdaDict does the same thing as: - #if player in self.pcache: - # #print "DEBUG: cachehit" - # pass - #else: - # self.pcache[player] = self.insertPlayer(player, siteid) - #result[player] = self.pcache[player] - - return result - - def insertPlayer(self, name, site_id): - result = None - _name = Charset.to_db_utf8(name) - c = self.get_cursor() - q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s" - q = q.replace('%s', self.sql.query['placeholder']) - - #NOTE/FIXME?: MySQL has ON DUPLICATE KEY UPDATE - #Usage: - # INSERT INTO `tags` (`tag`, `count`) - # VALUES ($tag, 1) - # ON DUPLICATE KEY UPDATE `count`=`count`+1; - - - #print "DEBUG: name: %s site: %s" %(name, site_id) - - c.execute (q, (site_id, _name)) - - tmp = c.fetchone() - if (tmp == None): #new player - c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)".replace('%s',self.sql.query['placeholder']) - ,(_name, site_id)) - #Get last id might be faster here. - #c.execute ("SELECT id FROM Players WHERE name=%s", (name,)) - result = self.get_last_insert_id(c) - else: - result = tmp[1] - return result def insertGameTypes(self, row): c = self.get_cursor() diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index cd63e871..003f5270 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -135,7 +135,7 @@ class DerivedStats(): #print "DEBUG: vpip: %s" %(self.hands['playersVpi']) self.playersAtStreetX(hand) # Gives playersAtStreet1..4 and Showdown #print "DEBUG: playersAtStreet 1:'%s' 2:'%s' 3:'%s' 4:'%s'" %(self.hands['playersAtStreet1'],self.hands['playersAtStreet2'],self.hands['playersAtStreet3'],self.hands['playersAtStreet4']) - self.streetXRaises(hand) # Empty function currently + self.streetXRaises(hand) def assembleHandsPlayers(self, hand): #street0VPI/vpip already called in Hand diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index 586d36d9..fced271a 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -106,7 +106,10 @@ or None if we fail to get the info """ m = self.re_GameInfo.search(handText) if not m: - return 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) mg = m.groupdict() diff --git a/pyfpdb/FullTiltPokerSummary.py b/pyfpdb/FullTiltPokerSummary.py index ba8b85cb..6ad7560e 100644 --- a/pyfpdb/FullTiltPokerSummary.py +++ b/pyfpdb/FullTiltPokerSummary.py @@ -33,6 +33,7 @@ class FullTiltPokerSummary(TourneySummary): games = { # base, category "Hold'em" : ('hold','holdem'), 'Omaha' : ('hold','omahahi'), + 'Omahai Hi' : ('hold','omahahi'), 'Omaha Hi/Lo' : ('hold','omahahilo'), 'Razz' : ('stud','razz'), 'RAZZ' : ('stud','razz'), @@ -55,7 +56,7 @@ class FullTiltPokerSummary(TourneySummary): re_TourneyInfo = re.compile(u""" \s.* (?PTournament|Sit\s\&\sGo)\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+ + (?PHold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s+ (?PNo\sLimit|Limit|LIMIT|Pot\sLimit)\s+ (Buy-In:\s\$(?P[.\d]+)(\s\+\s\$(?P[.\d]+))?\s+)? (Buy-In\sChips:\s(?P\d+)\s+)? diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index 756dd494..487cb12d 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -63,10 +63,10 @@ class Fulltilt(HandHistoryConverter): [%(LS)s]?(?P[.0-9]+)/[%(LS)s]?(?P[.0-9]+)\s(Ante\s[%(LS)s]?(?P[.0-9]+)\s)?-\s [%(LS)s]?(?P[.0-9]+\sCap\s)? (?P[-\da-zA-Z\/\'\s]+)\s-\s - (?P\d+:\d+:\d+\s(?P\w+)\s-\s\d+/\d+/\d+|\d+:\d+\s(?P\w+)\s-\s\w+\,\s\w+\s\d+\,\s\d+) + (?P.*$) (?P\(partial\))?\n (?:.*?\n(?PHand\s\#(?P=HID)\shas\sbeen\scanceled))? - ''' % substitutions, re.VERBOSE|re.DOTALL) + ''' % substitutions, re.MULTILINE|re.VERBOSE) re_TourneyExtraInfo = re.compile('''(((?P[^$]+)? (?P[%(LS)s])?(?P[.0-9]+)?\s*\+\s*[%(LS)s]?(?P[.0-9]+)? (\s(?P(KO|Heads\sUp|Matrix\s\dx|Rebuy|Madness)))? @@ -123,6 +123,7 @@ class Fulltilt(HandHistoryConverter): re_Mixed = re.compile(r'\s\-\s(?PHA|HORSE|HOSE)\s\-\s', re.VERBOSE) re_Max = re.compile("(?P\d+)( max)?", re.MULTILINE) # NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports. + re_DateTime = re.compile("""((?P[0-9]+):(?P[0-9]+):(?P[0-9]+)\s(?P\w+)\s-\s(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})|(?P

[0-9]+):(?P[0-9]+)\s(?P\w+)\s-\s\w+\,\s(?P\w+)\s(?P\d+)\,\s(?P[0-9]{4}))""", re.MULTILINE) @@ -225,18 +226,24 @@ class Fulltilt(HandHistoryConverter): hand.handid = m.group('HID') hand.tablename = m.group('TABLE') - timezone = "ET" - if m.group('TZ1') == "CET" or m.group('TZ2') == "CET": - timezone = "CET" - try: - stringformat = "%H:%M:%S " + m.group('TZ1') + " - %Y/%m/%d" - hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), stringformat) - except: - stringformat = "%H:%M " + m.group('TZ2') + " - %a, %B %d, %Y" - hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), stringformat) + if m.group('DATETIME'): + # This section of code should match either a single date (which is ET) or + # the last date in the header, which is also recorded in ET. + timezone = "ET" + m1 = self.re_DateTime.finditer(m.group('DATETIME')) + datetimestr = "2000/01/01 00:00:00" + for a in m1: + if a.group('TZ2') == None: + 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')) + timezone = a.group('TZ') + hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") + else: # Short-lived date format + datetimestr = "%s/%s/%s %s:%s" % (a.group('Y2'), a.group('M2'),a.group('D2'),a.group('H2'),a.group('MIN2')) + timezone = a.group('TZ2') + hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%B/%d %H:%M") + + hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, timezone, "UTC") - hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, timezone, "UTC") - if m.group("CANCELLED") or m.group("PARTIAL"): raise FpdbParseError(hid=m.group('HID')) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 0121509d..03bec89d 100755 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -204,15 +204,18 @@ class GuiAutoImport (threading.Thread): # That is not correct. It should open another dir for importing while piping the # results to the same pipe. This means that self.path should be a a list of dirs # to watch. - if widget.get_active(): # toggled on + if data == "autostart" or (widget == self.startButton and self.startButton.get_active()): + self.startButton.set_active(True) # - Does the lock acquisition need to be more sophisticated for multiple dirs? # (see comment above about what to do if pipe already open) # - Ideally we want to release the lock if the auto-import is killed by some # kind of exception - is this possible? - if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired + if self.settings['global_lock'].acquire(wait=False, source="AutoImport"): # returns false immediately if lock not acquired self.addText(_("\nGlobal lock taken ... Auto Import Started.\n")) self.doAutoImportBool = True - widget.set_label(_(u' _Stop Auto Import ')) + self.startButton.set_label(_(u' _Stop Auto Import ')) + while gtk.events_pending(): # change the label NOW don't wait for the pipe to open + gtk.main_iteration(False) if self.pipe_to_hud is None: if Configuration.FROZEN: # if py2exe, run hud_main.exe path = Configuration.EXEC_PATH @@ -224,15 +227,14 @@ class GuiAutoImport (threading.Thread): command = 'pythonw "'+path+'\\HUD_main.pyw" ' + self.settings['cl_options'] else: command = 'python "'+path+'\\HUD_main.pyw" ' + self.settings['cl_options'] - # uncomment above line if you want hud_main stdout to work ... and make sure you are running fpdb.py using python.exe not pythonw.exe bs = 0 else: command = os.path.join(sys.path[0], 'HUD_main.pyw') command = [command, ] + string.split(self.settings['cl_options']) bs = 1 - try: print _("opening pipe to HUD") + try: if Configuration.FROZEN or (os.name == "nt" and win32console.GetConsoleWindow()) == 0: self.pipe_to_hud = subprocess.Popen(command, bufsize=bs, stdin=subprocess.PIPE, @@ -242,8 +244,6 @@ class GuiAutoImport (threading.Thread): ) else: self.pipe_to_hud = subprocess.Popen(command, bufsize=bs, stdin=subprocess.PIPE, universal_newlines=True) - #self.pipe_to_hud.stdout.close() - #self.pipe_to_hud.stderr.close() except: err = traceback.extract_tb(sys.exc_info()[2])[-1] #self.addText( "\n*** GuiAutoImport Error opening pipe: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])) @@ -268,8 +268,9 @@ class GuiAutoImport (threading.Thread): if self.pipe_to_hud.poll() is not None: self.addText(_("\n * Stop Auto Import: HUD already terminated")) else: + self.pipe_to_hud.terminate() #print >>self.pipe_to_hud.stdin, "\n" - self.pipe_to_hud.communicate('\n') # waits for process to terminate + # self.pipe_to_hud.communicate('\n') # waits for process to terminate self.pipe_to_hud = None self.startButton.set_label(_(u' Start _Auto Import ')) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 443d65aa..2880a7d7 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -224,9 +224,6 @@ class GuiGraphViewer (threading.Thread): self.graphBox.add(self.canvas) self.canvas.show() self.canvas.draw() - - #TODO: Do something useful like alert user - #print "No hands returned by graph query" else: self.ax.set_title(_("Profit graph for ring games"+names),fontsize=12) @@ -340,7 +337,10 @@ class GuiGraphViewer (threading.Thread): if len(winnings) == 0: return (None, None, None) - green = map(lambda x:float(x[1]), winnings) + #Insert a 0th entry into winnings so graph starts 'zerod' + winnings.insert(0, (0,0,0)) + + green = map(lambda x: float(x[1]), winnings) blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings) red = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings) greenline = cumsum(green) diff --git a/pyfpdb/GuiPositionalStats.py b/pyfpdb/GuiPositionalStats.py index 2f7c36cc..06d28883 100644 --- a/pyfpdb/GuiPositionalStats.py +++ b/pyfpdb/GuiPositionalStats.py @@ -397,7 +397,7 @@ class GuiPositionalStats (threading.Thread): query = query.replace("", bigblindselect) query = query.replace("", "") query = query.replace("", "-1") - query = query.replace("", "-1") + query = query.replace("", "-1") else: if self.db.backend == self.MYSQL_INNODB: bigblindselect = """concat('$', trim(leading ' ' from @@ -416,7 +416,7 @@ class GuiPositionalStats (threading.Thread): query = query.replace("", bigblindselect) query = query.replace("", ",gt.bigBlind") query = query.replace("", "hc.gametypeId") - query = query.replace("", "h.gameTypeId") + query = query.replace("", "h.gametypeId") # Filter on dates query = query.replace("", " between '" + dates[0] + "' and '" + dates[1] + "'") diff --git a/pyfpdb/GuiReplayer.py b/pyfpdb/GuiReplayer.py new file mode 100644 index 00000000..ba5a985e --- /dev/null +++ b/pyfpdb/GuiReplayer.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#Copyright 2010 Maxime Grandchamp +#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() + + +from Hand import * +import Configuration +import Database +import SQL +import fpdb_import +import Filters +import pygtk +pygtk.require('2.0') +import gtk +import math +import gobject + + +class GuiReplayer: + def __init__(self, config, querylist, mainwin, options = None, debug=True): + self.debug = debug + self.conf = config + self.main_window = mainwin + self.sql = querylist + + # These are temporary variables until it becomes possible + # to select() a Hand object from the database + self.filename="regression-test-files/cash/Stars/Flop/NLHE-FR-USD-0.01-0.02-201005.microgrind.txt" + self.site="PokerStars" + + if options.filename != None: + self.filename = options.filename + if options.sitename != None: + self.site = options.sitename + + self.db = Database.Database(self.conf, sql=self.sql) + + filters_display = { "Heroes" : True, + "Sites" : False, + "Games" : False, + "Limits" : False, + "LimitSep" : False, + "LimitType" : False, + "Type" : False, + "Seats" : False, + "SeatSep" : False, + "Dates" : True, + "Groups" : False, + "GroupsAll" : False, + "Button1" : True, + "Button2" : False + } + + + self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display) + #self.filters.registerButton1Name(_("Import Hand")) + #self.filters.registerButton1Callback(self.importhand) + #self.filters.registerButton2Name(_("temp")) + #self.filters.registerButton2Callback(self.temp()) + + # hierarchy: self.mainHBox / self.hpane / self.replayBox / self.area + + self.mainHBox = gtk.HBox(False, 0) + self.mainHBox.show() + + self.leftPanelBox = self.filters.get_vbox() + + self.hpane = gtk.HPaned() + self.hpane.pack1(self.leftPanelBox) + self.mainHBox.add(self.hpane) + + self.replayBox = gtk.VBox(False, 0) + self.replayBox.show() + + self.hpane.pack2(self.replayBox) + self.hpane.show() + + self.area=gtk.DrawingArea() + self.pangolayout = self.area.create_pango_layout("") + self.area.connect("expose-event", self.area_expose) + self.style = self.area.get_style() + self.gc = self.style.fg_gc[gtk.STATE_NORMAL] + self.area.show() + + self.replayBox.pack_start(self.area) + + self.MyHand = self.importhand() + + self.maxseats=self.MyHand.maxseats + + if self.MyHand.gametype['currency']=="USD": #TODO: check if there are others .. + self.currency="$" + elif self.MyHand.gametype['currency']=="EUR": + self.currency="€" + + + self.table={} #create table with positions, player names, status (live/folded), stacks and chips on table + for i in range(0,self.maxseats): # radius: 200, center: 250,250 + x= int (round(250+200*math.cos(2*i*math.pi/self.maxseats))) + y= int (round(250+200*math.sin(2*i*math.pi/self.maxseats))) + try: + self.table[i]={"name":self.MyHand.players[i][1],"stack":Decimal(self.MyHand.players[i][2]),"x":x,"y":y,"chips":0,"status":"live"} #save coordinates of each player + try: + self.table[i]['holecards']=self.MyHand.holecards["PREFLOP"][self.MyHand.players[i][1]][1]+' '+self.MyHand.holecards["PREFLOP"][self.MyHand.players[i][1]][2] + print "holecards: ",self.table[i]['holecards'] + except: + self.table[i]['holecards']='' + except IndexError: #if seat is empty + print "seat ",i+1," out of ",self.maxseats," empty" + + self.actions=[] #create list with all actions + + if isinstance(self.MyHand, HoldemOmahaHand): + if self.MyHand.gametype['category'] == 'holdem': + self.play_holdem() + + self.action_number=0 + self.action_level=0 + self.pot=0 + gobject.timeout_add(1000,self.draw_action) + + + def area_expose(self, area, event): + self.style = self.area.get_style() + self.gc = self.style.fg_gc[gtk.STATE_NORMAL] + + playerid='999' #makes sure we have an error if player is not recognised + for i in range(0,len(self.table)): #surely there must be a better way to find the player id in the table... + if self.table[i]['name']==self.actions[self.action_number][1]: + playerid=i + + if self.actions[self.action_number][2]=="folds": + self.table[playerid]["status"]="folded" + + if self.actions[self.action_number][3]: + self.table[playerid]["stack"] -= Decimal(self.actions[self.action_number][3]) #decreases stack if player bets + self.pot += Decimal(self.actions[self.action_number][3]) #increase pot + self.table[playerid]["chips"] += Decimal(self.actions[self.action_number][3]) #increase player's chips on table + + + cm = self.gc.get_colormap() #create colormap toi be able to play with colours + + color = cm.alloc_color("black") #defaults to black + self.gc.set_foreground(color) + + self.area.window.draw_arc(self.gc, 0, 125, 125, 300, 300, 0, 360*64) #table + + for i in self.table: + if self.table[i]["status"]=="folded": + color = cm.alloc_color("grey") #player has folded => greyed out + self.gc.set_foreground(color) + else: + color = cm.alloc_color("black") #player is live + self.gc.set_foreground(color) + self.pangolayout.set_text(self.table[i]["name"]+self.table[i]["holecards"]) #player names + holecards + self.area.window.draw_layout(self.gc, self.table[i]["x"],self.table[i]["y"], self.pangolayout) + self.pangolayout.set_text('$'+str(self.table[i]["stack"])) #player stacks + self.area.window.draw_layout(self.gc, self.table[i]["x"]+10,self.table[i]["y"]+20, self.pangolayout) + + color = cm.alloc_color("green") + self.gc.set_foreground(color) + + self.pangolayout.set_text(self.currency+str(self.pot)) #displays pot + self.area.window.draw_layout(self.gc,270,270, self.pangolayout) + + if self.actions[self.action_number][0]>1: #displays flop + self.pangolayout.set_text(self.MyHand.board['FLOP'][0]+" "+self.MyHand.board['FLOP'][1]+" "+self.MyHand.board['FLOP'][2]) + self.area.window.draw_layout(self.gc,210,240, self.pangolayout) + if self.actions[self.action_number][0]>2: #displays turn + self.pangolayout.set_text(self.MyHand.board['TURN'][0]) + self.area.window.draw_layout(self.gc,270,240, self.pangolayout) + if self.actions[self.action_number][0]>3: #displays river + self.pangolayout.set_text(self.MyHand.board['RIVER'][0]) + self.area.window.draw_layout(self.gc,290,240, self.pangolayout) + + color = cm.alloc_color("red") #highlights the action + self.gc.set_foreground(color) + + self.pangolayout.set_text(self.actions[self.action_number][2]) #displays action + self.area.window.draw_layout(self.gc, self.table[playerid]["x"]+10,self.table[playerid]["y"]+35, self.pangolayout) + if self.actions[self.action_number][3]: #displays amount + self.pangolayout.set_text(self.currency+self.actions[self.action_number][3]) + self.area.window.draw_layout(self.gc, self.table[playerid]["x"]+10,self.table[playerid]["y"]+55, self.pangolayout) + + color = cm.alloc_color("black") #we don't want to draw the filters and others in red + self.gc.set_foreground(color) + + def play_holdem(self): + actions=('BLINDSANTES','PREFLOP','FLOP','TURN','RIVER') + for action in actions: + for i in range(0,len(self.MyHand.actions[action])): + player=self.MyHand.actions[action][i][0] + act=self.MyHand.actions[action][i][1] + try: + amount=str(self.MyHand.actions[action][i][2]) + except: + amount='' #no amount + self.actions.append([actions.index(action),player,act,amount]) #create table with all actions + + + def draw_action(self): + if self.action_number==len(self.actions)-1: #no more actions, we exit the loop + return False + + if self.actions[self.action_number][0]!=self.action_level: #have we changed street ? + self.action_level=self.actions[self.action_number][0] #record the new street + if self.action_level>1: #we don't want to refresh if simply moving from antes/blinds to preflop action + alloc = self.area.get_allocation() + rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height) + self.area.window.invalidate_rect(rect, True) #make sure we refresh the whole screen + + self.action_number+=1 + if self.area.window: + playerid='999' #makes sure we have an error if player is not recognised + for i in range(0,len(self.table)): #surely there must be a better way to find the player id in the table... + if self.table[i]['name']==self.actions[self.action_number][1]: + playerid=i + rect = gtk.gdk.Rectangle(self.table[playerid]["x"],self.table[playerid]["y"],100,100) + self.area.window.invalidate_rect(rect, True) #refresh player area of the screen + rect = gtk.gdk.Rectangle(270,270,100,50) + self.area.window.invalidate_rect(rect, True) #refresh pot area + self.area.window.process_updates(True) + print "draw action: ",self.action_number,self.actions[self.action_number][1],self.actions[self.action_number][2],self.actions[self.action_number][3] + return True + + + def get_vbox(self): + """returns the vbox of this thread""" + return self.mainHBox + + def importhand(self, handnumber=1): + """Temporary function that grabs a Hand object from a specified file. Obviously this will + be replaced by a function to select a hand from the db in the not so distant future. + This code has been shamelessly stolen from Carl + """ + if True: + settings = {} + settings.update(self.conf.get_db_parameters()) + settings.update(self.conf.get_import_parameters()) + settings.update(self.conf.get_default_paths()) + + importer = fpdb_import.Importer(False, settings, self.conf, None) + importer.setDropIndexes("don't drop") + importer.setFailOnError(True) + importer.setThreads(-1) + importer.setCallHud(False) + importer.setFakeCacheHHC(True) + + print "DEBUG: self.filename: '%s' self.site: '%s'" %(self.filename, self.site) + importer.addBulkImportImportFileOrDir(self.filename, site=self.site) + (stored, dups, partial, errs, ttime) = importer.runImport() + + hhc = importer.getCachedHHC() + handlist = hhc.getProcessedHands() + + return handlist[0] + else: + # Fetch hand info + # We need at least sitename, gametype, handid + # for the Hand.__init__ + + ####### Shift this section in Database.py for all to use ###### + handid = 40 + q = self.sql.query['get_gameinfo_from_hid'] + q = q.replace('%s', self.sql.query['placeholder']) + + c = self.db.get_cursor() + + c.execute(q, (handid,)) + 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]} + #FIXME: smallbet and bigbet are res[8] and res[9] respectively + ###### End section ######## + print "DEBUG: gametype: %s" % gametype + if gametype['base'] == 'hold': + h = HoldemOmahaHand(config = self.conf, hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid) + h.select(self.db, handid) + elif gametype['base'] == 'stud': + print "DEBUG: Create stud hand here" + elif gametype['base'] == 'draw': + print "DEBUG: Create draw hand here" + + def temp(self): + pass + +def main(argv=None): + """main can also be called in the python interpreter, by supplying the command line as the argument.""" + if argv is None: + argv = sys.argv[1:] + + def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + + import Options + + (options, argv) = Options.fpdb_options() + + if options.usage == True: + #Print usage examples and exit + sys.exit(0) + + if options.sitename: + options.sitename = Options.site_alias(options.sitename) + if options.sitename == False: + usage() + + config = Configuration.Config(file = "HUD_config.test.xml") + db = Database.Database(config) + sql = SQL.Sql(db_server = 'sqlite') + + main_window = gtk.Window() + main_window.connect('destroy', destroy) + + replayer = GuiReplayer(config, sql, main_window, options=options, debug=True) + + main_window.add(replayer.get_vbox()) + main_window.set_default_size(800,800) + main_window.show() + gtk.main() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pyfpdb/GuiRingPlayerStats.py b/pyfpdb/GuiRingPlayerStats.py index 0d40fd59..d3ba1bee 100644 --- a/pyfpdb/GuiRingPlayerStats.py +++ b/pyfpdb/GuiRingPlayerStats.py @@ -75,10 +75,6 @@ onlinehelp = {'Game':_('Type of Game'), 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) @@ -91,11 +87,6 @@ class DemoTips(TreeViewTooltips): return (display) def 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 + 30, y - (h + 10) @@ -661,18 +652,18 @@ class GuiRingPlayerStats (GuiPlayerStats.GuiPlayerStats): query = query.replace("", bbtest) if holecards: # re-use level variables for hole card query - query = query.replace("", "hp.startcards") - query = query.replace("" + query = query.replace("", "hp.startcards") + query = query.replace("" , ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 " + " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 " + " end desc ") else: - query = query.replace("", "") + query = query.replace("", "") groupLevels = "show" not in str(limits) if groupLevels: - query = query.replace("", "p.name") # need to use p.name for sqlite posn stats to work + query = query.replace("", "p.name") # need to use p.name for sqlite posn stats to work else: - query = query.replace("", "h.gameTypeId") + query = query.replace("", "h.gametypeId") # process self.detailFilters (a list of tuples) flagtest = '' diff --git a/pyfpdb/GuiSessionViewer.py b/pyfpdb/GuiSessionViewer.py index 6f0fd269..8a85fd30 100644 --- a/pyfpdb/GuiSessionViewer.py +++ b/pyfpdb/GuiSessionViewer.py @@ -37,8 +37,6 @@ try: from matplotlib.finance import candlestick2 from numpy import diff, nonzero, sum, cumsum, max, min, append -# from matplotlib.dates import DateFormatter, WeekdayLocator, HourLocator, \ -# DayLocator, MONDAY, timezone except ImportError, inst: print _("""Failed to load numpy and/or matplotlib in Session Viewer""") @@ -157,15 +155,10 @@ class GuiSessionViewer (threading.Thread): # make sure Hand column is not displayed #[x for x in self.columns if x[0] == 'hand'][0][1] = False if DEBUG == False: - warning_string = """ -Session Viewer is proof of concept code only, and contains many bugs. - -Feel free to use the viewer, but there is no guarantee that the data is accurate. - -If you are interested in developing the code further please contact us via the usual channels. - -Thankyou -""" + warning_string = _("Session Viewer is proof of concept code only, and contains many bugs.\n") + warning_string += _("Feel free to use the viewer, but there is no guarantee that the data is accurate.\n") + warning_string += _("If you are interested in developing the code further please contact us via the usual channels.\n") + warning_string += _("Thankyou") self.warning_box(warning_string) def warning_box(self, str, diatitle=_("FPDB WARNING")): @@ -265,8 +258,9 @@ Thankyou #end def fillStatsFrame(self, vbox): def generateDatasets(self, playerids, sitenos, limits, seats): - THRESHOLD = 1800 # Minimum number of seconds between consecutive hands before being considered a new session - PADDING = 5 # Additional time in minutes to add to a session, session startup, shutdown etc (FiXME: user configurable) + print "DEBUG: Starting generateDatasets" + THRESHOLD = 1800 # Min # of secs between consecutive hands before being considered a new session + PADDING = 5 # Additional time in minutes to add to a session, session startup, shutdown etc # Get a list of all handids and their timestampts #FIXME: Query still need to filter on blind levels @@ -284,10 +278,15 @@ Thankyou self.db.cursor.execute(q) hands = self.db.cursor.fetchall() + hands.insert(0, (hands[0][0], 0, 0, 0, 0)) + # Take that list and create an array of the time between hands times = map(lambda x:long(x[0]), hands) handids = map(lambda x:int(x[1]), hands) winnings = map(lambda x:float(x[4]), hands) + #print "DEBUG: times : %s" % times + #print "DEBUG: handids : %s" % handids + #print "DEBUG: winnings: %s" % winnings #print "DEBUG: len(times) %s" %(len(times)) diffs = diff(times) # This array is the difference in starttime between consecutive hands diffs2 = append(diffs,THRESHOLD + 1) # Append an additional session to the end of the diffs, so the next line @@ -318,8 +317,9 @@ Thankyou cum_sum = cum_sum/100 sid = 1 # Take all results and format them into a list for feeding into gui model. + #print "DEBUG: range(len(index[0]): %s" % range(len(index[0])) for i in range(len(index[0])): - hds = index[0][i] - first_idx + 1 # Number of hands in session + hds = index[0][i] - first_idx # Number of hands in session if hds > 0: stime = strftime("%d/%m/%Y %H:%M", localtime(times[first_idx])) # Formatted start time etime = strftime("%d/%m/%Y %H:%M", localtime(times[index[0][i]])) # Formatted end time @@ -328,12 +328,14 @@ Thankyou minutesplayed = 1 minutesplayed = minutesplayed + PADDING hph = hds*60/minutesplayed # Hands per hour - won = sum(winnings[first_idx:index[0][i]])/100.0 - hwm = max(cum_sum[first_idx:index[0][i]]) - lwm = min(cum_sum[first_idx:index[0][i]]) + end_idx = first_idx+hds+1 + won = sum(winnings[first_idx:end_idx])/100.0 + #print "DEBUG: winnings[%s:%s]: %s" % (first_idx, end_idx, winnings[first_idx:end_idx]) + hwm = max(cum_sum[first_idx:end_idx]) + lwm = min(cum_sum[first_idx:end_idx]) open = (sum(winnings[:first_idx]))/100 - close = (sum(winnings[:index[0][i]]))/100 - #print "DEBUG: range: (%s, %s) - (min, max): (%s, %s) - (open,close): (%s, %s)" %(first_idx, index[0][i], lwm, hwm, open, close) + close = (sum(winnings[:end_idx]))/100 + #print "DEBUG: range: (%s, %s) - (min, max): (%s, %s) - (open,close): (%s, %s)" %(first_idx, end_idx, lwm, hwm, open, close) results.append([sid, hds, stime, etime, hph, won]) opens.append(open) @@ -374,17 +376,17 @@ Thankyou def generateGraph(self, opens, closes, highs, lows): self.clearGraphData() -# print "DEBUG:" -# print "highs = %s" % highs -# print "lows = %s" % lows -# print "opens = %s" % opens -# print "closes = %s" % closes -# print "len(highs): %s == len(lows): %s" %(len(highs), len(lows)) -# print "len(opens): %s == len(closes): %s" %(len(opens), len(closes)) -# -# for i in range(len(highs)): -# print "DEBUG: (%s, %s, %s, %s)" %(lows[i], opens[i], closes[i], highs[i]) -# print "DEBUG: diffs h/l: %s o/c: %s" %(lows[i] - highs[i], opens[i] - closes[i]) + #print "DEBUG:" + #print "\thighs = %s" % highs + #print "\tlows = %s" % lows + #print "\topens = %s" % opens + #print "\tcloses = %s" % closes + #print "\tlen(highs): %s == len(lows): %s" %(len(highs), len(lows)) + #print "\tlen(opens): %s == len(closes): %s" %(len(opens), len(closes)) + + #for i in range(len(highs)): + # print "DEBUG: (%s, %s, %s, %s)" %(lows[i], opens[i], closes[i], highs[i]) + # print "DEBUG: diffs h/l: %s o/c: %s" %(lows[i] - highs[i], opens[i] - closes[i]) self.ax = self.fig.add_subplot(111) diff --git a/pyfpdb/GuiStove.py b/pyfpdb/GuiStove.py new file mode 100644 index 00000000..b600c81e --- /dev/null +++ b/pyfpdb/GuiStove.py @@ -0,0 +1,222 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2008-2010 Steffen Schaumburg +#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() + +import pygtk +pygtk.require('2.0') +import gtk +import os +import sys + +import Charset + +DEBUG = False + +class GuiStove(): + + def __init__(self, config, parent, debug=True): + """Constructor for GraphViewer""" + self.conf = config + self.parent = parent + + self.mainHBox = gtk.HBox(False, 0) + + # hierarchy: self.mainHBox / self.notebook + + self.notebook = gtk.Notebook() + self.notebook.set_tab_pos(gtk.POS_TOP) + self.notebook.set_show_tabs(True) + self.notebook.set_show_border(True) + + self.createFlopTab() + self.createStudTab() + self.createDrawTab() + + + self.mainHBox.add(self.notebook) + + self.mainHBox.show_all() + + if DEBUG == False: + warning_string = _("Stove is a GUI mockup of a EV calculation page, and completely non functional.\n") + warning_string = _("Unless you are interested in developing this feature, please ignore this page.\n") + warning_string = _("If you are interested in developing the code further see GuiStove.py and Stove.py\n") + warning_string = _("Thank you\n") + self.warning_box(warning_string) + + + def warning_box(self, str, diatitle=_("FPDB WARNING")): + diaWarning = gtk.Dialog(title=diatitle, parent=self.parent, flags=gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) + + label = gtk.Label(str) + diaWarning.vbox.add(label) + label.show() + + response = diaWarning.run() + diaWarning.destroy() + return response + + + def get_active_text(combobox): + model = combobox.get_model() + active = combobox.get_active() + if active < 0: + return None + return model[active][0] + + def create_combo_box(self, strings): + combobox = gtk.combo_box_new_text() + for label in strings: + combobox.append_text(label) + combobox.set_active(0) + return combobox + + def createDrawTab(self): + tab_title = "Draw" + label = gtk.Label(tab_title) + + ddbox = gtk.VBox(False, 0) + self.notebook.append_page(ddbox, label) + + def createStudTab(self): + tab_title = "Stud" + label = gtk.Label(tab_title) + + ddbox = gtk.VBox(False, 0) + self.notebook.append_page(ddbox, label) + + def createFlopTab(self): + # hierarchy: hbox / ddbox / ddhbox / Label + flop_games_cb | label + players_cb + # / gamehbox / in_frame / table / + # / out_frame + + tab_title = "Flop" + label = gtk.Label(tab_title) + + ddbox = gtk.VBox(False, 0) + self.notebook.append_page(ddbox, label) + + ddhbox = gtk.HBox(False, 0) + gamehbox = gtk.HBox(False, 0) + + ddbox.add(ddhbox) + ddbox.add(gamehbox) + + # Combo boxes in the top row + + games = [ "Holdem", "Omaha", "Omaha 8", ] + players = [ "2", "3", "4", "5", "6", "7", "8", "9", "10" ] + flop_games_cb = self.create_combo_box(games) + players_cb = self.create_combo_box(players) + + label = gtk.Label("Gametype:") + ddhbox.add(label) + ddhbox.add(flop_games_cb) + label = gtk.Label("Players:") + ddhbox.add(label) + ddhbox.add(players_cb) + + # Frames for Stove input and output + + in_frame = gtk.Frame("Input:") + out_frame = gtk.Frame("Output:") + + gamehbox.add(in_frame) + gamehbox.add(out_frame) + + outstring = """ +No board given. Using Monte-Carlo simulation... +Enumerated 2053443 possible plays. +Your hand: (Ad Ac) +Against the range: { + AhAd, AhAs, AdAs, KhKd, KhKs, + KhKc, KdKs, KdKc, KsKc, QhQd, + QhQs, QhQc, QdQs, QdQc, QsQc, + JhJd, JhJs, JhJc, JdJs, JdJc, + JsJc + } + + Win Lose Tie + 69.91% 15.83% 14.26% + +""" + self.outputlabel = gtk.Label(outstring) + out_frame.add(self.outputlabel) + + # Input Frame + table = gtk.Table(4, 4, True) + label = gtk.Label("Board:") + board = gtk.Entry() + board.connect("changed", self.set_board_flop, board) + + btn1 = gtk.Button() + btn1.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + #btn.connect('clicked', self._some_function, arg) + table.attach(label, 0, 1, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(board, 1, 2, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(btn1, 2, 3, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + + + label = gtk.Label("Player1:") + board = gtk.Entry() + board.connect("changed", self.set_hero_cards_flop, board) + btn2 = gtk.Button() + btn2.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + #btn.connect('clicked', self._some_function, arg) + btn3 = gtk.Button() + btn3.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + #btn.connect('clicked', self._some_function, arg) + table.attach(label, 0, 1, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(board, 1, 2, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(btn2, 2, 3, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(btn3, 3, 4, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + + + label = gtk.Label("Player2:") + board = gtk.Entry() + board.connect("changed", self.set_villain_cards_flop, board) + btn4 = gtk.Button() + btn4.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + #btn.connect('clicked', self._some_function, arg) + btn5 = gtk.Button() + btn5.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + #btn.connect('clicked', self._some_function, arg) + table.attach(label, 0, 1, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(board, 1, 2, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(btn4, 2, 3, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + table.attach(btn5, 3, 4, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK) + + #table.attach(label, i, i+1, j, j+1,) + in_frame.add(table) + + def set_board_flop(self, caller, string): + print "DEBUG: called set_board_flop: '%s' '%s'" %(caller ,string) + + def set_hero_cards_flop(self, caller, string): + print "DEBUG: called set_hero_cards_flop" + + def set_villain_cards_flop(self, caller, string): + print "DEBUG: called set_villain_cards_flop" + + + + def get_vbox(self): + """returns the vbox of this thread""" + return self.mainHBox + #end def get_vbox diff --git a/pyfpdb/GuiTourneyPlayerStats.py b/pyfpdb/GuiTourneyPlayerStats.py index ef6c0f66..0b830547 100644 --- a/pyfpdb/GuiTourneyPlayerStats.py +++ b/pyfpdb/GuiTourneyPlayerStats.py @@ -382,7 +382,7 @@ class GuiTourneyPlayerStats (GuiPlayerStats.GuiPlayerStats): #query = query.replace("", bbtest) - #query = query.replace("", "") + #query = query.replace("", "") # process self.detailFilters (a list of tuples) flagtest = '' diff --git a/pyfpdb/HUD_config.test.xml b/pyfpdb/HUD_config.test.xml index 556bbede..cfad09c5 100644 --- a/pyfpdb/HUD_config.test.xml +++ b/pyfpdb/HUD_config.test.xml @@ -2,7 +2,7 @@ - +