Merge branch 'master' of git://git.assembla.com/free_poker_tools

This commit is contained in:
grindi 2010-07-14 22:18:42 +04:00
commit 959e0e11cf
23 changed files with 665 additions and 176 deletions

View File

@ -1,5 +1,5 @@
# Variable definitions # Variable definitions
VERSION = 0.12 VERSION = 0.20
DATE = $(shell date +%Y%m%d) DATE = $(shell date +%Y%m%d)
all: all:

View File

@ -1,5 +1,5 @@
README.txt README.txt
updated 26 March 2009, REB updated 22 February 2010, REB
fpdb - Free Poker Database fpdb - Free Poker Database
@ -29,7 +29,7 @@ fpdb supports:
Omaha (incl Hi/low) Omaha (incl Hi/low)
7 Card Stud (incl Hi/low) 7 Card Stud (incl Hi/low)
Razz Razz
Draw support is under development Triple Draw and Badugi
Mixed Games -- HUD under development Mixed Games -- HUD under development
Operating Systems: Operating Systems:
@ -38,23 +38,38 @@ fpdb supports:
Mac OS/X -- no support for HUD Mac OS/X -- no support for HUD
Databases: Databases:
SQLite configured by default
MySQL MySQL
PostgreSQL PostgreSQL
SQLite under development
Downloads: Downloads:
Releases: http://sourceforge.net/project/showfiles.php?group_id=226872 Releases: http://sourceforge.net/project/showfiles.php?group_id=226872
Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool
Developers: Developers:
At least 7 people have contributed code or patches. Others are welcome. At least 10 people have contributed code or patches. Others are welcome.
Source Code:
If you received fpdb as the Windows compressed exe, then you did not
receive souce code for fpdb or the included libraries. If you wish, you can
obtain the source code here:
fpdb: see Downloads, above.
python: http://python.org/
gtk: http://www.gtk.org/download.html
pygtk: http://www.pygtk.org/downloads.html
psycopg2: http://initd.org/pub/software/psycopg/
mysqldb: http://sourceforge.net/projects/mysql-python/files/
sqlalchemy: http://www.sqlalchemy.org/download.html
numpy: http://www.scipy.org/Download
matplotlib: http://sourceforge.net/projects/matplotlib/files/
License License
======= =======
Trademarks of third parties have been used under Fair Use or similar laws. Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso Copyright 2008 Steffen Jobbagy-Felso
Copyright 2009 Ray E. Barker Copyright 2009,2010 Ray E. Barker
Permission is granted to copy, distribute and/or modify this Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License, document under the terms of the GNU Free Documentation License,
Version 1.2 as published by the Free Software Foundation; with Version 1.2 as published by the Free Software Foundation; with

View File

@ -414,6 +414,7 @@ class Import:
self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhArchiveBase = node.getAttribute("hhArchiveBase")
self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True) self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True)
self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False)
self.saveStarsHH = string_to_bool(node.getAttribute("saveStarsHH"), default=False)
def __str__(self): def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
@ -469,7 +470,8 @@ class Config:
self.file = file self.file = file
self.dir_self = get_exec_path() self.dir_self = get_exec_path()
self.dir_config = os.path.dirname(self.file) # self.dir_config = os.path.dirname(self.file)
self.dir_config = get_default_config_path()
self.dir_log = os.path.join(self.dir_config, 'log') self.dir_log = os.path.join(self.dir_config, 'log')
self.dir_database = os.path.join(self.dir_config, 'database') self.dir_database = os.path.join(self.dir_config, 'database')
self.log_file = os.path.join(self.dir_log, 'fpdb-log.txt') self.log_file = os.path.join(self.dir_log, 'fpdb-log.txt')
@ -481,12 +483,19 @@ class Config:
print "\nReading configuration file %s\n" % file print "\nReading configuration file %s\n" % file
try: try:
doc = xml.dom.minidom.parse(file) doc = xml.dom.minidom.parse(file)
self.file_error = None
except: except:
log.error("Error parsing %s. See error log file." % (file)) log.error("Error parsing %s. See error log file." % (file))
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
print "press enter to continue" self.file_error = sys.exc_info()[1]
sys.stdin.readline() # we could add a parameter to decide whether to return or read a line and exit?
sys.exit() return
#print "press enter to continue"
#sys.stdin.readline()
#sys.exit()
#ExpatError: not well-formed (invalid token): line 511, column 4
#sys.exc_info = (<class 'xml.parsers.expat.ExpatError'>, ExpatError('not well-formed (invalid token): line 511,
# column 4',), <traceback object at 0x024503A0>)
self.doc = doc self.doc = doc
self.supported_sites = {} self.supported_sites = {}
@ -688,18 +697,8 @@ class Config:
try: db['db-server'] = self.supported_databases[name].db_server try: db['db-server'] = self.supported_databases[name].db_server
except: pass except: pass
if self.supported_databases[name].db_server== DATABASE_TYPE_MYSQL: db['db-backend'] = self.get_backend(self.supported_databases[name].db_server)
db['db-backend'] = 2
elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL:
db['db-backend'] = 3
elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE:
db['db-backend'] = 4
# sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252)
# feel free to remove or improve this if you understand the problems
# better than me (not hard!)
Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True
else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
return db return db
def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None,
@ -719,6 +718,23 @@ class Config:
if db_type is not None: self.supported_databases[db_name].dp_type = db_type if db_type is not None: self.supported_databases[db_name].dp_type = db_type
return return
def get_backend(self, name):
"""Returns the number of the currently used backend"""
if name == DATABASE_TYPE_MYSQL:
ret = 2
elif name == DATABASE_TYPE_POSTGRESQL:
ret = 3
elif name == DATABASE_TYPE_SQLITE:
ret = 4
# sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252)
# feel free to remove or improve this if you understand the problems
# better than me (not hard!)
Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True
else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
return ret
def getDefaultSite(self): def getDefaultSite(self):
"Returns first enabled site or None" "Returns first enabled site or None"
for site_name,site in self.supported_sites.iteritems(): for site_name,site in self.supported_sites.iteritems():
@ -808,8 +824,12 @@ class Config:
try: imp['saveActions'] = self.imp.saveActions try: imp['saveActions'] = self.imp.saveActions
except: imp['saveActions'] = True except: imp['saveActions'] = True
try: imp['saveStarsHH'] = self.imp.saveStarsHH
except: imp['saveStarsHH'] = False
try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache
except: imp['fastStoreHudCache'] = True except: imp['fastStoreHudCache'] = True
return imp return imp
def get_default_paths(self, site = None): def get_default_paths(self, site = None):

