441 lines
18 KiB
Python
441 lines
18 KiB
Python
#!/usr/bin/python
|
|
|
|
#Copyright 2008 Steffen Jobbagy-Felso
|
|
#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 threading
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gtk
|
|
import os
|
|
from time import time, strftime
|
|
|
|
import Card
|
|
import fpdb_import
|
|
import fpdb_db
|
|
import Filters
|
|
import FpdbSQLQueries
|
|
|
|
class GuiPlayerStats (threading.Thread):
|
|
def __init__(self, config, querylist, mainwin, debug=True):
|
|
self.debug=debug
|
|
self.conf=config
|
|
self.main_window=mainwin
|
|
self.MYSQL_INNODB = 2
|
|
self.PGSQL = 3
|
|
self.SQLITE = 4
|
|
|
|
# create new db connection to avoid conflicts with other threads
|
|
self.db = fpdb_db.fpdb_db()
|
|
self.db.do_connect(self.conf)
|
|
self.cursor=self.db.cursor
|
|
self.sql = querylist
|
|
|
|
settings = {}
|
|
settings.update(config.get_db_parameters())
|
|
settings.update(config.get_tv_parameters())
|
|
settings.update(config.get_import_parameters())
|
|
settings.update(config.get_default_paths())
|
|
|
|
# text used on screen stored here so that it can be configured
|
|
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
|
|
}
|
|
|
|
filters_display = { "Heroes" : True,
|
|
"Sites" : True,
|
|
"Games" : False,
|
|
"Limits" : True,
|
|
"LimitSep" : True,
|
|
"Seats" : True,
|
|
"SeatSep" : True,
|
|
"Dates" : False,
|
|
"Groups" : True,
|
|
"Button1" : True,
|
|
"Button2" : True
|
|
}
|
|
|
|
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
|
|
self.filters.registerButton1Name("_Filters")
|
|
self.filters.registerButton1Callback(self.showDetailFilter)
|
|
self.filters.registerButton2Name("_Refresh")
|
|
self.filters.registerButton2Callback(self.refreshStats)
|
|
|
|
# ToDo: store in config
|
|
# ToDo: create popup to adjust column config
|
|
# columns to display, keys match column name returned by sql, values in tuple are:
|
|
# is column displayed, column heading, xalignment, formatting
|
|
self.columns = [ ("game", True, "Game", 0.0, "%s")
|
|
, ("hand", False, "Hand", 0.0, "%s") # true not allowed for this line
|
|
, ("n", True, "Hds", 1.0, "%d")
|
|
, ("avgseats", True, "Seats", 1.0, "%3.1f")
|
|
, ("vpip", True, "VPIP", 1.0, "%3.1f")
|
|
, ("pfr", True, "PFR", 1.0, "%3.1f")
|
|
, ("pf3", True, "PF3", 1.0, "%3.1f")
|
|
, ("steals", True, "Steals", 1.0, "%3.1f")
|
|
, ("saw_f", True, "Saw_F", 1.0, "%3.1f")
|
|
, ("sawsd", True, "SawSD", 1.0, "%3.1f")
|
|
, ("wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f")
|
|
, ("wmsd", True, "W$SD", 1.0, "%3.1f")
|
|
, ("flafq", True, "FlAFq", 1.0, "%3.1f")
|
|
, ("tuafq", True, "TuAFq", 1.0, "%3.1f")
|
|
, ("rvafq", True, "RvAFq", 1.0, "%3.1f")
|
|
, ("pofafq", False, "PoFAFq", 1.0, "%3.1f")
|
|
, ("net", True, "Net($)", 1.0, "%6.2f")
|
|
, ("bbper100", True, "BB/100", 1.0, "%4.2f")
|
|
, ("rake", True, "Rake($)", 1.0, "%6.2f")
|
|
, ("variance", True, "Variance", 1.0, "%5.2f")
|
|
]
|
|
|
|
# Detail filters: This holds the data used in the popup window, extra values are
|
|
# added at the end of these lists during processing
|
|
# sql test, screen description, min, max
|
|
self.handtests = [ # already in filter class : ['h.seats', 'Number of Players', 2, 10]
|
|
['h.maxSeats', 'Size of Table', 2, 10]
|
|
,['h.playersVpi', 'Players who VPI', 0, 10]
|
|
,['h.playersAtStreet1', 'Players at Flop', 0, 10]
|
|
,['h.playersAtStreet2', 'Players at Turn', 0, 10]
|
|
,['h.playersAtStreet3', 'Players at River', 0, 10]
|
|
,['h.playersAtStreet4', 'Players at Street7', 0, 10]
|
|
,['h.playersAtShowdown', 'Players at Showdown', 0, 10]
|
|
,['h.street0Raises', 'Bets to See Flop', 0, 5]
|
|
,['h.street1Raises', 'Bets to See Turn', 0, 5]
|
|
,['h.street2Raises', 'Bets to See River', 0, 5]
|
|
,['h.street3Raises', 'Bets to See Street7', 0, 5]
|
|
,['h.street4Raises', 'Bets to See Showdown', 0, 5]
|
|
]
|
|
|
|
self.stats_frame = None
|
|
self.stats_vbox = None
|
|
self.detailFilters = [] # the data used to enhance the sql select
|
|
|
|
self.main_hbox = gtk.HBox(False, 0)
|
|
self.main_hbox.show()
|
|
|
|
self.stats_frame = gtk.Frame()
|
|
self.stats_frame.show()
|
|
|
|
self.stats_vbox = gtk.VBox(False, 0)
|
|
self.stats_vbox.show()
|
|
self.stats_frame.add(self.stats_vbox)
|
|
self.fillStatsFrame(self.stats_vbox)
|
|
|
|
self.main_hbox.pack_start(self.filters.get_vbox())
|
|
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
|
|
|
|
# make sure Hand column is not displayed
|
|
[x for x in self.columns if x[0] == 'hand'][0][1] == False
|
|
|
|
def get_vbox(self):
|
|
"""returns the vbox of this thread"""
|
|
return self.main_hbox
|
|
|
|
def refreshStats(self, widget, data):
|
|
try: self.stats_vbox.destroy()
|
|
except AttributeError: pass
|
|
self.stats_vbox = gtk.VBox(False, 0)
|
|
self.stats_vbox.show()
|
|
self.stats_frame.add(self.stats_vbox)
|
|
self.fillStatsFrame(self.stats_vbox)
|
|
|
|
def fillStatsFrame(self, vbox):
|
|
sites = self.filters.getSites()
|
|
heroes = self.filters.getHeroes()
|
|
siteids = self.filters.getSiteIds()
|
|
limits = self.filters.getLimits()
|
|
seats = self.filters.getSeats()
|
|
sitenos = []
|
|
playerids = []
|
|
|
|
# Which sites are selected?
|
|
for site in sites:
|
|
if sites[site] == True:
|
|
sitenos.append(siteids[site])
|
|
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
|
|
result = self.db.cursor.fetchall()
|
|
if len(result) == 1:
|
|
playerids.append(result[0][0])
|
|
|
|
if not sitenos:
|
|
#Should probably pop up here.
|
|
print "No sites selected - defaulting to PokerStars"
|
|
sitenos = [2]
|
|
if not playerids:
|
|
print "No player ids found"
|
|
return
|
|
if not limits:
|
|
print "No limits found"
|
|
return
|
|
|
|
self.createStatsTable(vbox, playerids, sitenos, limits, seats)
|
|
|
|
def createStatsTable(self, vbox, playerids, sitenos, limits, seats):
|
|
starttime = time()
|
|
|
|
# Display summary table at top of page
|
|
# 3rd parameter passes extra flags, currently includes:
|
|
# holecards - whether to display card breakdown (True/False)
|
|
flags = [False]
|
|
self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
|
|
|
|
# Separator
|
|
sep = gtk.HSeparator()
|
|
vbox.pack_start(sep, expand=False, padding=3)
|
|
sep.show_now()
|
|
vbox.show_now()
|
|
heading = gtk.Label(self.filterText['handhead'])
|
|
heading.show()
|
|
vbox.pack_start(heading, expand=False, padding=3)
|
|
|
|
# Scrolled window for detailed table (display by hand)
|
|
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
|
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
|
swin.show()
|
|
vbox.pack_start(swin, expand=True, padding=3)
|
|
|
|
vbox1 = gtk.VBox(False, 0)
|
|
vbox1.show()
|
|
swin.add_with_viewport(vbox1)
|
|
|
|
# Detailed table
|
|
flags = [True]
|
|
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
|
|
|
|
self.db.db.commit()
|
|
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
|
|
#end def fillStatsFrame(self, vbox):
|
|
|
|
def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats):
|
|
row = 0
|
|
sqlrow = 0
|
|
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
|
|
if not flags: holecards = False
|
|
else: holecards = flags[0]
|
|
|
|
|
|
self.stats_table = gtk.Table(1, 1, False)
|
|
self.stats_table.set_col_spacings(4)
|
|
self.stats_table.show()
|
|
|
|
tmp = self.sql.query[query]
|
|
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats)
|
|
self.cursor.execute(tmp)
|
|
result = self.cursor.fetchall()
|
|
colnames = [desc[0].lower() for desc in self.cursor.description]
|
|
|
|
# pre-fetch some constant values:
|
|
cols_to_show = [x for x in self.columns if x[colshow]]
|
|
hgametypeid_idx = colnames.index('hgametypeid')
|
|
|
|
liststore = gtk.ListStore(*([str] * len(cols_to_show)))
|
|
view = gtk.TreeView(model=liststore)
|
|
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
|
|
vbox.pack_start(view, expand=False, padding=3)
|
|
textcell = gtk.CellRendererText()
|
|
numcell = gtk.CellRendererText()
|
|
numcell.set_property('xalign', 1.0)
|
|
listcols = []
|
|
|
|
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
|
|
for col, column in enumerate(cols_to_show):
|
|
if column[colalias] == 'game' and holecards:
|
|
s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
|
|
else:
|
|
s = column[colheading]
|
|
listcols.append(gtk.TreeViewColumn(s))
|
|
view.append_column(listcols[col])
|
|
if column[colformat] == '%s':
|
|
if col == 1 and holecards:
|
|
listcols[col].pack_start(textcell, expand=True)
|
|
else:
|
|
listcols[col].pack_start(textcell, expand=True)
|
|
listcols[col].add_attribute(textcell, 'text', col)
|
|
listcols[col].set_expand(True)
|
|
else:
|
|
listcols[col].pack_start(numcell, expand=True)
|
|
listcols[col].add_attribute(numcell, 'text', col)
|
|
listcols[col].set_alignment(1.0)
|
|
listcols[col].set_expand(True)
|
|
|
|
rows = len(result) # +1 for title row
|
|
|
|
while sqlrow < rows:
|
|
treerow = []
|
|
if(row%2 == 0):
|
|
bgcolor = "white"
|
|
else:
|
|
bgcolor = "lightgrey"
|
|
for col,column in enumerate(cols_to_show):
|
|
if column[colalias] in colnames:
|
|
value = result[sqlrow][colnames.index(column[colalias])]
|
|
else:
|
|
if column[colalias] == 'game':
|
|
if holecards:
|
|
value = Card.twoStartCardString( result[sqlrow][hgametypeid_idx] )
|
|
else:
|
|
minbb = result[sqlrow][colnames.index('minbigblind')]
|
|
maxbb = result[sqlrow][colnames.index('maxbigblind')]
|
|
value = result[sqlrow][colnames.index('limittype')] + ' ' \
|
|
+ result[sqlrow][colnames.index('category')].title() + ' ' \
|
|
+ result[sqlrow][colnames.index('name')] + ' $'
|
|
if 100 * int(minbb/100.0) != minbb:
|
|
value += '%.2f' % (minbb/100.0)
|
|
else:
|
|
value += '%.0f' % (minbb/100.0)
|
|
if minbb != maxbb:
|
|
if 100 * int(maxbb/100.0) != maxbb:
|
|
value += ' - $' + '%.2f' % (maxbb/100.0)
|
|
else:
|
|
value += ' - $' + '%.0f' % (maxbb/100.0)
|
|
else:
|
|
continue
|
|
if value and value != -999:
|
|
treerow.append(column[colformat] % value)
|
|
else:
|
|
treerow.append(' ')
|
|
iter = liststore.append(treerow)
|
|
sqlrow += 1
|
|
row += 1
|
|
vbox.show_all()
|
|
|
|
#end def addTable(self, query, vars, playerids, sitenos, limits, seats):
|
|
|
|
def refineQuery(self, query, flags, playerids, sitenos, limits, seats):
|
|
if not flags: holecards = False
|
|
else: holecards = flags[0]
|
|
|
|
if playerids:
|
|
nametest = str(tuple(playerids))
|
|
nametest = nametest.replace("L", "")
|
|
nametest = nametest.replace(",)",")")
|
|
query = query.replace("<player_test>", nametest)
|
|
else:
|
|
query = query.replace("<player_test>", "1 = 2")
|
|
|
|
if seats:
|
|
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
|
|
if 'show' in seats and seats['show']:
|
|
query = query.replace('<groupbyseats>', ',h.seats')
|
|
query = query.replace('<orderbyseats>', ',h.seats')
|
|
else:
|
|
query = query.replace('<groupbyseats>', '')
|
|
query = query.replace('<orderbyseats>', '')
|
|
else:
|
|
query = query.replace('<seats_test>', 'between 0 and 100')
|
|
query = query.replace('<groupbyseats>', '')
|
|
query = query.replace('<orderbyseats>', '')
|
|
|
|
if [x for x in limits if str(x).isdigit()]:
|
|
blindtest = str(tuple([x for x in limits if str(x).isdigit()]))
|
|
blindtest = blindtest.replace("L", "")
|
|
blindtest = blindtest.replace(",)",")")
|
|
query = query.replace("<gtbigBlind_test>", " and gt.bigBlind in " + blindtest + " ")
|
|
else:
|
|
query = query.replace("<gtbigBlind_test>", "")
|
|
|
|
if holecards: # pinch level variables for hole card query
|
|
query = query.replace("<hgameTypeId>", "hp.startcards")
|
|
query = query.replace("<orderbyhgameTypeId>", ",hgameTypeId desc")
|
|
else:
|
|
query = query.replace("<orderbyhgameTypeId>", "")
|
|
groupLevels = "show" not in str(limits)
|
|
if groupLevels:
|
|
query = query.replace("<hgameTypeId>", "-1")
|
|
else:
|
|
query = query.replace("<hgameTypeId>", "h.gameTypeId")
|
|
|
|
# process self.detailFilters (a list of tuples)
|
|
flagtest = ''
|
|
#self.detailFilters = [('h.seats', 5, 6)] # for debug
|
|
if self.detailFilters:
|
|
for f in self.detailFilters:
|
|
if len(f) == 3:
|
|
# X between Y and Z
|
|
flagtest += ' and %s between %s and %s ' % (f[0], str(f[1]), str(f[2]))
|
|
query = query.replace("<flagtest>", flagtest)
|
|
|
|
# allow for differences in sql cast() function:
|
|
if self.db.backend == self.MYSQL_INNODB:
|
|
query = query.replace("<signed>", 'signed ')
|
|
else:
|
|
query = query.replace("<signed>", '')
|
|
|
|
#print "query =\n", query
|
|
return(query)
|
|
#end def refineQuery(self, query, playerids, sitenos, limits):
|
|
|
|
def showDetailFilter(self, widget, data):
|
|
detailDialog = gtk.Dialog(title="Detailed Filters", parent=self.main_window
|
|
,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
|
|
,buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
|
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
|
|
|
|
handbox = gtk.VBox(True, 0)
|
|
detailDialog.vbox.pack_start(handbox, False, False, 0)
|
|
handbox.show()
|
|
|
|
label = gtk.Label("Hand Filters:")
|
|
handbox.add(label)
|
|
label.show()
|
|
|
|
betweenFilters = []
|
|
for htest in self.handtests:
|
|
hbox = gtk.HBox(False, 0)
|
|
handbox.pack_start(hbox, False, False, 0)
|
|
hbox.show()
|
|
|
|
cb = gtk.CheckButton()
|
|
lbl_from = gtk.Label(htest[1])
|
|
lbl_from.set_alignment(xalign=0.0, yalign=0.5)
|
|
lbl_tween = gtk.Label('between')
|
|
lbl_to = gtk.Label('and')
|
|
adj1 = gtk.Adjustment(value=htest[2], lower=0, upper=10, step_incr=1, page_incr=1, page_size=0)
|
|
sb1 = gtk.SpinButton(adjustment=adj1, climb_rate=0.0, digits=0)
|
|
adj2 = gtk.Adjustment(value=htest[3], lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
|
|
sb2 = gtk.SpinButton(adjustment=adj2, climb_rate=0.0, digits=0)
|
|
|
|
for df in [x for x in self.detailFilters if x[0] == htest[0]]:
|
|
cb.set_active(True)
|
|
|
|
hbox.pack_start(cb, expand=False, padding=3)
|
|
hbox.pack_start(lbl_from, expand=True, padding=3)
|
|
hbox.pack_start(lbl_tween, expand=False, padding=3)
|
|
hbox.pack_start(sb1, False, False, 0)
|
|
hbox.pack_start(lbl_to, expand=False, padding=3)
|
|
hbox.pack_start(sb2, False, False, 0)
|
|
|
|
cb.show()
|
|
lbl_from.show()
|
|
lbl_tween.show()
|
|
sb1.show()
|
|
lbl_to.show()
|
|
sb2.show()
|
|
|
|
htest[4:7] = [cb,sb1,sb2]
|
|
|
|
response = detailDialog.run()
|
|
|
|
if response == gtk.RESPONSE_ACCEPT:
|
|
self.detailFilters = []
|
|
for ht in self.handtests:
|
|
if ht[4].get_active():
|
|
self.detailFilters.append( (ht[0], ht[5].get_value_as_int(), ht[6].get_value_as_int()) )
|
|
ht[2],ht[3] = ht[5].get_value_as_int(), ht[6].get_value_as_int()
|
|
print "detailFilters =", self.detailFilters
|
|
self.refreshStats(None, None)
|
|
|
|
detailDialog.destroy()
|
|
|
|
|