View File

@ -142,14 +142,18 @@ class Database:
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
] ]
, [ # indexes for sqlite (list index 4) , [ # indexes for sqlite (list index 4)
# {'tab':'Players', 'col':'name', 'drop':0} unique indexes not dropped
# {'tab':'Hands', 'col':'siteHandNo', 'drop':0} unique indexes not dropped
{'tab':'Hands', 'col':'gametypeId', 'drop':0} {'tab':'Hands', 'col':'gametypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':0} , {'tab':'HandsPlayers', 'col':'handId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':0} , {'tab':'HandsPlayers', 'col':'playerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
#, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} unique indexes not dropped , {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
] ]
] ]
@ -226,7 +230,7 @@ class Database:
# create index indexname on tablename (col); # create index indexname on tablename (col);
def __init__(self, c, sql = None): def __init__(self, c, sql = None, autoconnect = True):
#log = Configuration.get_logger("logging.conf", "db", log_dir=c.dir_log) #log = Configuration.get_logger("logging.conf", "db", log_dir=c.dir_log)
log.debug("Creating Database instance, sql = %s" % sql) log.debug("Creating Database instance, sql = %s" % sql)
self.config = c self.config = c
@ -247,6 +251,7 @@ class Database:
else: else:
self.sql = sql self.sql = sql
if autoconnect:
# connect to db # connect to db
self.do_connect(c) self.do_connect(c)
@ -313,7 +318,7 @@ class Database:
self.__connected = True self.__connected = True
def connect(self, backend=None, host=None, database=None, def connect(self, backend=None, host=None, database=None,
user=None, password=None): user=None, password=None, create=False):
"""Connects a database with the given parameters""" """Connects a database with the given parameters"""
if backend is None: if backend is None:
raise FpdbError('Database backend not defined') raise FpdbError('Database backend not defined')
@ -377,6 +382,7 @@ class Database:
print msg print msg
raise FpdbError(msg) raise FpdbError(msg)
elif backend == Database.SQLITE: elif backend == Database.SQLITE:
create = True
import sqlite3 import sqlite3
if use_pool: if use_pool:
sqlite3 = pool.manage(sqlite3, pool_size=1) sqlite3 = pool.manage(sqlite3, pool_size=1)
@ -384,13 +390,14 @@ class Database:
# log.warning("SQLite won't work well without 'sqlalchemy' installed.") # log.warning("SQLite won't work well without 'sqlalchemy' installed.")
if database != ":memory:": if database != ":memory:":
if not os.path.isdir(self.config.dir_database): if not os.path.isdir(self.config.dir_database) and create:
print "Creating directory: '%s'" % (self.config.dir_database) print "Creating directory: '%s'" % (self.config.dir_database)
log.info("Creating directory: '%s'" % (self.config.dir_database)) log.info("Creating directory: '%s'" % (self.config.dir_database))
os.mkdir(self.config.dir_database) os.mkdir(self.config.dir_database)
database = os.path.join(self.config.dir_database, database) database = os.path.join(self.config.dir_database, database)
self.db_path = database self.db_path = database
log.info("Connecting to SQLite: %(database)s" % {'database':self.db_path}) log.info("Connecting to SQLite: %(database)s" % {'database':self.db_path})
if os.path.exists(database) or create:
self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES ) self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES )
sqlite3.register_converter("bool", lambda x: bool(int(x))) sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0") sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
@ -405,11 +412,13 @@ class Database:
self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes
self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish
else: else:
raise FpdbError("unrecognised database backend:"+backend) raise FpdbError("sqlite database "+database+" does not exist")
else:
raise FpdbError("unrecognised database backend:"+str(backend))
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
self.cursor.execute(self.sql.query['set tx level']) self.cursor.execute(self.sql.query['set tx level'])
self.check_version(database=database, create=True) self.check_version(database=database, create=create)
def check_version(self, database, create): def check_version(self, database, create):
@ -721,6 +730,8 @@ class Database:
# now get the stats # now get the stats
c.execute(self.sql.query[query], subs) c.execute(self.sql.query[query], subs)
#for row in c.fetchall(): # needs "explain query plan" in sql statement
# print "query plan: ", row
colnames = [desc[0] for desc in c.description] colnames = [desc[0] for desc in c.description]
for row in c.fetchall(): for row in c.fetchall():
playerid = row[0] playerid = row[0]
@ -907,7 +918,6 @@ class Database:
print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \ print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \
% (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n')) % (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n'))
else: else:
print "Only MySQL and Postgres supported so far"
return -1 return -1
for idx in self.indexes[self.backend]: for idx in self.indexes[self.backend]:
@ -942,7 +952,6 @@ class Database:
print "warning: index %s_%s_idx not dropped %s, continuing ..." \ print "warning: index %s_%s_idx not dropped %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n')) % (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
else: else:
print "Error: Only MySQL and Postgres supported so far"
return -1 return -1
if self.backend == self.PGSQL: if self.backend == self.PGSQL:
@ -997,7 +1006,6 @@ class Database:
except: except:
print " create fk failed: " + str(sys.exc_info()) print " create fk failed: " + str(sys.exc_info())
else: else:
print "Only MySQL and Postgres supported so far"
return -1 return -1
for idx in self.indexes[self.backend]: for idx in self.indexes[self.backend]:
@ -1019,7 +1027,6 @@ class Database:
except: except:
print " create index failed: " + str(sys.exc_info()) print " create index failed: " + str(sys.exc_info())
else: else:
print "Only MySQL and Postgres supported so far"
return -1 return -1
if self.backend == self.PGSQL: if self.backend == self.PGSQL:
@ -2096,6 +2103,7 @@ class HandToWrite:
if __name__=="__main__": if __name__=="__main__":
c = Configuration.Config() c = Configuration.Config()
sql = SQL.Sql(db_server = 'sqlite')
db_connection = Database(c) # mysql fpdb holdem db_connection = Database(c) # mysql fpdb holdem
# db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem # db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem
@ -2113,13 +2121,26 @@ if __name__=="__main__":
if hero: if hero:
print "nutOmatic is id_player = %d" % hero print "nutOmatic is id_player = %d" % hero
# example of displaying query plan in sqlite:
if db_connection.backend == 4:
print
c = db_connection.get_cursor()
c.execute('explain query plan '+sql.query['get_table_name'], (h, ))
for row in c.fetchall():
print "query plan: ", row
print
t0 = time()
stat_dict = db_connection.get_stats_from_hand(h, "ring") stat_dict = db_connection.get_stats_from_hand(h, "ring")
t1 = time()
for p in stat_dict.keys(): for p in stat_dict.keys():
print p, " ", stat_dict[p] print p, " ", stat_dict[p]
print "cards =", db_connection.get_cards(u'1') print "cards =", db_connection.get_cards(u'1')
db_connection.close_connection db_connection.close_connection
print "get_stats took: %4.3f seconds" % (t1-t0)
print "press enter to continue" print "press enter to continue"
sys.stdin.readline() sys.stdin.readline()

View File

@ -162,7 +162,7 @@ class DerivedStats():
self.handsplayers[player]['wonAtSD'] = 1.0 self.handsplayers[player]['wonAtSD'] = 1.0
for player in hand.pot.committed: for player in hand.pot.committed:
self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])- (100*hand.pot.common[player]))
self.calcCBets(hand) self.calcCBets(hand)
@ -227,7 +227,7 @@ class DerivedStats():
#print "bb =", bb, "sb =", sb, "players =", players #print "bb =", bb, "sb =", sb, "players =", players
for i,player in enumerate(reversed(players)): for i,player in enumerate(reversed(players)):
self.handsplayers[player]['position'] = str(i) self.handsplayers[player]['position'] = i
def assembleHudCache(self, hand): def assembleHudCache(self, hand):
# No real work to be done - HandsPlayers data already contains the correct info # No real work to be done - HandsPlayers data already contains the correct info
@ -305,12 +305,13 @@ class DerivedStats():
"""Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|) """Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|)
Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB
(note: I don't think PT2 counts SB steals in HU hands, maybe we shouldn't?)
Fold to steal - folding blind after steal attemp wo any other callers or raisers Fold to steal - folding blind after steal attemp wo any other callers or raisers
""" """
steal_attempt = False steal_attempt = False
steal_positions = ('1', '0', 'S') steal_positions = (1, 0, 'S')
if hand.gametype['base'] == 'stud': if hand.gametype['base'] == 'stud':
steal_positions = ('2', '1', '0') steal_positions = (2, 1, 0)
for action in hand.actions[hand.actionStreets[1]]: for action in hand.actions[hand.actionStreets[1]]:
pname, act = action[0], action[1] pname, act = action[0], action[1]
posn = self.handsplayers[pname]['position'] posn = self.handsplayers[pname]['position']
@ -344,9 +345,9 @@ class DerivedStats():
for action in hand.actions[hand.actionStreets[1]]: for action in hand.actions[hand.actionStreets[1]]:
# FIXME: fill other(3|4)BStreet0 - i have no idea what does it mean # FIXME: fill other(3|4)BStreet0 - i have no idea what does it mean
pname, aggr = action[0], action[1] in ('raises', 'bets') pname, aggr = action[0], action[1] in ('raises', 'bets')
self.handsplayers[pname]['street0_3BChance'] = bet_level == 2 self.handsplayers[pname]['street0_3BChance'] = self.handsplayers[pname]['street0_3BChance'] or bet_level == 2
self.handsplayers[pname]['street0_4BChance'] = bet_level == 3 self.handsplayers[pname]['street0_4BChance'] = bet_level == 3
self.handsplayers[pname]['street0_3BDone'] = aggr and (self.handsplayers[pname]['street0_3BChance']) self.handsplayers[pname]['street0_3BDone'] = self.handsplayers[pname]['street0_3BDone'] or (aggr and self.handsplayers[pname]['street0_3BChance'])
self.handsplayers[pname]['street0_4BDone'] = aggr and (self.handsplayers[pname]['street0_4BChance']) self.handsplayers[pname]['street0_4BDone'] = aggr and (self.handsplayers[pname]['street0_4BChance'])
if aggr: if aggr:
bet_level += 1 bet_level += 1

View File

@ -211,6 +211,9 @@ class Filters(threading.Thread):
self.Button2.connect("clicked", self.callback['button2'], "clicked") self.Button2.connect("clicked", self.callback['button2'], "clicked")
self.Button2.set_sensitive(True) self.Button2.set_sensitive(True)
# make sure any locks on db are released:
self.db.rollback()
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainVBox

View File

@ -65,8 +65,8 @@ class Fulltilt(HandHistoryConverter):
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+)) (\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE) ''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE) re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE) re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose #static regex for tourney purpose
@ -128,6 +128,7 @@ class Fulltilt(HandHistoryConverter):
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re) logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostDead = re.compile(r"^%s posts a dead small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE) self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
@ -298,6 +299,8 @@ class Fulltilt(HandHistoryConverter):
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind except: # no small blind
hand.addBlind(None, None, None) hand.addBlind(None, None, None)
for a in self.re_PostDead.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText): for a in self.re_PostBoth.finditer(hand.handText):

298
pyfpdb/GuiDatabase.py Executable file
View File

@ -0,0 +1,298 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#Copyright 2008 Carl Gherardi
#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 <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import sys
import traceback
import Queue
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import pango
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("maintdbs")
import Exceptions
import Database
class GuiDatabase:
COL_DBMS = 0
COL_NAME = 1
COL_DESC = 2
COL_USER = 3
COL_PASS = 4
COL_HOST = 5
COL_ICON = 6
def __init__(self, config, mainwin, dia):
self.config = config
self.main_window = mainwin
self.dia = dia
try:
#self.dia.set_modal(True)
self.vbox = self.dia.vbox
#gtk.Widget.set_size_request(self.vbox, 700, 400);
# list of databases in self.config.supported_databases:
self.liststore = gtk.ListStore(str, str, str, str
,str, str, str, str) #object, gtk.gdk.Pixbuf)
# dbms, name, comment, user, pass, ip, status(, icon?)
# this is how to add a filter:
#
# # Creation of the filter, from the model
# filter = self.liststore.filter_new()
# filter.set_visible_column(1)
#
# # The TreeView gets the filter as model
# self.listview = gtk.TreeView(filter)
self.listview = gtk.TreeView(model=self.liststore)
self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE)
self.listcols = []
scrolledwindow = gtk.ScrolledWindow()
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.add(self.listview)
self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0)
refreshbutton = gtk.Button("Refresh")
refreshbutton.connect("clicked", self.refresh, None)
self.vbox.pack_start(refreshbutton, False, False, 3)
refreshbutton.show()
col = self.addTextColumn("Type", 0, False)
col = self.addTextColumn("Name", 1, False)
col = self.addTextColumn("Description", 2, True)
col = self.addTextColumn("Username", 3, True)
col = self.addTextColumn("Password", 4, True)
col = self.addTextColumn("Host", 5, True)
col = self.addTextObjColumn("", 6)
self.loadDbs()
self.dia.connect('response', self.dialog_response_cb)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'guidbmaint: '+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def dialog_response_cb(self, dialog, response_id):
# this is called whether close button is pressed or window is closed
dialog.destroy()
def get_dialog(self):
return self.dia
def addTextColumn(self, title, n, editable=False):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRender = gtk.CellRendererText()
cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
cRender.set_property('editable', editable)
cRender.connect('edited', self.edited_cb, (self.liststore,n))
col.pack_start(cRender, True)
col.add_attribute(cRender, 'text', n)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
col.set_clickable(True)
col.connect("clicked", self.sortCols, n)
return(col)
def edited_cb(self, cell, path, new_text, user_data):
liststore, col = user_data
valid = True
name = self.liststore[path][self.COL_NAME]
# Validate new value (only for dbms so far, but dbms now not updateable so no validation at all!)
#if col == self.COL_DBMS:
# if new_text not in Configuration.DATABASE_TYPES:
# valid = False
if valid:
self.liststore[path][col] = new_text
self.config.set_db_parameters( db_server = self.liststore[path][self.COL_DBMS]
, db_name = name
, db_ip = self.liststore[path][self.COL_HOST]
, db_user = self.liststore[path][self.COL_USER]
, db_pass = self.liststore[path][self.COL_PASS] )
return
def check_new_name(self, path, new_text):
name_ok = True
for i,db in enumerate(self.liststore):
if i != path and new_text == db[self.COL_NAME]:
name_ok = False
#TODO: popup an error message telling user names must be unique
return name_ok
def addTextObjColumn(self, title, n):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRenderT = gtk.CellRendererText()
cRenderT.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
col.pack_start(cRenderT, False)
col.add_attribute(cRenderT, 'text', n)
cRenderP = gtk.CellRendererPixbuf()
col.pack_start(cRenderP, False)
col.add_attribute(cRenderP, 'stock-id', n+1)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
#col.set_clickable(True)
#col.connect("clicked", self.sortCols, p)
return(col)
def loadDbs(self):
self.liststore.clear()
self.listcols = []
self.dbs = [] # list of tuples: (dbms, name, comment, user, passwd, host, status, icon)
try:
# want to fill: dbms, name, comment, user, passwd, host, status(, icon?)
for name in self.config.supported_databases: #db_ip/db_user/db_pass/db_server
dbms = self.config.supported_databases[name].db_server # mysql/postgresql/sqlite
dbms_num = self.config.get_backend(dbms) # 2 / 3 / 4
comment = ""
if dbms == 'sqlite':
user = ""
passwd = ""
else:
user = self.config.supported_databases[name].db_user
passwd = self.config.supported_databases[name].db_pass
host = self.config.supported_databases[name].db_ip
status = ""
icon = None
err_msg = ""
db = Database.Database(self.config, sql = None, autoconnect = False)
# try to connect to db, set status and err_msg if it fails
try:
# is creating empty db for sqlite ... mod db.py further?
# add noDbTables flag to db.py?
db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False)
if db.connected:
status = 'ok'
icon = gtk.STOCK_APPLY
if db.wrongDbVersion:
status = 'old'
icon = gtk.STOCK_INFO
except Exceptions.FpdbMySQLAccessDenied:
err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbMySQLNoDatabase:
err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \
+ "Please check that the MySQL service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbPostgresqlAccessDenied:
err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
except Exceptions.FpdbPostgresqlNoDatabase:
err_msg = "Postgres client reports: Unable to connect - " \
+ "Please check that the Postgres service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: '
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) )
status = "failed"
icon = gtk.STOCK_CANCEL
b = gtk.Button(name)
b.show()
iter = self.liststore.append( (dbms, name, comment, user, passwd, host, status, icon) )
self.listview.show()
scrolledwindow.show()
self.vbox.show()
self.dia.set_focus(self.listview)
self.vbox.show_all()
self.dia.show()
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'loaddbs error: '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' \
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def sortCols(self, col, n):
try:
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
col.set_sort_order(gtk.SORT_DESCENDING)
else:
col.set_sort_order(gtk.SORT_ASCENDING)
self.liststore.set_sort_column_id(n, col.get_sort_order())
#self.liststore.set_sort_func(n, self.sortnums, (n,grid))
for i in xrange(len(self.listcols)):
self.listcols[i].set_sort_indicator(False)
self.listcols[n].set_sort_indicator(True)
# use this listcols[col].set_sort_indicator(True)
# to turn indicator off for other cols
except:
err = traceback.extract_tb(sys.exc_info()[2])
print "***sortCols error: " + str(sys.exc_info()[1])
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
def refresh(self, widget, data):
self.loadDbs()
if __name__=="__main__":
config = Configuration.Config()
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
win.set_title("Test Log Viewer")
win.set_border_width(1)
win.set_default_size(600, 500)
win.set_resizable(True)
dia = gtk.Dialog("Log Viewer",
win,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
dia.set_default_size(500, 500)
log = GuiLogView(config, win, dia.vbox)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
pass
dia.destroy()

View File

@ -92,7 +92,7 @@ class GuiPlayerStats (threading.Thread):
, ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line , ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line
, ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code) , ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code)
, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code) , ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
, ["n", True, "Hds", 1.0, "%d", "str"] , ["n", True, "Hds", 1.0, "%1.0f", "str"]
, ["avgseats", False, "Seats", 1.0, "%3.1f", "str"] , ["avgseats", False, "Seats", 1.0, "%3.1f", "str"]
, ["vpip", True, "VPIP", 1.0, "%3.1f", "str"] , ["vpip", True, "VPIP", 1.0, "%3.1f", "str"]
, ["pfr", True, "PFR", 1.0, "%3.1f", "str"] , ["pfr", True, "PFR", 1.0, "%3.1f", "str"]
@ -482,6 +482,22 @@ class GuiPlayerStats (threading.Thread):
gametest = "and gt.category IS NULL" gametest = "and gt.category IS NULL"
query = query.replace("<game_test>", gametest) query = query.replace("<game_test>", gametest)
sitetest = ""
q = []
for m in self.filters.display.items():
if m[0] == 'Sites' and m[1]:
for n in sitenos:
q.append(n)
if len(q) > 0:
sitetest = str(tuple(q))
sitetest = sitetest.replace("L", "")
sitetest = sitetest.replace(",)",")")
sitetest = sitetest.replace("u'","'")
sitetest = "and gt.siteId in %s" % sitetest
else:
sitetest = "and gt.siteId IS NULL"
query = query.replace("<site_test>", sitetest)
if seats: if seats:
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
if 'show' in seats and seats['show']: if 'show' in seats and seats['show']:
@ -520,7 +536,7 @@ class GuiPlayerStats (threading.Thread):
blindtest = str(tuple(nolims)) blindtest = str(tuple(nolims))
blindtest = blindtest.replace("L", "") blindtest = blindtest.replace("L", "")
blindtest = blindtest.replace(",)",")") blindtest = blindtest.replace(",)",")")
bbtest = bbtest + blindtest + ' ) ) )' bbtest = bbtest + blindtest + ' ) )'
else: else:
bbtest = bbtest + '(-1) ) )' bbtest = bbtest + '(-1) ) )'
if type == 'ring': if type == 'ring':
@ -539,7 +555,7 @@ class GuiPlayerStats (threading.Thread):
query = query.replace("<orderbyhgameTypeId>", "") query = query.replace("<orderbyhgameTypeId>", "")
groupLevels = "show" not in str(limits) groupLevels = "show" not in str(limits)
if groupLevels: if groupLevels:
query = query.replace("<hgameTypeId>", "p.name") query = query.replace("<hgameTypeId>", "-1")
else: else:
query = query.replace("<hgameTypeId>", "h.gameTypeId") query = query.replace("<hgameTypeId>", "h.gameTypeId")

View File

@ -61,7 +61,7 @@ import Hud
# get config and set up logger # get config and set up logger
c = Configuration.Config(file=options.config) c = Configuration.Config(file=options.config, dbname=options.dbname)
log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt') log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt')
@ -237,7 +237,7 @@ class HUD_main(object):
try: try:
(table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \
self.db_connection.get_table_info(new_hand_id) self.db_connection.get_table_info(new_hand_id)
except Exception, err: except Exception:
log.error("db error: skipping %s" % new_hand_id) log.error("db error: skipping %s" % new_hand_id)
continue continue
t1 = time.time() t1 = time.time()

View File

@ -125,6 +125,7 @@ class Hand(object):
# currency symbol for this hand # currency symbol for this hand
self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
self.pot.setSym(self.sym) self.pot.setSym(self.sym)
self.is_duplicate = False # i.e. don't update hudcache if true
def __str__(self): def __str__(self):
vars = ( ("BB", self.bb), vars = ( ("BB", self.bb),
@ -236,6 +237,7 @@ db: a connected Database object"""
# TourneysPlayers # TourneysPlayers
else: else:
log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo']) log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo'])
self.is_duplicate = True # i.e. don't update hudcache
raise FpdbHandDuplicate(hh['siteHandNo']) raise FpdbHandDuplicate(hh['siteHandNo'])
def updateHudCache(self, db): def updateHudCache(self, db):
@ -321,7 +323,9 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio
self.stacks[player] -= Decimal(ante) self.stacks[player] -= Decimal(ante)
act = (player, 'posts', "ante", ante, self.stacks[player]==0) act = (player, 'posts', "ante", ante, self.stacks[player]==0)
self.actions['BLINDSANTES'].append(act) self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(ante)) # self.pot.addMoney(player, Decimal(ante))
self.pot.addCommonMoney(player, Decimal(ante))
#I think the antes should be common money, don't have enough hand history to check
def addBlind(self, player, blindtype, amount): def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind. # if player is None, it's a missing small blind.
@ -340,14 +344,16 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio
self.actions['BLINDSANTES'].append(act) self.actions['BLINDSANTES'].append(act)
if blindtype == 'both': if blindtype == 'both':
# work with the real ammount. limit games are listed as $1, $2, where
# the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2....
amount = self.bb amount = self.bb
self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb)) self.pot.addCommonMoney(player, Decimal(self.sb))
if blindtype == 'secondsb': if blindtype == 'secondsb':
amount = Decimal(0) amount = Decimal(0)
self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb)) self.pot.addCommonMoney(player, Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount)) self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount)) self.pot.addMoney(player, Decimal(amount))
@ -511,9 +517,6 @@ Card ranks will be uppercased
for entry in self.collected: for entry in self.collected:
self.totalcollected += Decimal(entry[1]) self.totalcollected += Decimal(entry[1])
def getGameTypeAsString(self): def getGameTypeAsString(self):
"""\ """\
Map the tuple self.gametype onto the pokerstars string describing it Map the tuple self.gametype onto the pokerstars string describing it
@ -674,6 +677,7 @@ class HoldemOmahaHand(Hand):
if self.maxseats is None: if self.maxseats is None:
self.maxseats = hhc.guessMaxSeats(self) self.maxseats = hhc.guessMaxSeats(self)
hhc.readOther(self) hhc.readOther(self)
#print "\nHand:\n"+str(self)
elif builtFrom == "DB": elif builtFrom == "DB":
if handid is not None: if handid is not None:
self.select(handid) # Will need a handId self.select(handid) # Will need a handId
@ -991,11 +995,12 @@ class DrawHand(Hand):
self.lastBet['DEAL'] = Decimal(amount) self.lastBet['DEAL'] = Decimal(amount)
elif blindtype == 'both': elif blindtype == 'both':
# extra small blind is 'dead' # extra small blind is 'dead'
self.lastBet['DEAL'] = Decimal(self.bb) amount = Decimal(amount)/3
amount += amount
self.lastBet['DEAL'] = Decimal(amount)
self.posted = self.posted + [[player,blindtype]] self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted) #print "DEBUG: self.posted: %s" %(self.posted)
def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False): def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False):
if player == self.hero: # we have hero's cards just update shown/mucked if player == self.hero: # we have hero's cards just update shown/mucked
if shown: self.shown.add(player) if shown: self.shown.add(player)
@ -1410,7 +1415,7 @@ class Pot(object):
self.contenders = set() self.contenders = set()
self.committed = {} self.committed = {}
self.streettotals = {} self.streettotals = {}
self.common = Decimal(0) self.common = {}
self.total = None self.total = None
self.returned = {} self.returned = {}
self.sym = u'$' # this is the default currency symbol self.sym = u'$' # this is the default currency symbol
@ -1420,13 +1425,14 @@ class Pot(object):
def addPlayer(self,player): def addPlayer(self,player):
self.committed[player] = Decimal(0) self.committed[player] = Decimal(0)
self.common[player] = Decimal(0)
def addFold(self, player): def addFold(self, player):
# addFold must be called when a player folds # addFold must be called when a player folds
self.contenders.discard(player) self.contenders.discard(player)
def addCommonMoney(self, amount): def addCommonMoney(self, player, amount):
self.common += amount self.common[player] += amount
def addMoney(self, player, amount): def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur # addMoney must be called for any actions that put money in the pot, in the order they occur
@ -1434,7 +1440,7 @@ class Pot(object):
self.committed[player] += amount self.committed[player] += amount
def markTotal(self, street): def markTotal(self, street):
self.streettotals[street] = sum(self.committed.values()) + self.common self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values())
def getTotalAtStreet(self, street): def getTotalAtStreet(self, street):
if street in self.streettotals: if street in self.streettotals:
@ -1442,11 +1448,11 @@ class Pot(object):
return 0 return 0
def end(self): def end(self):
self.total = sum(self.committed.values()) + self.common self.total = sum(self.committed.values()) + sum(self.common.values())
# Return any uncalled bet. # Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()]) committed = sorted([ (v,k) for (k,v) in self.committed.items()])
print "DEBUG: committed: %s" % committed #print "DEBUG: committed: %s" % committed
#ERROR below. lastbet is correct in most cases, but wrong when #ERROR below. lastbet is correct in most cases, but wrong when
# additional money is committed to the pot in cash games # additional money is committed to the pot in cash games
# due to an additional sb being posted. (Speculate that # due to an additional sb being posted. (Speculate that

View File

@ -69,6 +69,7 @@ out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
self.config = config self.config = config
self.import_parameters = self.config.get_import_parameters()
#log = Configuration.get_logger("logging.conf", "parser", log_dir=self.config.dir_log) #log = Configuration.get_logger("logging.conf", "parser", log_dir=self.config.dir_log)
log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) ) log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) )
@ -87,12 +88,8 @@ follow : whether to tail -f the input"""
if in_path == '-': if in_path == '-':
self.in_fh = sys.stdin self.in_fh = sys.stdin
self.out_fh = get_out_fh(out_path, self.import_parameters)
if out_path == '-':
self.out_fh = sys.stdout
else:
# TODO: out_path should be sanity checked.
self.out_fh = sys.stdout
self.follow = follow self.follow = follow
self.compiledPlayers = set() self.compiledPlayers = set()
self.maxseats = 10 self.maxseats = 10
@ -446,8 +443,8 @@ or None if we fail to get the info """
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at maxseats when not specified in HH.""" """Return a guess at maxseats when not specified in HH."""
# if some other code prior to this has already set it, return it # if some other code prior to this has already set it, return it
if maxseats > 1 and maxseats < 11: if self.maxseats > 1 and self.maxseats < 11:
return maxseats return self.maxseats
mo = self.maxOccSeat(hand) mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy if mo == 10: return 10 #that was easy
@ -515,3 +512,23 @@ def getSiteHhc(config, sitename):
hhcName = config.supported_sites[sitename].converter hhcName = config.supported_sites[sitename].converter
hhcModule = __import__(hhcName) hhcModule = __import__(hhcName)
return getattr(hhcModule, hhcName[:-6]) return getattr(hhcModule, hhcName[:-6])
def get_out_fh(out_path, parameters):
if out_path == '-':
return(sys.stdout)
elif parameters['saveStarsHH']:
out_dir = os.path.dirname(out_path)
if not os.path.isdir(out_dir) and out_dir != '':
try:
os.makedirs(out_dir)
except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D
log.error("Unable to create output directory %s for HHC!" % out_dir)
print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir
else:
log.info("Created directory '%s'" % out_dir)
try:
return(codecs.open(out_path, 'w', 'utf8'))
except:
log.error("out_path %s couldn't be opened" % (out_path))
else:
return(sys.stdout)

View File

@ -27,7 +27,7 @@ def fpdb_options():
action="store_true", action="store_true",
help="If passed error output will go to the console rather than .") help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName", parser.add_option("-d", "--databaseName",
dest="dbname", default="fpdb", dest="dbname",
help="Overrides the default database name") help="Overrides the default database name")
parser.add_option("-c", "--configFile", parser.add_option("-c", "--configFile",
dest="config", default=None, dest="config", default=None,

View File

@ -77,14 +77,15 @@ class PartyPoker(HandHistoryConverter):
re.VERBOSE) re.VERBOSE)
re_HandInfo = re.compile(""" re_HandInfo = re.compile("""
^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)\s+ ^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)?\s+
(?: \#|\(|)(?P<TABLE>\d+)\)?\s+ (?: \#|\(|)(?P<TABLE>\d+)\)?\s+
(?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)? (?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
(\(No\sDP\)\s)? (\(No\sDP\)\s)?
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
\s+Total\s+number\s+of\s+players\s+\:\s+(?P<PLYRS>\d+)/?(?P<MAX>\d+)?
""", """,
re.VERBOSE|re.MULTILINE) re.VERBOSE|re.MULTILINE|re.DOTALL)
re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE) re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
re_SplitHands = re.compile('\x00+') re_SplitHands = re.compile('\x00+')
@ -106,7 +107,6 @@ class PartyPoker(HandHistoryConverter):
def guessMaxSeats(self, hand): def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH.""" """Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand) mo = self.maxOccSeat(hand)
if mo == 10: return mo if mo == 10: return mo
if mo == 2: return 2 if mo == 2: return 2
if mo <= 6: return 6 if mo <= 6: return 6
@ -260,6 +260,7 @@ class PartyPoker(HandHistoryConverter):
for i,v in enumerate(self.collected): for i,v in enumerate(self.collected):
if v[0] in self.pot.returned: if v[0] in self.pot.returned:
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]] self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
self.collectees[v[0]] -= self.pot.returned[v[0]]
return origTotalPot() return origTotalPot()
return totalPot return totalPot
instancemethod = type(hand.totalPot) instancemethod = type(hand.totalPot)
@ -313,6 +314,9 @@ class PartyPoker(HandHistoryConverter):
if key == 'PLAY' and info['PLAY'] != 'Real': if key == 'PLAY' and info['PLAY'] != 'Real':
# if realy party doesn's save play money hh # if realy party doesn's save play money hh
hand.gametype['currency'] = 'play' hand.gametype['currency'] = 'play'
if key == 'MAX' and info[key] is not None:
hand.maxseats = int(info[key])
def readButton(self, hand): def readButton(self, hand):
m = self.re_Button.search(hand.handText) m = self.re_Button.search(hand.handText)
@ -465,8 +469,9 @@ class PartyPoker(HandHistoryConverter):
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
"Returns string to search in windows titles" "Returns string to search in windows titles"
if type=="tour": if type=="tour":
print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (table_name, table_number) TableName = table_name.split(" ")
return "%s.+Table\s#%s" % (table_name, table_number) print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number)
return "%s.+Table\s#%s" % (TableName[0], table_number)
else: else:
print 'party', 'getTableTitleRe', table_number print 'party', 'getTableTitleRe', table_number
return table_name return table_name

22
pyfpdb/PokerStarsToFpdb.py Executable file → Normal file
View File

@ -140,6 +140,14 @@ class PokerStars(HandHistoryConverter):
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to fpdb info strings # translations from captured groups to fpdb info strings
Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
'0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'), '2.00': ('0.50', '1.00'),
'4.00': ('1.00', '2.00'), '6.00': ('1.00', '3.00'), '10.00': ('2.00', '5.00'),
'20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'), '60.00': ('15.00', '30.00'),
'100.00': ('25.00', '50.00'),'200.00': ('50.00', '100.00'),'400.00': ('100.00', '200.00'),
'1000.00': ('250.00', '500.00')}
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category games = { # base, category
"Hold'em" : ('hold','holdem'), "Hold'em" : ('hold','holdem'),
@ -173,6 +181,10 @@ class PokerStars(HandHistoryConverter):
else: else:
info['type'] = 'tour' info['type'] = 'tour'
if info['limitType'] == 'fl' and info['bb'] is not None and info['type'] == 'ring' and info['base'] != 'stud':
info['sb'] = Lim_Blinds[mg['BB']][0]
info['bb'] = Lim_Blinds[mg['BB']][1]
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
@ -287,16 +299,14 @@ class PokerStars(HandHistoryConverter):
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand): def readBlinds(self, hand):
try: liveBlind = True
count = 0
for a in self.re_PostSB.finditer(hand.handText): for a in self.re_PostSB.finditer(hand.handText):
if count == 0: if liveBlind:
hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB')) hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB'))
count = 1 liveBlind = False
else: else:
# Post dead blinds as ante
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB')) hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText): for a in self.re_PostBoth.finditer(hand.handText):

View File

@ -1346,6 +1346,7 @@ class Sql:
# same as above except stats are aggregated for all blind/limit levels # same as above except stats are aggregated for all blind/limit levels
self.query['get_stats_from_hand_aggregated'] = """ self.query['get_stats_from_hand_aggregated'] = """
/* explain query plan */
SELECT hc.playerId AS player_id, SELECT hc.playerId AS player_id,
max(case when hc.gametypeId = h.gametypeId max(case when hc.gametypeId = h.gametypeId
then hp.seatNo then hp.seatNo
@ -1915,6 +1916,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId) inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test> <game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
@ -1999,6 +2001,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId) inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test> <game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>
@ -2076,8 +2079,7 @@ class Sql:
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr ,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr ,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats ,avg(h.seats+0.0) AS avgseats
/*,variance(hp.totalProfit/100.0) AS variance*/ ,variance(hp.totalProfit/100.0) AS variance
,0.0 AS variance
from HandsPlayers hp from HandsPlayers hp
inner join Hands h on (h.id = hp.handId) inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId) inner join Gametypes gt on (gt.Id = h.gameTypeId)
@ -2085,6 +2087,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId) inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test> where hp.playerId in <player_test>
<game_test> <game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/ /*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test> and h.seats <seats_test>
<flagtest> <flagtest>

View File

@ -32,12 +32,16 @@ import gtk
import gobject import gobject
# fpdb/free poker tools modules # fpdb/free poker tools modules
import Configuration
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os # get the correct module for the current os
if os.name == 'posix': if os.name == 'posix':
import XTables as Tables import XTables as Tables
elif os.name == 'nt': elif os.name == 'nt':
import WinTables as Tables import WinTables as Tables
config = Configuration.Config()
# Main function used for testing # Main function used for testing
if __name__=="__main__": if __name__=="__main__":
# c = Configuration.Config() # c = Configuration.Config()
@ -82,11 +86,16 @@ if __name__=="__main__":
(tour_no, tab_no) = table_name.split(",", 1) (tour_no, tab_no) = table_name.split(",", 1)
tour_no = tour_no.rstrip() tour_no = tour_no.rstrip()
tab_no = tab_no.rstrip() tab_no = tab_no.rstrip()
table = Tables.Table(None, tournament = tour_no, table_number = tab_no) type = "tour"
table_kwargs = dict(tournament = tour_no, table_number = tab_no)
else: # not a tournament else: # not a tournament
print "cash game" print "cash game"
table_name = table_name.rstrip() table_name = table_name.rstrip()
table = Tables.Table(None, table_name = table_name) 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) table.gdk_handle = gtk.gdk.window_foreign_new(table.number)
print "table =", table print "table =", table

View File

@ -68,7 +68,7 @@ class Table(Table_Window):
window_number = None window_number = None
for listing in os.popen('xwininfo -root -tree').readlines(): for listing in os.popen('xwininfo -root -tree').readlines():
if re.search(search_string, listing): if re.search(search_string, listing):
print listing # print listing
mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing)
self.number = int( mo.group(1), 0) self.number = int( mo.group(1), 0)
self.width = int( mo.group(4) ) self.width = int( mo.group(4) )
@ -89,7 +89,6 @@ class Table(Table_Window):
# break # break
if window_number is None: if window_number is None:
print "Window %s not found. Skipping." % search_string
return None return None
# my_geo = self.window.get_geometry() # my_geo = self.window.get_geometry()

View File

@ -97,6 +97,7 @@ except:
import GuiPrefs import GuiPrefs
import GuiLogView import GuiLogView
import GuiDatabase
import GuiBulkImport import GuiBulkImport
import GuiPlayerStats import GuiPlayerStats
import GuiPositionalStats import GuiPositionalStats
@ -288,11 +289,32 @@ class fpdb:
dia.destroy() dia.destroy()
def dia_create_del_database(self, widget, data=None): def dia_maintain_dbs(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete Database") self.warning_box("Unimplemented: Maintain Databases")
self.obtain_global_lock() return
if len(self.tab_names) == 1:
if self.obtain_global_lock(): # returns true if successful
# only main tab has been opened, open dialog
dia = gtk.Dialog("Maintain Databases",
self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
dia.set_default_size(700, 320)
prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
# save updated config
self.config.save()
self.release_global_lock() self.release_global_lock()
dia.destroy()
else:
self.warning_box("Cannot open Database Maintenance window because "
+ "other windows have been opened. Re-start fpdb to use this option.")
def dia_create_del_user(self, widget, data=None): def dia_create_del_user(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete user") self.warning_box("Unimplemented: Create/Delete user")
self.obtain_global_lock() self.obtain_global_lock()
@ -620,7 +642,7 @@ class fpdb:
<menuitem action="tableviewer"/> <menuitem action="tableviewer"/>
</menu> </menu>
<menu action="database"> <menu action="database">
<menuitem action="createdb"/> <menuitem action="maintaindbs"/>
<menuitem action="createuser"/> <menuitem action="createuser"/>
<menuitem action="createtabs"/> <menuitem action="createtabs"/>
<menuitem action="rebuildhudcache"/> <menuitem action="rebuildhudcache"/>
@ -663,7 +685,7 @@ class fpdb:
('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented), ('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented),
('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer), ('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer),
('database', None, '_Database'), ('database', None, '_Database'),
('createdb', None, 'Create or Delete _Database (todo)', None, 'Create or Delete Database', self.dia_create_del_database), ('maintaindbs', None, '_Maintain Databases (todo)', None, 'Maintain Databases', self.dia_maintain_dbs),
('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user), ('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user),
('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables), ('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables),
('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache), ('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
@ -685,9 +707,15 @@ class fpdb:
window.add_accel_group(accel_group) window.add_accel_group(accel_group)
return menubar return menubar
def load_profile(self): def load_profile(self, create_db = False):
"""Loads profile from the provided path name.""" """Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname) self.config = Configuration.Config(file=options.config, dbname=options.dbname)
if self.config.file_error:
self.warning_box( "There is an error in your config file\n" + self.config.file
+ "\n\nError is: " + str(self.config.file_error)
, diatitle="CONFIG FILE ERROR" )
exit()
log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log) log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log)
print "Logfile is " + os.path.join(self.config.dir_log, self.config.log_file) + "\n" print "Logfile is " + os.path.join(self.config.dir_log, self.config.log_file) + "\n"
if self.config.example_copy: if self.config.example_copy:
@ -905,7 +933,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None) self.tab_main_help(None, None)
self.window.show() self.window.show()
self.load_profile() self.load_profile(create_db = True)
if not options.errorsToConsole: if not options.errorsToConsole:
fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt') fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt')
@ -997,6 +1025,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
return response return response
def validate_config(self): def validate_config(self):
if self.config.get_import_parameters().get('saveStarsHH'):
hhbase = self.config.get_import_parameters().get("hhArchiveBase") hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase) hhbase = os.path.expanduser(hhbase)
#hhdir = os.path.join(hhbase,site) #hhdir = os.path.join(hhbase,site)

View File

@ -456,7 +456,7 @@ class Importer:
# FIXME: Need to test for bulk import that isn't rebuilding the cache # FIXME: Need to test for bulk import that isn't rebuilding the cache
if self.callHud: if self.callHud:
for hand in handlist: for hand in handlist:
if hand is not None: if hand is not None and not hand.is_duplicate:
hand.updateHudCache(self.database) hand.updateHudCache(self.database)
self.database.commit() self.database.commit()

View File

@ -73,6 +73,7 @@ from distutils.core import setup
import py2exe import py2exe
import glob import glob
import matplotlib import matplotlib
import shutil
from datetime import date from datetime import date
@ -111,7 +112,7 @@ def test_and_remove(top):
# remove build and dist dirs if they exist # remove build and dist dirs if they exist
test_and_remove('dist') test_and_remove('dist')
test_and_remove('build') test_and_remove('build')
test_and_remove('gfx') #test_and_remove('gfx')
today = date.today().strftime('%Y%m%d') today = date.today().strftime('%Y%m%d')
@ -151,7 +152,7 @@ setup(
}, },
# files in 2nd value in tuple are moved to dir named in 1st value # files in 2nd value in tuple are moved to dir named in 1st value
data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf']) data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf', '../docs/readme.txt'])
,(dist_dir, [r'..\run_fpdb.bat']) ,(dist_dir, [r'..\run_fpdb.bat'])
,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') ) ,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') )
# line below has problem with fonts subdir ('not a regular file') # line below has problem with fonts subdir ('not a regular file')
@ -174,3 +175,36 @@ dest = dest.replace('\\', '\\\\')
os.rename( 'pyfpdb', dest ) os.rename( 'pyfpdb', dest )
print "Enter directory name for GTK 2.14 (e.g. c:\code\gtk_2.14.7-20090119)\n: ", # the comma means no newline
gtk_dir = sys.stdin.readline().rstrip()
print "\ncopying files and dirs from ", gtk_dir, "to", dest.replace('\\\\', '\\'), "..."
src = os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src = os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src_dir = os.path.join(gtk_dir, 'etc')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'etc')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'lib')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'lib')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'share')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'share')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )

0
run_fpdb.py Executable file → Normal file
View File