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

Conflicts:
	pyfpdb/Database.py
	pyfpdb/SQL.py
	pyfpdb/fpdb_db.py
	pyfpdb/fpdb_save_to_db.py
This commit is contained in:
Ray 2009-06-08 10:16:34 -04:00
commit 4ef4260342
34 changed files with 5016 additions and 1822 deletions

35
THANKS.txt Normal file
View File

@ -0,0 +1,35 @@
=**Thanks - Contributors to FPDB**=
This page is to acknowledge the people developing and contributing to FPDB, be it with code, documentation, testing, forum support and everything else that is required to make an open source project work.
Most people have found the project though the software forum at [[http://forumserver.twoplustwo.com/45/software/|2+2]] so most references to 2+2 aliases
Sincere apologies to those not listed. FPDB started life in August 2008, but we only got around to putting together a proper contributers list in April 2009. Private messages and emails have been deleted so we dont have a full history of non-code based contributions
==**Developers**==
Active:
Steffen123 - The initial code drop and project lead.
Eratosthenes - Initial HUD code drop, release manager
Ekdikeo - Contributor - contributed Everleaf support and much HUD work
Sorrow - Contributor - Graphing, import framework
mcturnbull (2+2?) - Contributor - import framework
Inactive:
Sqlcoder - SQL query guru - Graphing filters, Player Stats, Positional Stats queries + general sql optimisation work
Bostik - Author of [[http://bostik.iki.fi/pokerstats/|PokerStats]] - Postgres linux work. PokerStats import code was the inspiration for the new framework. I'm sure well use more of his ideas in future.
FIXME - Original windows installer/packager
==Documentation==
FIXME - Original web site guy
FIXME - Guy who wrote the Mac install page
FIXME - Any other wiki editors.
==Other==
Eleatic Stranger - Super tester and contributor - Our #1 tester. Tracks the git repo and works the code harder than anyone and gives great feedback and bug reports. Thankyou.
Xaviax - Tester and honorary helpdesk - Another excellent tester tracking git, has responded to many queries in the fpdb thread on his own time.
KayosD - Hand History donation - Carbon Poker
freerollerjb - Hand History donation - Carbon Poker
puru - Hand History donation - Carbon Poker
freestailo - Hand History donation - Carbon Poker
MoDDe (Sourceforge) - Hand History Donation - Betfair
Jay10826 - Hand History donation - Ultimate Bet

View File

@ -54,6 +54,7 @@ class CarbonPoker(HandHistoryConverter):
print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
self.setFileType("xml")
self.siteId = 4 # Needs to match id entry in Sites database
def readSupportedGames(self):
pass

83
pyfpdb/Card.py Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/python
#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.
def twoStartCards(value1, suit1, value2, suit2):
""" Function to convert 2 value,suit pairs into a Holdem style starting hand e.g. AQo
Hand is stored as an int 13 * x + y where (x+2) represents rank of 1st card and
(y+2) represents rank of second card (2=2 .. 14=Ace)
If x > y then pair is suited, if x < y then unsuited"""
if value1 < 2 or value2 < 2:
return(0)
if (suit1 == suit2 and value1 < value2) or (suit1 != suit2 and value2 > value1):
return(13 * (value2-2) + (value1-1))
else:
return(13 * (value1-2) + (value2-1))
def twoStartCardString(card):
""" Function to convert an int representing 2 holdem hole cards (as created by twoStartCards)
into a string like AQo """
if card <= 0:
return 'xx'
else:
card -= 1
s = ('2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A')
x = card/13
y = card - 13*x
if x == y: return(s[x] + s[y])
elif x > y: return(s[x] + s[y] + 's')
else: return(s[y] + s[x] + 'o')
def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4):
""" Function to convert 4 value,suit pairs into a Omaha style starting hand,
haven't decided how to encode this yet """
# This doesn't actually do anything yet - CG
# What combinations do we need to store? just cards: AA23? some suits as well e.g. when
# double suited ATcKTd? Lots more possible combos than holdem :-( 270K vs 1326? not sure
# Probably need to use this field as a key into some other table - sc
#AAKKds
#AAKKs
#AAKKr
# Is probably what we are looking for
return(0)
def cardFromValueSuit(value, suit):
""" 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As """
if suit == 'h': return(value-1)
elif suit == 'd': return(value+12)
elif suit == 'c': return(value+25)
elif suit == 's': return(value+38)
else: return(0)
def valueSuitFromCard(card):
""" Function to convert a card stored in the database (int 0-52) into value
and suit like 9s, 4c etc """
if card < 0 or card > 52:
return('')
else:
return( ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah'
, '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d', 'Td', 'Jd', 'Qd', 'Kd', 'Ad'
, '2c', '3c', '4c', '5c', '6c', '7c', '8c', '9c', 'Tc', 'Jc', 'Qc', 'Kc', 'Ac'
, '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', 'Ts', 'Js', 'Qs', 'Ks', 'As'
][card] )

View File

@ -197,6 +197,15 @@ class Aux_window:
temp = temp + "%s" % self.layout[layout]
return temp
class HHC:
def __init__(self, node):
self.site = node.getAttribute("site")
self.converter = node.getAttribute("converter")
def __str__(self):
return "%s:\t%s" % (self.site, self.converter)
class Popup:
def __init__(self, node):
self.name = node.getAttribute("pu_name")
@ -277,6 +286,7 @@ class Config:
self.supported_games = {}
self.supported_databases = {}
self.aux_windows = {}
self.hhcs = {}
self.popup_windows = {}
# s_sites = doc.getElementsByTagName("supported_sites")
@ -299,6 +309,11 @@ class Config:
aw = Aux_window(node = aw_node)
self.aux_windows[aw.name] = aw
# s_dbs = doc.getElementsByTagName("mucked_windows")
for hhc_node in doc.getElementsByTagName("hhc"):
hhc = HHC(node = hhc_node)
self.hhcs[hhc.site] = hhc
# s_dbs = doc.getElementsByTagName("popup_windows")
for pu_node in doc.getElementsByTagName("pu"):
pu = Popup(node = pu_node)
@ -707,6 +722,11 @@ if __name__== "__main__":
print c.aux_windows[w]
print "----------- END AUX WINDOW FORMATS -----------"
print "\n----------- HAND HISTORY CONVERTERS -----------"
for w in c.hhcs.keys():
print c.hhcs[w]
print "----------- END HAND HISTORY CONVERTERS -----------"
print "\n----------- POPUP WINDOW FORMATS -----------"
for w in c.popup_windows.keys():
print c.popup_windows[w]

128
pyfpdb/Database.py Normal file → Executable file
View File

@ -27,12 +27,14 @@ Create and manage the database objects.
import sys
import traceback
import string
from datetime import datetime, date, time, timedelta
# pyGTK modules
# FreePokerTools modules
import Configuration
import SQL
import Card
class Database:
def __init__(self, c, db_name, game):
@ -83,7 +85,37 @@ class Database:
sys.exit()
self.type = db_params['db-type']
self.sql = SQL.Sql(game = game, type = self.type)
self.db_server = c.supported_databases[db_name].db_server
self.sql = SQL.Sql(game = game, type = self.type, db_server = self.db_server)
self.connection.rollback()
# To add to config:
self.hud_style = 'T' # A=All-time
# S=Session
# T=timed (last n days)
# Future values may also include:
# H=Hands (last n hands)
self.hud_hands = 1000 # Max number of hands from each player to use for hud stats
self.hud_days = 90 # Max number of days from each player to use for hud stats
self.hud_session_gap = 30 # Gap (minutes) between hands that indicates a change of session
# (hands every 2 mins for 1 hour = one session, if followed
# by a 40 minute gap and then more hands on same table that is
# a new session)
cur = self.connection.cursor()
self.hand_1day_ago = 0
cur.execute(self.sql.query['get_hand_1day_ago'])
row = cur.fetchone()
if row and row[0]:
self.hand_1day_ago = row[0]
#print "hand 1day ago =", self.hand_1day_ago
d = timedelta(days=self.hud_days)
now = datetime.utcnow() - d
self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day)
self.hand_nhands_ago = 0 # todo
#cur.execute(self.sql.query['get_table_name'], (hand_id, ))
#row = cur.fetchone()
def close_connection(self):
self.connection.close()
@ -126,13 +158,19 @@ class Database:
"""Get and return the cards for each player in the hand."""
cards = {} # dict of cards, the key is the seat number example: {1: 'AcQd9hTs5d'}
c = self.connection.cursor()
c.execute(self.sql.query['get_cards'], (hand, ))
c.execute(self.sql.query['get_cards'], [hand])
colnames = [desc[0] for desc in c.description]
cardnames = ['card1', 'card2', 'card3', 'card4', 'card5', 'card6', 'card7']
for row in c.fetchall():
s_dict = {}
for name, val in zip(colnames, row):
s_dict[name] = val
cards[s_dict['seat_number']] = (self.convert_cards(s_dict))
cs = ['', '', '', '', '', '', '']
seat = -1
for col,name in enumerate(colnames):
if name in cardnames:
cs[cardnames.index(name)] = Card.valueSuitFromCard(row[col])
elif name == 'seat_number':
seat = row[col]
if seat != -1:
cards[seat] = ''.join(cs)
return cards
def get_common_cards(self, hand):
@ -190,31 +228,89 @@ class Database:
return winners
def get_stats_from_hand(self, hand, aggregate = False):
if self.hud_style == 'S':
return( self.get_stats_from_hand_session(hand) )
else: # self.hud_style == A
if aggregate:
query = 'get_stats_from_hand_aggregated'
else:
query = 'get_stats_from_hand'
if self.hud_style == 'T':
stylekey = self.date_ndays_ago
else: # assume A (all-time)
stylekey = '0000000' # all stylekey values should be higher than this
subs = (hand, hand, stylekey)
#print "get stats: hud style =", self.hud_style, "subs =", subs
c = self.connection.cursor()
if aggregate:
query = 'get_stats_from_hand_aggregated'
subs = (hand, hand, hand)
else:
query = 'get_stats_from_hand'
subs = (hand, hand)
# now get the stats
# now get the stats
c.execute(self.sql.query[query], subs)
colnames = [desc[0] for desc in c.description]
stat_dict = {}
for row in c.fetchall():
t_dict = {}
for name, val in zip(colnames, row):
t_dict[name] = val
t_dict[name.lower()] = val
# print t_dict
stat_dict[t_dict['player_id']] = t_dict
return stat_dict
# uses query on handsplayers instead of hudcache to get stats on just this session
def get_stats_from_hand_session(self, hand):
if self.hud_style == 'S':
query = self.sql.query['get_stats_from_hand_session']
if self.db_server == 'mysql':
query = query.replace("<signed>", 'signed ')
else:
query = query.replace("<signed>", '')
else: # self.hud_style == A
return None
subs = (self.hand_1day_ago, hand)
c = self.connection.cursor()
# now get the stats
#print "sess_stats: subs =", subs, "subs[0] =", subs[0]
c.execute(query, subs)
colnames = [desc[0] for desc in c.description]
n,stat_dict = 0,{}
row = c.fetchone()
while row:
if colnames[0].lower() == 'player_id':
playerid = row[0]
else:
print "ERROR: query %s result does not have player_id as first column" % (query,)
break
for name, val in zip(colnames, row):
if not playerid in stat_dict:
stat_dict[playerid] = {}
stat_dict[playerid][name.lower()] = val
elif not name.lower() in stat_dict[playerid]:
stat_dict[playerid][name.lower()] = val
elif name.lower() not in ('hand_id', 'player_id', 'seat', 'screen_name', 'seats'):
stat_dict[playerid][name.lower()] += val
n += 1
if n >= 4000: break # todo: don't think this is needed so set nice and high
# for now - comment out or remove?
row = c.fetchone()
#print " %d rows fetched, len(stat_dict) = %d" % (n, len(stat_dict))
#print "session stat_dict =", stat_dict
return stat_dict
def get_player_id(self, config, site, player_name):
c = self.connection.cursor()
c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site})
row = c.fetchone()
return row[0]
if row:
return row[0]
else:
return None
if __name__=="__main__":
c = Configuration.Config()

View File

@ -26,8 +26,10 @@ class DerivedStats():
self.HDs = 0
self.street0VPI = 0
self.street0Aggr = 0
self.street0_3B4BChance = 0
self.street0_3B4BDone = 0
self.street0_3BChance = 0
self.street0_3BDone = 0
self.street0_4BChance = 0
self.street0_4BDone = 0
self.street1Seen = 0
self.street2Seen = 0

View File

@ -27,7 +27,8 @@ from HandHistoryConverter import *
class Everleaf(HandHistoryConverter):
# Static regexes
re_SplitHands = re.compile(r"(\n\n\n+)")
re_SplitHands = re.compile(r"\n\n\n+")
re_TailSplitHands = re.compile(r"(\n\n\n+)")
re_GameInfo = re.compile(ur"^(Blinds )?(?P<CURRENCY>\$| €|)(?P<SB>[.0-9]+)/(?:\$| €)?(?P<BB>[.0-9]+) (?P<LIMIT>NL|PL|) ?(?P<GAME>(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE)
#re.compile(ur"^(Blinds )?(?P<CURRENCY>\$| €|)(?P<SB>[.0-9]+)/(?:\$| €)?(?P<BB>[.0-9]+) (?P<LIMIT>NL|PL|) (?P<GAME>(Hold\'em|Omaha|7 Card Stud))", re.MULTILINE)
re_HandInfo = re.compile(ur".*#(?P<HID>[0-9]+)\n.*\n(Blinds )?(?:\$| €|)(?P<SB>[.0-9]+)/(?:\$| €|)(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<DATETIME>\d\d\d\d/\d\d/\d\d - \d\d:\d\d:\d\d)\nTable (?P<TABLE>.+$)", re.MULTILINE)
@ -48,6 +49,7 @@ debugging: if False, pass on partially supported game types. If true, have a go
logging.info("Initialising Everleaf converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 3 # Needs to match id entry in Sites database
self.debugging = debugging
if autostart:
self.start()

540
pyfpdb/Filters.py Normal file
View File

@ -0,0 +1,540 @@
#!/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
import sys
from optparse import OptionParser
from time import *
#import pokereval
import Configuration
import fpdb_db
import FpdbSQLQueries
class Filters(threading.Thread):
def __init__(self, db, settings, config, qdict, display = {},debug=True):
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=qdict
self.conf = config
self.display = display
self.sites = {}
self.games = {}
self.limits = {}
self.seats = {}
self.siteid = {}
self.heroes = {}
self.boxes = {}
# text used on screen stored here so that it can be configured
self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits'
,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players'
,'limitstitle':'Limits:', 'seatstitle':'Number of Players:'
}
# For use in date ranges.
self.start_date = gtk.Entry(max=12)
self.end_date = gtk.Entry(max=12)
self.start_date.set_property('editable', False)
self.end_date.set_property('editable', False)
# Outer Packing box
self.mainVBox = gtk.VBox(False, 0)
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
vbox = gtk.VBox(False, 0)
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
self.boxes['player'] = vbox
sitesFrame = gtk.Frame("Sites:")
sitesFrame.set_label_align(0.0, 0.0)
vbox = gtk.VBox(False, 0)
self.fillSitesFrame(vbox)
sitesFrame.add(vbox)
self.boxes['sites'] = vbox
# Game types
gamesFrame = gtk.Frame("Games:")
gamesFrame.set_label_align(0.0, 0.0)
gamesFrame.show()
vbox = gtk.VBox(False, 0)
self.fillGamesFrame(vbox)
gamesFrame.add(vbox)
self.boxes['games'] = vbox
# Limits
limitsFrame = gtk.Frame()
limitsFrame.show()
vbox = gtk.VBox(False, 0)
self.cbLimits = {}
self.cbNoLimits = None
self.cbAllLimits = None
self.fillLimitsFrame(vbox, self.display)
limitsFrame.add(vbox)
# Seats
seatsFrame = gtk.Frame()
seatsFrame.show()
vbox = gtk.VBox(False, 0)
self.sbSeats = {}
self.fillSeatsFrame(vbox, self.display)
seatsFrame.add(vbox)
# Date
dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0)
dateFrame.show()
vbox = gtk.VBox(False, 0)
self.fillDateFrame(vbox)
dateFrame.add(vbox)
self.boxes['date'] = vbox
# Buttons
self.Button1=gtk.Button("Unnamed 1")
self.Button1.set_sensitive(False)
self.Button2=gtk.Button("Unnamed 2")
self.Button2.set_sensitive(False)
self.mainVBox.add(playerFrame)
self.mainVBox.add(sitesFrame)
self.mainVBox.add(gamesFrame)
self.mainVBox.add(limitsFrame)
self.mainVBox.add(seatsFrame)
self.mainVBox.add(dateFrame)
self.mainVBox.add(self.Button1)
self.mainVBox.add(self.Button2)
self.mainVBox.show_all()
# Should do this cleaner
if "Heroes" not in self.display or self.display["Heroes"] == False:
playerFrame.hide()
if "Sites" not in self.display or self.display["Sites"] == False:
sitesFrame.hide()
if "Games" not in self.display or self.display["Games"] == False:
gamesFrame.hide()
if "Limits" not in self.display or self.display["Limits"] == False:
limitsFrame.hide()
if "Seats" not in self.display or self.display["Seats"] == False:
seatsFrame.hide()
if "Dates" not in self.display or self.display["Dates"] == False:
dateFrame.hide()
if "Button1" not in self.display or self.display["Button1"] == False:
self.Button1.hide()
if "Button2" not in self.display or self.display["Button2"] == False:
self.Button2.hide()
def get_vbox(self):
"""returns the vbox of this thread"""
return self.mainVBox
#end def get_vbox
def getSites(self):
return self.sites
def getSiteIds(self):
return self.siteid
def getHeroes(self):
return self.heroes
def getLimits(self):
ltuple = []
for l in self.limits:
if self.limits[l] == True:
ltuple.append(l)
return ltuple
def getSeats(self):
if 'from' in self.sbSeats:
self.seats['from'] = self.sbSeats['from'].get_value_as_int()
if 'to' in self.sbSeats:
self.seats['to'] = self.sbSeats['to'].get_value_as_int()
return self.seats
def getDates(self):
return self.__get_dates()
def registerButton1Name(self, title):
self.Button1.set_label(title)
def registerButton1Callback(self, callback):
self.Button1.connect("clicked", callback, "clicked")
self.Button1.set_sensitive(True)
def registerButton2Name(self, title):
self.Button2.set_label(title)
def registerButton2Callback(self, callback):
self.Button2.connect("clicked", callback, "clicked")
self.Button2.set_sensitive(True)
def cardCallback(self, widget, data=None):
print "DEBUG: %s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
def createPlayerLine(self, hbox, site, player):
label = gtk.Label(site +" id:")
hbox.pack_start(label, False, False, 0)
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site)
cb.connect('clicked', self.__set_site_select, site)
cb.set_active(True)
hbox.pack_start(cb, False, False, 0)
def createGameLine(self, hbox, game):
cb = gtk.CheckButton(game)
cb.connect('clicked', self.__set_game_select, game)
hbox.pack_start(cb, False, False, 0)
def createLimitLine(self, hbox, limit, ltext):
cb = gtk.CheckButton(str(ltext))
cb.connect('clicked', self.__set_limit_select, limit)
hbox.pack_start(cb, False, False, 0)
if limit != "none":
cb.set_active(True)
return(cb)
def __set_site_select(self, w, site):
#print w.get_active()
self.sites[site] = w.get_active()
print "self.sites[%s] set to %s" %(site, self.sites[site])
def __set_game_select(self, w, game):
#print w.get_active()
self.games[game] = w.get_active()
print "self.games[%s] set to %s" %(game, self.games[game])
def __set_limit_select(self, w, limit):
#print w.get_active()
self.limits[limit] = w.get_active()
print "self.limit[%s] set to %s" %(limit, self.limits[limit])
if str(limit).isdigit():
if self.limits[limit]:
if self.cbNoLimits != None:
self.cbNoLimits.set_active(False)
else:
if self.cbAllLimits != None:
self.cbAllLimits.set_active(False)
elif limit == "all":
if self.limits[limit]:
for cb in self.cbLimits.values():
cb.set_active(True)
elif limit == "none":
if self.limits[limit]:
for cb in self.cbLimits.values():
cb.set_active(False)
def __set_seat_select(self, w, seat):
#print "__set_seat_select: seat =", seat, "active =", w.get_active()
self.seats[seat] = w.get_active()
print "self.seats[%s] set to %s" %(seat, self.seats[seat])
def fillPlayerFrame(self, vbox):
for site in self.conf.get_supported_sites():
pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0)
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(pathHBox, site, player)
def fillSitesFrame(self, vbox):
for site in self.conf.get_supported_sites():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
self.createSiteLine(hbox, site)
#Get db site id for filtering later
self.cursor.execute(self.sql.query['getSiteId'], (site,))
result = self.db.cursor.fetchall()
if len(result) == 1:
self.siteid[site] = result[0][0]
else:
print "Either 0 or more than one site matched - EEK"
def fillGamesFrame(self, vbox):
self.cursor.execute(self.sql.query['getGames'])
result = self.db.cursor.fetchall()
if len(result) >= 1:
for line in result:
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
self.createGameLine(hbox, line[0])
else:
print "INFO: No games returned from database"
def fillLimitsFrame(self, vbox, display):
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, False, 0)
lbl_title = gtk.Label(self.filterText['limitstitle'])
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
hbox.pack_start(lbl_title, expand=True, padding=3)
showb = gtk.Button(label="hide", stock=None, use_underline=True)
showb.set_alignment(xalign=1.0, yalign=0.5)
showb.connect('clicked', self.__toggle_box, 'limits')
hbox.pack_start(showb, expand=False, padding=1)
vbox1 = gtk.VBox(False, 0)
vbox.pack_start(vbox1, False, False, 0)
self.boxes['limits'] = vbox1
self.cursor.execute(self.sql.query['getLimits'])
result = self.db.cursor.fetchall()
if len(result) >= 1:
hbox = gtk.HBox(True, 0)
vbox1.pack_start(hbox, False, False, 0)
vbox2 = gtk.VBox(False, 0)
hbox.pack_start(vbox2, False, False, 0)
vbox3 = gtk.VBox(False, 0)
hbox.pack_start(vbox3, False, False, 0)
for i, line in enumerate(result):
hbox = gtk.HBox(False, 0)
if i <= len(result)/2:
vbox2.pack_start(hbox, False, False, 0)
else:
vbox3.pack_start(hbox, False, False, 0)
self.cbLimits[line[0]] = self.createLimitLine(hbox, line[0], line[0])
if "LimitSep" in display and display["LimitSep"] == True and len(result) >= 2:
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
self.cbAllLimits = self.createLimitLine(hbox, 'all', self.filterText['limitsall'])
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
self.cbNoLimits = self.createLimitLine(hbox, 'none', self.filterText['limitsnone'])
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
cb = self.createLimitLine(hbox, 'show', self.filterText['limitsshow'])
else:
print "INFO: No games returned from database"
def fillSeatsFrame(self, vbox, display):
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, False, 0)
lbl_title = gtk.Label(self.filterText['seatstitle'])
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
hbox.pack_start(lbl_title, expand=True, padding=3)
showb = gtk.Button(label="hide", stock=None, use_underline=True)
showb.set_alignment(xalign=1.0, yalign=0.5)
showb.connect('clicked', self.__toggle_box, 'seats')
hbox.pack_start(showb, expand=False, padding=1)
vbox1 = gtk.VBox(False, 0)
vbox.pack_start(vbox1, False, False, 0)
self.boxes['seats'] = vbox1
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
lbl_from = gtk.Label(self.filterText['seatsbetween'])
lbl_to = gtk.Label(self.filterText['seatsand'])
adj1 = gtk.Adjustment(value=2, lower=2, 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=10, lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
sb2 = gtk.SpinButton(adjustment=adj2, climb_rate=0.0, digits=0)
hbox.pack_start(lbl_from, 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)
if "SeatSep" in display and display["SeatSep"] == True:
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
cb = gtk.CheckButton(self.filterText['seatsshow'])
cb.connect('clicked', self.__set_seat_select, 'show')
hbox.pack_start(cb, False, False, 0)
self.sbSeats['show'] = cb
self.seats['show'] = False
self.sbSeats['from'] = sb1
self.sbSeats['to'] = sb2
def fillCardsFrame(self, vbox):
hbox1 = gtk.HBox(True,0)
hbox1.show()
vbox.pack_start(hbox1, True, True, 0)
cards = [ "A", "K","Q","J","T","9","8","7","6","5","4","3","2" ]
for j in range(0, len(cards)):
hbox1 = gtk.HBox(True,0)
hbox1.show()
vbox.pack_start(hbox1, True, True, 0)
for i in range(0, len(cards)):
if i < (j + 1):
suit = "o"
else:
suit = "s"
button = gtk.ToggleButton("%s%s%s" %(cards[i], cards[j], suit))
button.connect("toggled", self.cardCallback, "%s%s%s" %(cards[i], cards[j], suit))
hbox1.pack_start(button, True, True, 0)
button.show()
def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
lbl_start = gtk.Label('From:')
btn_start = gtk.Button()
btn_start.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_start.connect('clicked', self.__calendar_dialog, self.start_date)
hbox.pack_start(lbl_start, expand=False, padding=3)
hbox.pack_start(btn_start, expand=False, padding=3)
hbox.pack_start(self.start_date, expand=False, padding=2)
#New row for end date
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
lbl_end = gtk.Label(' To:')
btn_end = gtk.Button()
btn_end.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_end.connect('clicked', self.__calendar_dialog, self.end_date)
btn_clear = gtk.Button(label=' Clear Dates ')
btn_clear.connect('clicked', self.__clear_dates)
hbox.pack_start(lbl_end, expand=False, padding=3)
hbox.pack_start(btn_end, expand=False, padding=3)
hbox.pack_start(self.end_date, expand=False, padding=2)
hbox.pack_start(btn_clear, expand=False, padding=15)
def __toggle_box(self, widget, entry):
if "Limits" not in self.display or self.display["Limits"] == False:
self.boxes[entry].hide()
elif self.boxes[entry].props.visible:
self.boxes[entry].hide()
widget.set_label("show")
else:
self.boxes[entry].show()
widget.set_label("hide")
def __calendar_dialog(self, widget, entry):
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
d.set_title('Pick a date')
vb = gtk.VBox()
cal = gtk.Calendar()
vb.pack_start(cal, expand=False, padding=0)
btn = gtk.Button('Done')
btn.connect('clicked', self.__get_date, cal, entry, d)
vb.pack_start(btn, expand=False, padding=4)
d.add(vb)
d.set_position(gtk.WIN_POS_MOUSE)
d.show_all()
def __clear_dates(self, w):
self.start_date.set_text('')
self.end_date.set_text('')
def __get_dates(self):
t1 = self.start_date.get_text()
t2 = self.end_date.get_text()
if t1 == '':
t1 = '1970-01-01'
if t2 == '':
t2 = '2020-12-12'
return (t1, t2)
def __get_date(self, widget, calendar, entry, win):
# year and day are correct, month is 0..11
(year, month, day) = calendar.get_date()
month += 1
ds = '%04d-%02d-%02d' % (year, month, day)
entry.set_text(ds)
win.destroy()
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()
parser = OptionParser()
(options, sys.argv) = parser.parse_args(args = argv)
config = Configuration.Config()
db = None
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())
db = fpdb_db.fpdb_db()
db.connect(settings['db-backend'],
settings['db-host'],
settings['db-databaseName'],
settings['db-user'],
settings['db-password'])
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name())
i = Filters(db, settings, config, qdict)
main_window = gtk.Window()
main_window.connect('destroy', destroy)
main_window.add(i.get_vbox())
main_window.show()
gtk.main()
if __name__ == '__main__':
sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@ -28,11 +28,13 @@ class Fulltilt(HandHistoryConverter):
# Static regexes
re_GameInfo = re.compile('- (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<LIMIT>(No Limit|Pot Limit|Limit))? (?P<GAME>(Hold\'em|Omaha Hi|Razz))')
re_SplitHands = re.compile(r"(\n\n+)")
re_SplitHands = re.compile(r"\n\n+")
re_TailSplitHands = re.compile(r"(\n\n+)")
re_HandInfo = re.compile('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[- a-zA-Z]+) (\((?P<TABLEATTRIBUTES>.+)\) )?- \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<GAMETYPE>[a-zA-Z\' ]+) - (?P<DATETIME>.*)')
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>.*) \(\$(?P<CASH>[.0-9]+)\)\n')
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\
@ -43,6 +45,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising Fulltilt converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 1 # Needs to match id entry in Sites database
if autostart:
self.start()

View File

@ -57,28 +57,55 @@ class GuiAutoImport (threading.Thread):
self.database=settings['db-databaseName']
self.mainVBox=gtk.VBox(False,1)
self.mainVBox.show()
self.settingsHBox = gtk.HBox(False, 0)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show()
hbox = gtk.HBox(True, 0) # contains 2 equal vboxes
self.mainVBox.pack_start(hbox, False, False, 0)
self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:")
self.settingsHBox.pack_start(self.intervalLabel)
self.intervalLabel.show()
vbox1 = gtk.VBox(True, 0)
hbox.pack_start(vbox1, True, True, 0)
vbox2 = gtk.VBox(True, 0)
hbox.pack_start(vbox2, True, True, 0)
self.intervalEntry=gtk.Entry()
self.intervalLabel = gtk.Label("Time between imports in seconds:")
self.intervalLabel.set_alignment(xalign=1.0, yalign=0.5)
vbox1.pack_start(self.intervalLabel, True, True, 0)
hbox = gtk.HBox(False, 0)
vbox2.pack_start(hbox, True, True, 0)
self.intervalEntry = gtk.Entry()
self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval")))
self.settingsHBox.pack_start(self.intervalEntry)
self.intervalEntry.show()
hbox.pack_start(self.intervalEntry, False, False, 0)
lbl1 = gtk.Label()
hbox.pack_start(lbl1, expand=True, fill=True)
self.addSites(self.mainVBox)
lbl = gtk.Label('')
vbox1.pack_start(lbl, expand=True, fill=True)
lbl = gtk.Label('')
vbox2.pack_start(lbl, expand=True, fill=True)
self.addSites(vbox1, vbox2)
hbox = gtk.HBox(False, 0)
self.mainVBox.pack_start(hbox, expand=True, padding=3)
hbox = gtk.HBox(False, 0)
self.mainVBox.pack_start(hbox, expand=False, padding=3)
lbl1 = gtk.Label()
hbox.pack_start(lbl1, expand=True, fill=False)
self.doAutoImportBool = False
self.startButton=gtk.ToggleButton("Start Autoimport")
self.startButton = gtk.ToggleButton(" _Start Autoimport ")
self.startButton.connect("clicked", self.startClicked, "start clicked")
self.mainVBox.add(self.startButton)
self.startButton.show()
hbox.pack_start(self.startButton, expand=False, fill=False)
lbl2 = gtk.Label()
hbox.pack_start(lbl2, expand=True, fill=False)
hbox = gtk.HBox(False, 0)
hbox.show()
self.mainVBox.pack_start(hbox, expand=True, padding=3)
self.mainVBox.show_all()
#end of GuiAutoImport.__init__
@ -127,7 +154,7 @@ class GuiAutoImport (threading.Thread):
# to watch.
if widget.get_active(): # toggled on
self.doAutoImportBool = True
widget.set_label(u'Stop Autoimport')
widget.set_label(u' _Stop Autoimport ')
if self.pipe_to_hud is None:
if os.name == 'nt':
command = "python HUD_main.py" + " %s" % (self.database)
@ -163,7 +190,7 @@ class GuiAutoImport (threading.Thread):
#print >>self.pipe_to_hud.stdin, "\n"
self.pipe_to_hud.communicate('\n') # waits for process to terminate
self.pipe_to_hud = None
self.startButton.set_label(u'Start Autoimport')
self.startButton.set_label(u' _Start Autoimport ')
@ -177,40 +204,41 @@ class GuiAutoImport (threading.Thread):
#Create the site line given required info and setup callbacks
#enabling and disabling sites from this interface not possible
#expects a box to layout the line horizontally
def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True):
def createSiteLine(self, hbox1, hbox2, site, iconpath, hhpath, filter_name, active = True):
label = gtk.Label(site + " auto-import:")
hbox.pack_start(label, False, False, 0)
hbox1.pack_start(label, False, False, 3)
label.show()
dirPath=gtk.Entry()
dirPath.set_text(hhpath)
hbox.pack_start(dirPath, False, True, 0)
hbox1.pack_start(dirPath, True, True, 3)
dirPath.show()
browseButton=gtk.Button("Browse...")
browseButton.connect("clicked", self.browseClicked, [site] + [dirPath])
hbox.pack_start(browseButton, False, False, 0)
hbox2.pack_start(browseButton, False, False, 3)
browseButton.show()
label = gtk.Label(site + " filter:")
hbox.pack_start(label, False, False, 0)
label = gtk.Label(' ' + site + " filter:")
hbox2.pack_start(label, False, False, 3)
label.show()
filter=gtk.Entry()
filter.set_text(filter_name)
hbox.pack_start(filter, False, True, 0)
hbox2.pack_start(filter, True, True, 3)
filter.show()
def addSites(self, vbox):
def addSites(self, vbox1, vbox2):
the_sites = self.config.get_supported_sites()
for site in the_sites:
pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show()
pathHBox1 = gtk.HBox(False, 0)
vbox1.pack_start(pathHBox1, False, True, 0)
pathHBox2 = gtk.HBox(False, 0)
vbox2.pack_start(pathHBox2, False, True, 0)
params = self.config.get_site_parameters(site)
paths = self.config.get_default_paths(site)
self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled'])
self.createSiteLine(pathHBox1, pathHBox2, site, False, paths['hud-defaultPath'], params['converter'], params['enabled'])
self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']]
if __name__== "__main__":

28
pyfpdb/GuiBulkImport.py Normal file → Executable file
View File

@ -30,7 +30,6 @@ import gtk
# fpdb/FreePokerTools modules
import fpdb_simple
import fpdb_import
import fpdb_db
import Configuration
class GuiBulkImport():
@ -66,13 +65,16 @@ class GuiBulkImport():
self.importer.setDropIndexes(cb_model[cb_index][0])
else:
self.importer.setDropIndexes("auto")
hhc=self.cbfilter.get_model()[self.cbfilter.get_active()][0]
sitename = self.cbfilter.get_model()[self.cbfilter.get_active()][0]
self.lab_info.set_text("Importing")
self.importer.addBulkImportImportFileOrDir(self.inputFile,filter=hhc)
self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename)
self.importer.setCallHud(False)
starttime = time()
(stored, dups, partial, errs, ttime) = self.importer.runImport()
ttime = time() - starttime
if ttime == 0:
ttime = 1
print 'GuiBulkImport.import_dir done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %d/sec'\
% (stored, dups, partial, errs, ttime, stored / ttime)
self.importer.clearFileList()
@ -83,8 +85,7 @@ class GuiBulkImport():
"""returns the vbox of this thread"""
return self.vbox
def __init__(self, db, settings, config):
self.db = db # this is an instance of fpdb_db
def __init__(self, settings, config):
self.settings = settings
self.config = config
self.importer = fpdb_import.Importer(self, self.settings,
@ -175,11 +176,9 @@ class GuiBulkImport():
# ComboBox - filter
self.cbfilter = gtk.combo_box_new_text()
self.cbfilter.append_text("passthrough")
self.cbfilter.append_text("BetfairToFpdb")
self.cbfilter.append_text("EverleafToFpdb")
self.cbfilter.append_text("FulltiltToFpdb")
self.cbfilter.append_text("PokerStarsToFpdb")
for w in self.config.hhcs:
print w
self.cbfilter.append_text(w)
self.cbfilter.set_active(0)
self.table.attach(self.cbfilter, 3, 4, 2, 3, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK)
self.cbfilter.show()
@ -220,8 +219,8 @@ def main(argv=None):
help="Input file in quiet mode")
parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True,
help="don't start gui; deprecated (just give a filename with -f).")
parser.add_option("-c", "--convert", dest="filtername", default="passthrough", metavar="FILTER",
help="Conversion filter (*passthrough, FullTiltToFpdb, PokerStarsToFpdb, EverleafToFpdb)")
parser.add_option("-c", "--convert", dest="filtername", default="PokerStars", metavar="FILTER",
help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf)")
parser.add_option("-x", "--failOnError", action="store_true", default=False,
help="If this option is passed it quits when it encounters any error")
parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int",
@ -229,7 +228,6 @@ def main(argv=None):
(options, sys.argv) = parser.parse_args(args = argv)
config = Configuration.Config()
db = None
settings = {}
settings['minPrint'] = options.minPrint
@ -245,7 +243,7 @@ def main(argv=None):
print '-q is deprecated. Just use "-f filename" instead'
# This is because -q on its own causes an error, so -f is necessary and sufficient for cmd line use
if not options.filename:
i = GuiBulkImport(db, settings, config)
i = GuiBulkImport(settings, config)
main_window = gtk.Window()
main_window.connect('destroy', destroy)
main_window.add(i.vbox)
@ -256,7 +254,7 @@ def main(argv=None):
importer = fpdb_import.Importer(False,settings, config)
importer.setDropIndexes("auto")
importer.setFailOnError(options.failOnError)
importer.addBulkImportImportFileOrDir(os.path.expanduser(options.filename), filter=options.filtername)
importer.addBulkImportImportFileOrDir(os.path.expanduser(options.filename), site=options.filtername)
importer.setCallHud(False)
importer.runImport()
importer.clearFileList()

View File

@ -39,269 +39,9 @@ except:
import fpdb_import
import fpdb_db
import Filters
class GuiGraphViewer (threading.Thread):
def get_vbox(self):
"""returns the vbox of this thread"""
return self.mainHBox
#end def get_vbox
def clearGraphData(self):
self.fig.clf()
if self.canvas is not None:
self.canvas.destroy()
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
def generateGraph(self, widget, data):
self.clearGraphData()
sitenos = []
playerids = []
# Which sites are selected?
for site in self.sites:
if self.sites[site] == True:
sitenos.append(self.siteid[site])
self.cursor.execute(self.sql.query['getPlayerId'], (self.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
#Set graph properties
self.ax = self.fig.add_subplot(111)
#Get graph data from DB
starttime = time()
line = self.getRingProfitGraph(playerids, sitenos)
print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games")
#Set axis labels and grid overlay properites
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
if(line == None):
#TODO: Do something useful like alert user
print "No hands returned by graph query"
else:
# text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
text = "All Hands, " + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
self.ax.annotate(text,
xy=(10, -10),
xycoords='axes points',
horizontalalignment='left', verticalalignment='top',
fontsize=10)
#Draw plot
self.ax.plot(line,)
self.graphBox.add(self.canvas)
self.canvas.show()
self.exportButton.set_sensitive(True)
#end of def showClicked
def getRingProfitGraph(self, names, sites):
tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
# print "DEBUG: getRingProfitGraph"
start_date, end_date = self.__get_dates()
if start_date == '':
start_date = '1970-01-01'
if end_date == '':
end_date = '2020-12-12'
#Buggered if I can find a way to do this 'nicely' take a list of intergers and longs
# and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names))
sitetest = str(tuple(sites))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
sitetest = sitetest.replace(",)",")")
#Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
tmp = tmp.replace("<player_test>", nametest)
tmp = tmp.replace("<site_test>", sitetest)
tmp = tmp.replace("<startdate_test>", start_date)
tmp = tmp.replace("<enddate_test>", end_date)
# print "DEBUG: sql query:"
# print tmp
self.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
if(winnings == ()):
return None
y=map(lambda x:float(x[3]), winnings)
line = cumsum(y)
return line/100
#end of def getRingProfitGraph
def createPlayerLine(self, hbox, site, player):
label = gtk.Label(site +" id:")
hbox.pack_start(label, False, False, 0)
label.show()
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site)
cb.connect('clicked', self.__set_site_select, site)
hbox.pack_start(cb, False, False, 0)
cb.show()
def __set_site_select(self, w, site):
# This doesn't behave as intended - self.site only allows 1 site for the moment.
print w.get_active()
self.sites[site] = w.get_active()
print "self.sites[%s] set to %s" %(site, self.sites[site])
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(pathHBox, site, player)
def fillSitesFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
self.createSiteLine(hbox, site)
#Get db site id for filtering later
self.cursor.execute(self.sql.query['getSiteId'], (site,))
result = self.db.cursor.fetchall()
if len(result) == 1:
self.siteid[site] = result[0][0]
else:
print "Either 0 or more than one site matched - EEK"
def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_start = gtk.Label('From:')
lbl_start.show()
btn_start = gtk.Button()
btn_start.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_start.connect('clicked', self.__calendar_dialog, self.start_date)
btn_start.show()
hbox.pack_start(lbl_start, expand=False, padding=3)
hbox.pack_start(btn_start, expand=False, padding=3)
hbox.pack_start(self.start_date, expand=False, padding=2)
self.start_date.show()
#New row for end date
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_end = gtk.Label(' To:')
lbl_end.show()
btn_end = gtk.Button()
btn_end.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_end.connect('clicked', self.__calendar_dialog, self.end_date)
btn_end.show()
btn_clear = gtk.Button(label=' Clear Dates ')
btn_clear.connect('clicked', self.__clear_dates)
btn_clear.show()
hbox.pack_start(lbl_end, expand=False, padding=3)
hbox.pack_start(btn_end, expand=False, padding=3)
hbox.pack_start(self.end_date, expand=False, padding=2)
self.end_date.show()
hbox.pack_start(btn_clear, expand=False, padding=15)
def __calendar_dialog(self, widget, entry):
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
d.set_title('Pick a date')
vb = gtk.VBox()
cal = gtk.Calendar()
vb.pack_start(cal, expand=False, padding=0)
btn = gtk.Button('Done')
btn.connect('clicked', self.__get_date, cal, entry, d)
vb.pack_start(btn, expand=False, padding=4)
d.add(vb)
d.set_position(gtk.WIN_POS_MOUSE)
d.show_all()
def __clear_dates(self, w):
self.start_date.set_text('')
self.end_date.set_text('')
def __get_dates(self):
t1 = self.start_date.get_text()
t2 = self.end_date.get_text()
return (t1, t2)
def __get_date(self, widget, calendar, entry, win):
# year and day are correct, month is 0..11
(year, month, day) = calendar.get_date()
month += 1
ds = '%04d-%02d-%02d' % (year, month, day)
entry.set_text(ds)
win.destroy()
def exportGraph (self, widget, data):
if self.fig is None:
return # Might want to disable export button until something has been generated.
dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
#TODO: Suggest path and filename to start with
response = dia_chooser.run()
if response == gtk.RESPONSE_OK:
self.exportDir = dia_chooser.get_filename()
print "DEBUG: self.exportDir = %s" %(self.exportDir)
elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no graph exported'
dia_chooser.destroy()
#TODO: Check to see if file exists
#NOTE: Dangerous - will happily overwrite any file we have write access too
#TODO: This asks for a directory but will take a filename and overwrite it.
self.fig.savefig(self.exportDir, format="png")
def __init__(self, db, settings, querylist, config, debug=True):
"""Constructor for GraphViewer"""
@ -313,21 +53,28 @@ class GuiGraphViewer (threading.Thread):
self.sql=querylist
self.conf = config
self.sites = {}
self.siteid = {}
self.heroes = {}
filters_display = { "Heroes" : True,
"Sites" : True,
"Games" : True,
"Limits" : True,
"Seats" : False,
"Dates" : True,
"Button1" : True,
"Button2" : True
}
# For use in date ranges.
self.start_date = gtk.Entry(max=12)
self.end_date = gtk.Entry(max=12)
self.start_date.set_property('editable', False)
self.end_date.set_property('editable', False)
self.filters = Filters.Filters(db, settings, config, querylist, display = filters_display)
self.filters.registerButton1Name("Refresh Graph")
self.filters.registerButton1Callback(self.generateGraph)
self.filters.registerButton2Name("Export to File")
self.filters.registerButton2Callback(self.exportGraph)
self.mainHBox = gtk.HBox(False, 0)
self.mainHBox.show()
self.leftPanelBox = gtk.VBox(False, 0)
self.leftPanelBox = self.filters.get_vbox()
self.graphBox = gtk.VBox(False, 0)
self.graphBox.show()
self.hpane = gtk.HPaned()
self.hpane.pack1(self.leftPanelBox)
@ -336,55 +83,15 @@ class GuiGraphViewer (threading.Thread):
self.mainHBox.add(self.hpane)
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
sitesFrame = gtk.Frame("Sites:")
sitesFrame.set_label_align(0.0, 0.0)
sitesFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillSitesFrame(vbox)
sitesFrame.add(vbox)
dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0)
dateFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillDateFrame(vbox)
dateFrame.add(vbox)
graphButton=gtk.Button("Generate Graph")
graphButton.connect("clicked", self.generateGraph, "cliced data")
graphButton.show()
self.fig = None
self.exportButton=gtk.Button("Export to File")
self.exportButton.connect("clicked", self.exportGraph, "show clicked")
self.exportButton.set_sensitive(False)
self.exportButton.show()
self.leftPanelBox.add(playerFrame)
self.leftPanelBox.add(sitesFrame)
self.leftPanelBox.add(dateFrame)
self.leftPanelBox.add(graphButton)
self.leftPanelBox.add(self.exportButton)
self.leftPanelBox.show()
self.graphBox.show()
#self.exportButton.set_sensitive(False)
self.fig = Figure(figsize=(5,4), dpi=100)
self.canvas = None
self.db.db.rollback()
#################################
#
# self.db.cursor.execute("""select UNIX_TIMESTAMP(handStart) as time, id from Hands ORDER BY time""")
@ -412,3 +119,144 @@ class GuiGraphViewer (threading.Thread):
# print "Total: ", total
#################################
def get_vbox(self):
"""returns the vbox of this thread"""
return self.mainHBox
#end def get_vbox
def clearGraphData(self):
self.fig.clf()
if self.canvas is not None:
self.canvas.destroy()
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
def generateGraph(self, widget, data):
self.clearGraphData()
sitenos = []
playerids = []
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
# 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
#Set graph properties
self.ax = self.fig.add_subplot(111)
#Get graph data from DB
starttime = time()
line = self.getRingProfitGraph(playerids, sitenos, limits)
print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games")
#Set axis labels and grid overlay properites
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
if line == None or line == []:
#TODO: Do something useful like alert user
print "No hands returned by graph query"
else:
# text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
text = "All Hands, " + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
self.ax.annotate(text,
xy=(10, -10),
xycoords='axes points',
horizontalalignment='left', verticalalignment='top',
fontsize=10)
#Draw plot
self.ax.plot(line,)
self.graphBox.add(self.canvas)
self.canvas.show()
#self.exportButton.set_sensitive(True)
#end of def showClicked
def getRingProfitGraph(self, names, sites, limits):
tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
# print "DEBUG: getRingProfitGraph"
start_date, end_date = self.filters.getDates()
#Buggered if I can find a way to do this 'nicely' take a list of intergers and longs
# and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names))
sitetest = str(tuple(sites))
limittest = str(tuple(limits))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
sitetest = sitetest.replace(",)",")")
limittest = limittest.replace("L", "")
limittest = limittest.replace(",)",")")
#Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
tmp = tmp.replace("<player_test>", nametest)
tmp = tmp.replace("<site_test>", sitetest)
tmp = tmp.replace("<startdate_test>", start_date)
tmp = tmp.replace("<enddate_test>", end_date)
tmp = tmp.replace("<limit_test>", limittest)
#print "DEBUG: sql query:"
#print tmp
self.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall()
self.db.db.rollback()
if(winnings == ()):
return None
y=map(lambda x:float(x[3]), winnings)
line = cumsum(y)
return line/100
#end of def getRingProfitGraph
def exportGraph (self, widget, data):
if self.fig is None:
return # Might want to disable export button until something has been generated.
dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
#TODO: Suggest path and filename to start with
response = dia_chooser.run()
if response == gtk.RESPONSE_OK:
self.exportDir = dia_chooser.get_filename()
print "DEBUG: self.exportDir = %s" %(self.exportDir)
elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no graph exported'
dia_chooser.destroy()
#TODO: Check to see if file exists
#NOTE: Dangerous - will happily overwrite any file we have write access too
#TODO: This asks for a directory but will take a filename and overwrite it.
self.fig.savefig(self.exportDir, format="png")

View File

@ -20,158 +20,429 @@ 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 get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
def toggleCallback(self, widget, data=None):
# print "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
self.activesite = data
print "DEBUG: activesite set to %s" %(self.activesite)
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
def fillStatsFrame(self, vbox):
# Get currently active site and grab playerid
tmp = self.sql.query['playerStats']
result = self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[self.activesite],))
result = self.cursor.fetchall()
if not result == ():
pid = result[0][0]
pid = result[0][0]
tmp = tmp.replace("<player_test>", "(" + str(pid) + ")")
self.cursor.execute(tmp)
result = self.cursor.fetchall()
cols = 16
rows = len(result)+1 # +1 for title row
self.stats_table = gtk.Table(rows, cols, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
# Create header row
titles = ("Game", "Hands", "VPIP", "PFR", "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq", "PoFAFq", "Net($)", "BB/100", "$/hand", "Variance")
col = 0
row = 0
for t in titles:
l = gtk.Label(titles[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1, yoptions=gtk.SHRINK)
col +=1
for row in range(rows-1):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col in range(cols):
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if result[row][col]:
l = gtk.Label(result[row][col])
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
self.fdb.db.commit()
#end def fillStatsFrame(self, vbox):
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(hbox, site, player)
hbox = gtk.HBox(False, 0)
button = gtk.Button("Refresh")
button.connect("clicked", self.refreshStats, False)
button.show()
hbox.add(button)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
def createPlayerLine(self, hbox, site, player):
if(self.buttongroup == None):
button = gtk.RadioButton(None, site + " id:")
button.set_active(True)
self.buttongroup = button
self.activesite = site
else:
button = gtk.RadioButton(self.buttongroup, site + " id:")
hbox.pack_start(button, True, True, 0)
button.connect("toggled", self.toggleCallback, site)
button.show()
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
def __init__(self, db, config, querylist, debug=True):
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.fdb = fpdb_db.fpdb_db()
self.fdb.do_connect(self.conf)
self.cursor=self.fdb.cursor
self.db = fpdb_db.fpdb_db()
self.db.do_connect(self.conf)
self.cursor=self.db.cursor
self.sql = querylist
self.activesite = None
self.buttongroup = None
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" : True,
"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")
, ("bb100xr", True, "bbxr/100", 1.0, "%4.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.heroes = {}
self.stat_table = None
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()
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame = gtk.Frame()
self.stats_frame.show()
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
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()
dates = self.filters.getDates()
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, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates):
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, dates)
# 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, dates)
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, dates):
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, dates)
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, dates):
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>", '')
# Filter on dates
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
#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()
self.main_hbox.pack_start(playerFrame)
self.main_hbox.pack_start(statsFrame)

View File

@ -23,9 +23,80 @@ import os
import fpdb_import
import fpdb_db
import Filters
import FpdbSQLQueries
class GuiPositionalStats (threading.Thread):
def __init__(self, config, querylist, debug=True):
self.debug=debug
self.conf=config
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())
filters_display = { "Heroes" : True,
"Sites" : True,
"Games" : False,
"Limits" : True,
"LimitSep" : True,
"Seats" : True,
"SeatSep" : True,
"Dates" : True,
"Button1" : True,
"Button2" : False
}
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
self.filters.registerButton1Name("Refresh")
self.filters.registerButton1Callback(self.refreshStats)
self.stat_table = None
self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame.show()
# This could be stored in config eventually, or maybe configured in this window somehow.
# Each posncols element is the name of a column returned by the sql
# query (in lower case) and each posnheads element is the text to use as
# the heading in the GUI. Both sequences should be the same length.
# To miss columns out remove them from both tuples (the 1st 2 elements should always be included).
# To change the heading just edit the second list element as required
# If the first list element does not match a query column that pair is ignored
self.posncols = ( "game", "avgseats", "plposition", "vpip", "pfr", "pf3", "steals"
, "saw_f", "sawsd", "wtsdwsf", "wmsd", "flafq", "tuafq", "rvafq"
, "pofafq", "net", "bbper100", "profitperhand", "variance", "n"
)
self.posnheads = ( "Game", "Seats", "Posn", "VPIP", "PFR", "PF3", "Steals"
, "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq"
, "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds"
)
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(statsFrame)
def get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
@ -35,177 +106,255 @@ class GuiPositionalStats (threading.Thread):
self.activesite = data
print "DEBUG: activesite set to %s" %(self.activesite)
def cardCallback(self, widget, data=None):
print "DEBUG: %s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
def fillStatsFrame(self, vbox):
# Get currently active site and grab playerid
print "DEBUG: attempting to fill stats frame"
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
seats = self.filters.getSeats()
dates = self.filters.getDates()
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, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates):
self.stats_table = gtk.Table(1, 1, False) # gtk table expands as required
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
row = 0
col = 0
for t in self.posnheads:
l = gtk.Label(self.posnheads[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1, yoptions=gtk.SHRINK)
col +=1
tmp = self.sql.query['playerStatsByPosition']
result = self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[self.activesite],))
tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates)
self.cursor.execute(tmp)
result = self.cursor.fetchall()
if not result == ():
pid = result[0][0]
pid = result[0][0]
tmp = tmp.replace("<player_test>", "(" + str(pid) + ")")
self.cursor.execute(tmp)
result = self.cursor.fetchall()
cols = 16
rows = len(result)+1 # +1 for title row
self.stats_table = gtk.Table(rows, cols, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
# Create header row
titles = ("Game", "Position", "#", "VPIP", "PFR", "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq", "PoFAFq", "Net($)", "BB/100", "$/hand", "Variance")
rows = len(result)
colnames = [desc[0].lower() for desc in self.cursor.description]
col = 0
row = 0
for t in titles:
l = gtk.Label(titles[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1, yoptions=gtk.SHRINK)
col +=1
for row in range(rows-1):
if(row%2 == 0):
bgcolor = "white"
last_game,last_seats,sqlrow = "","",0
while sqlrow < rows:
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
rowprinted=0
avgcol = colnames.index('avgseats')
for col,colname in enumerate(self.posncols):
if colname in colnames:
sqlcol = colnames.index(colname)
else:
continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
# print blank row between levels:
if result[sqlrow][sqlcol]:
if sqlrow == 0:
l = gtk.Label(result[sqlrow][sqlcol])
rowprinted=1
elif result[sqlrow][0] != last_game:
l = gtk.Label(' ')
elif 'show' in seats and seats['show'] and result[sqlrow][avgcol] != last_seats:
l = gtk.Label(' ')
else:
bgcolor = "lightgrey"
for col in range(cols):
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if result[row][col]:
l = gtk.Label(result[row][col])
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
self.fdb.db.commit()
l = gtk.Label(result[sqlrow][sqlcol])
rowprinted=1
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
elif col == 1:
l.set_alignment(xalign=0.5, yalign=0.5)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
last_game = result[sqlrow][0]
last_seats = result[sqlrow][avgcol]
if rowprinted:
sqlrow = sqlrow+1
row = row + 1
# show totals at bottom
tmp = self.sql.query['playerStats']
tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates)
self.cursor.execute(tmp)
result = self.cursor.fetchall()
rows = len(result)
colnames = [desc[0].lower() for desc in self.cursor.description]
# blank row between main stats and totals:
col = 0
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(' ')
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
row = row + 1
for sqlrow in range(rows):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col,colname in enumerate(self.posncols):
if colname in colnames:
sqlcol = colnames.index(colname)
elif colname != "plposition":
continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if colname == 'plposition':
l = gtk.Label('Totals')
elif result[sqlrow][sqlcol]:
l = gtk.Label(result[sqlrow][sqlcol])
else:
l = gtk.Label(' ')
if col == 0:
l.set_alignment(xalign=0.0, yalign=0.5)
elif col == 1:
l.set_alignment(xalign=0.5, yalign=0.5)
else:
l.set_alignment(xalign=1.0, yalign=0.5)
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
row = row + 1
self.db.db.rollback()
#end def fillStatsFrame(self, vbox):
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(hbox, site, player)
hbox = gtk.HBox(False, 0)
button = gtk.Button("Refresh")
button.connect("clicked", self.refreshStats, False)
button.show()
hbox.add(button)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
def fillCardsFrame(self, vbox):
hbox1 = gtk.HBox(True,0)
hbox1.show()
vbox.pack_start(hbox1, True, True, 0)
cards = [ "A", "K","Q","J","T","9","8","7","6","5","4","3","2" ]
for j in range(0, len(cards)):
hbox1 = gtk.HBox(True,0)
hbox1.show()
vbox.pack_start(hbox1, True, True, 0)
for i in range(0, len(cards)):
if i < (j + 1):
suit = "o"
else:
suit = "s"
button = gtk.ToggleButton("%s%s%s" %(cards[i], cards[j], suit))
button.connect("toggled", self.cardCallback, "%s%s%s" %(cards[i], cards[j], suit))
hbox1.pack_start(button, True, True, 0)
button.show()
def createPlayerLine(self, hbox, site, player):
if(self.buttongroup == None):
button = gtk.RadioButton(None, site + " id:")
button.set_active(True)
self.buttongroup = button
self.activesite = site
def refineQuery(self, query, playerids, sitenos, limits, seats, dates):
if playerids:
nametest = str(tuple(playerids))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
query = query.replace("<player_test>", nametest)
else:
button = gtk.RadioButton(self.buttongroup, site + " id:")
hbox.pack_start(button, True, True, 0)
button.connect("toggled", self.toggleCallback, site)
button.show()
query = query.replace("<player_test>", "1 = 2")
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.__set_hero_name(pname, site)
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>', ',hc.activeSeats')
query = query.replace('<orderbyseats>', ',stats.AvgSeats')
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>', '')
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
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>", "gt.bigBlind in " + blindtest)
else:
query = query.replace("<gtbigBlind_test>", "gt.bigBlind = -1 ")
def __init__(self, db, config, querylist, debug=True):
self.debug=debug
self.conf=config
groupLevels = "show" not in str(limits)
if groupLevels:
if self.db.backend == self.MYSQL_INNODB:
bigblindselect = """concat('$'
,trim(leading ' ' from
case when min(gt.bigBlind) < 100
then format(min(gt.bigBlind)/100.0, 2)
else format(min(gt.bigBlind)/100.0, 0)
end)
,' - $'
,trim(leading ' ' from
case when max(gt.bigBlind) < 100
then format(max(gt.bigBlind)/100.0, 2)
else format(max(gt.bigBlind)/100.0, 0)
end)
) """
else:
bigblindselect = """'$' ||
trim(leading ' ' from
case when min(gt.bigBlind) < 100
then to_char(min(gt.bigBlind)/100.0,'90D00')
else to_char(min(gt.bigBlind)/100.0,'999990')
end)
|| ' - $' ||
trim(leading ' ' from
case when max(gt.bigBlind) < 100
then to_char(max(gt.bigBlind)/100.0,'90D00')
else to_char(max(gt.bigBlind)/100.0,'999990')
end) """
bigblindselect = "cast('' as char)" # avoid odd effects when some posns and/or seats
# are missing from some limits (dunno why cast is
# needed but it says "unknown type" otherwise?!
query = query.replace("<selectgt.bigBlind>", bigblindselect)
query = query.replace("<groupbygt.bigBlind>", "")
query = query.replace("<hcgametypeId>", "-1")
query = query.replace("<hgameTypeId>", "-1")
else:
if self.db.backend == self.MYSQL_INNODB:
bigblindselect = """concat('$', trim(leading ' ' from
case when gt.bigBlind < 100
then format(gt.bigBlind/100.0, 2)
else format(gt.bigBlind/100.0, 0)
end
) )"""
else:
bigblindselect = """'$' || trim(leading ' ' from
case when gt.bigBlind < 100
then to_char(gt.bigBlind/100.0,'90D00')
else to_char(gt.bigBlind/100.0,'999990')
end
) """
query = query.replace("<selectgt.bigBlind>", bigblindselect)
query = query.replace("<groupbygt.bigBlind>", ",gt.bigBlind")
query = query.replace("<hcgametypeId>", "hc.gametypeId")
query = query.replace("<hgameTypeId>", "h.gameTypeId")
# create new db connection to avoid conflicts with other threads
self.fdb = fpdb_db.fpdb_db()
self.fdb.do_connect(self.conf)
self.cursor=self.fdb.cursor
self.sql = querylist
self.activesite = None
self.buttongroup = None
self.heroes = {}
self.stat_table = None
self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
cardsFrame = gtk.Frame("Cards:")
cardsFrame.set_label_align(0.0, 0.0)
cardsFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillCardsFrame(vbox)
cardsFrame.add(vbox)
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame.show()
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(playerFrame)
self.main_hbox.pack_start(statsFrame)
# Filter on dates
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
#print "query =\n", query
return(query)
#end def refineQuery(self, query, playerids, sitenos, limits):

310
pyfpdb/GuiSessionViewer.py Normal file
View File

@ -0,0 +1,310 @@
#!/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, localtime
from numpy import diff, nonzero
import Card
import fpdb_import
import fpdb_db
import Filters
import FpdbSQLQueries
class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, debug=True):
self.debug=debug
self.conf=config
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.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")
]
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()
self.db.cursor.execute("""select UNIX_TIMESTAMP(handStart) as time, id from Hands ORDER BY time""")
THRESHOLD = 1800
hands = self.db.cursor.fetchall()
times = map(lambda x:long(x[0]), hands)
handids = map(lambda x:int(x[1]), hands)
print "DEBUG: len(times) %s" %(len(times))
diffs = diff(times)
print "DEBUG: len(diffs) %s" %(len(diffs))
index = nonzero(diff(times) > THRESHOLD)
print "DEBUG: len(index[0]) %s" %(len(index[0]))
print "DEBUG: index %s" %(index)
print "DEBUG: index[0][0] %s" %(index[0][0])
total = 0
last_idx = 0
for i in range(len(index[0])):
print "Hands in session %4s: %4s Start: %s End: %s Total: %s" %(i, index[0][i] - last_idx, strftime("%d/%m/%Y %H:%M", localtime(times[last_idx])), strftime("%d/%m/%Y %H:%M", localtime(times[index[0][i]])), times[index[0][i]] - times[last_idx])
total = total + (index[0][i] - last_idx)
last_idx = index[0][i] + 1
print "Total: ", total
#
# 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=False)
# listcols[col].add_attribute(textcell, 'text', col)
# else:
# listcols[col].pack_start(numcell, expand=False)
# listcols[col].add_attribute(numcell, 'text', col)
#
# 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()

View File

@ -76,7 +76,7 @@ class GuiTableViewer (threading.Thread):
arr=[]
#first prepare the header row
if (self.category=="holdem" or self.category=="omahahi" or self.category=="omahahilo"):
tmp=("Name", "HDs", "VPIP", "PFR", "PF3B4B", "ST")
tmp=("Name", "HDs", "VPIP", "PFR", "PF3B", "ST")
tmp+=("FS", "FB")
@ -131,7 +131,7 @@ class GuiTableViewer (threading.Thread):
tmp.append(str(row[6]))#Hands
tmp.append(self.hudDivide(row[7],row[6])) #VPIP
tmp.append(self.hudDivide(row[8],row[6])) #PFR
tmp.append(self.hudDivide(row[10],row[9])+" ("+str(row[9])+")") #PF3B4B
tmp.append(self.hudDivide(row[10],row[9])+" ("+str(row[9])+")") #PF3B
tmp.append(self.hudDivide(row[31],row[30])+" ("+str(row[30])+")") #ST

View File

@ -13,12 +13,12 @@
site_path="C:/Program Files/PokerStars/"
HH_path="C:/Program Files/PokerStars/HandHistory/YOUR SCREEN NAME HERE/"
decoder="pokerstars_decode_table"
converter="PokerStarsToFpdb"
bgcolor="#000000"
fgcolor="#FFFFFF"
hudopacity="1.0"
font="Sans"
font_size="8"
converter="passthrough"
supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout max="8" width="792" height="546" fav_seat="0">
<location seat="1" x="684" y="61"> </location>
@ -74,12 +74,12 @@
site_path="C:/Program Files/Full Tilt Poker/"
HH_path="C:/Program Files/Full Tilt Poker/HandHistory/YOUR SCREEN NAME HERE/"
decoder="fulltilt_decode_table"
converter="FulltiltToFpdb"
bgcolor="#000000"
fgcolor="#FFFFFF"
hudopacity="1.0"
font="Sans"
font_size="8"
converter="passthrough"
supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location>
@ -123,7 +123,7 @@
site_path=""
HH_path=""
decoder="everleaf_decode_table"
converter="EverleafToFpdb"
converter="EverleafToFpdb"
supported_games="holdem">
<layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location>
@ -288,6 +288,12 @@
</aw>
</aux_windows>
<hhcs>
<hhc site="PokerStars" converter="PokerStarsToFpdb"/>
<hhc site="Full Tilt Poker" converter="FulltiltToFpdb"/>
<hhc site="Everleaf" converter="EverleafToFpdb"/>
</hhcs>
<supported_databases>
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD" db_type="fpdb"></database>
</supported_databases>

View File

@ -81,10 +81,11 @@ class HUD_main(object):
def kill_hud(self, event, table):
# called by an event in the HUD, to kill this specific HUD
self.hud_dict[table].kill()
self.hud_dict[table].main_window.destroy()
self.vb.remove(self.hud_dict[table].tablehudlabel)
del(self.hud_dict[table])
if table in self.hud_dict:
self.hud_dict[table].kill()
self.hud_dict[table].main_window.destroy()
self.vb.remove(self.hud_dict[table].tablehudlabel)
del(self.hud_dict[table])
self.main_window.resize(1,1)
def create_HUD(self, new_hand_id, table, table_name, max, poker_game, stat_dict, cards):
@ -194,6 +195,7 @@ class HUD_main(object):
sys.stderr.write("table name "+table_name+" not found, skipping.\n")
else:
self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, stat_dict, cards)
self.db_connection.connection.rollback()
if __name__== "__main__":
sys.stderr.write("HUD_main starting\n")

View File

@ -36,6 +36,7 @@ class Hand:
self.sitename = sitename
self.stats = DerivedStats.DerivedStats(self)
self.gametype = gametype
self.starttime = 0
self.handText = handText
self.handid = 0
self.tablename = "Slartibartfast"
@ -86,10 +87,59 @@ Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object"""
# TODO:
# Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
# HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions
# BoardCards - ?
# BoardCards - Skip - no longer necessary?
# Hands - Summary information of hand indexed by handId - gameinfo
hh['siteHandNo'] = self.handid
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
#
hh['handStart'] = self.starttime
# seats TINYINT NOT NULL,
#
hh['tableName'] = self.tablename
hh['maxSeats'] = self.maxseats
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */
# boardcard2 smallint,
# boardcard3 smallint,
# boardcard4 smallint,
# boardcard5 smallint,
# texture smallint,
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
# Needs to be recorded
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
# Needs to be recorded
# playersAtStreet2 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet3 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtStreet4 SMALLINT NOT NULL,
# Needs to be recorded
# playersAtShowdown SMALLINT NOT NULL,
# Needs to be recorded
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
# Needs to be recorded
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
# Needs to be recorded
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
# Needs to be recorded
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
# Needs to be recorded
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
# Needs to be recorded
# street1Pot INT, /* pot size at flop/street4 */
# Needs to be recorded
# street2Pot INT, /* pot size at turn/street5 */
# Needs to be recorded
# street3Pot INT, /* pot size at river/street6 */
# Needs to be recorded
# street4Pot INT, /* pot size at sd/street7 */
# Needs to be recorded
# showdownPot INT, /* pot size at sd/street7 */
# comment TEXT,
# commentTs DATETIME
handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings?
# Tourneys ?
# TourneysPlayers

View File

@ -112,7 +112,9 @@ class HandHistoryConverter():
def start(self):
"""process a hand at a time from the input specified by in_path.
If in follow mode, wait for more data to turn up.
Otherwise, finish at eof..."""
Otherwise, finish at eof...
"""
starttime = time.time()
if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check"
@ -137,7 +139,11 @@ Otherwise, finish at eof..."""
def tailHands(self):
"""Generator of handTexts from a tailed file:
Tail the in_path file and yield handTexts separated by re_SplitHands"""
Tail the in_path file and yield handTexts separated by re_SplitHands.
This requires a regex that greedily groups and matches the 'splitter' between hands,
which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
"""
if self.in_path == '-': raise StopIteration
interval = 1.0 # seconds to sleep between reads for new data
fd = codecs.open(self.in_path,'r', self.codepage)
@ -161,7 +167,7 @@ Tail the in_path file and yield handTexts separated by re_SplitHands"""
else:
# yield hands
data = data + newdata
result = self.re_SplitHands.split(data)
result = self.re_TailSplitHands.split(data)
result = iter(result)
data = ''
# --x data (- is bit of splitter, x is paragraph) yield,...,keep

View File

@ -72,6 +72,7 @@ class OnGame(HandHistoryConverter):
HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init.
self.sitename = "OnGame"
self.setFileType("text", "cp1252")
self.siteId = 5 # Needs to match id entry in Sites database
#self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n+')

View File

@ -27,7 +27,8 @@ class PokerStars(HandHistoryConverter):
# Static regexes
re_GameInfo = re.compile("PokerStars Game #(?P<HID>[0-9]+):\s+(HORSE)? \(?(?P<GAME>Hold\'em|Razz|7 Card Stud|Omaha|Omaha Hi/Lo|Badugi) (?P<LIMIT>No Limit|Limit|Pot Limit),? \(?(?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\) - (?P<DATETIME>.*$)", re.MULTILINE)
re_SplitHands = re.compile('(\n\n+)')
re_SplitHands = re.compile('\n\n+')
re_TailSplitHands = re.compile('(\n\n\n+)')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
@ -43,6 +44,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising PokerStars converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 2 # Needs to match id entry in Sites database
if autostart:
self.start()
@ -72,7 +74,7 @@ follow : whether to tail -f the input"""
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["ring", "stud", "fl"],
["ring", "draw", "fl"],
#["ring", "draw", "fl"],
["ring", "omaha", "pl"]
]

View File

@ -29,7 +29,7 @@ Set up all of the SQL statements for a given game and database type.
class Sql:
def __init__(self, game = 'holdem', type = 'PT3'):
def __init__(self, game = 'holdem', type = 'PT3', db_server = 'mysql'):
self.query = {}
############################################################################
@ -172,149 +172,329 @@ class Sql:
"""
self.query['get_stats_from_hand'] = """
SELECT HudCache.playerId AS player_id,
seatNo AS seat,
name AS screen_name,
sum(HDs) AS n,
sum(street0VPI) AS vpip,
sum(street0Aggr) AS pfr,
sum(street0_3B4BChance) AS TB_opp_0,
sum(street0_3B4BDone) AS TB_0,
sum(street1Seen) AS saw_f,
sum(street1Seen) AS saw_1,
sum(street2Seen) AS saw_2,
sum(street3Seen) AS saw_3,
sum(street4Seen) AS saw_4,
sum(sawShowdown) AS sd,
sum(street1Aggr) AS aggr_1,
sum(street2Aggr) AS aggr_2,
sum(street3Aggr) AS aggr_3,
sum(street4Aggr) AS aggr_4,
sum(otherRaisedStreet1) AS was_raised_1,
sum(otherRaisedStreet2) AS was_raised_2,
sum(otherRaisedStreet3) AS was_raised_3,
sum(otherRaisedStreet4) AS was_raised_4,
sum(foldToOtherRaisedStreet1) AS f_freq_1,
sum(foldToOtherRaisedStreet2) AS f_freq_2,
sum(foldToOtherRaisedStreet3) AS f_freq_3,
sum(foldToOtherRaisedStreet4) AS f_freq_4,
sum(wonWhenSeenStreet1) AS w_w_s_1,
sum(wonAtSD) AS wmsd,
sum(stealAttemptChance) AS steal_opp,
sum(stealAttempted) AS steal,
sum(foldSbToStealChance) AS SBstolen,
sum(foldedSbToSteal) AS SBnotDef,
sum(foldBbToStealChance) AS BBstolen,
sum(foldedBbToSteal) AS BBnotDef,
sum(street1CBChance) AS CB_opp_1,
sum(street1CBDone) AS CB_1,
sum(street2CBChance) AS CB_opp_2,
sum(street2CBDone) AS CB_2,
sum(street3CBChance) AS CB_opp_3,
sum(street3CBDone) AS CB_3,
sum(street4CBChance) AS CB_opp_4,
sum(street4CBDone) AS CB_4,
sum(foldToStreet1CBChance) AS f_cb_opp_1,
sum(foldToStreet1CBDone) AS f_cb_1,
sum(foldToStreet2CBChance) AS f_cb_opp_2,
sum(foldToStreet2CBDone) AS f_cb_2,
sum(foldToStreet3CBChance) AS f_cb_opp_3,
sum(foldToStreet3CBDone) AS f_cb_3,
sum(foldToStreet4CBChance) AS f_cb_opp_4,
sum(foldToStreet4CBDone) AS f_cb_4,
sum(totalProfit) AS net,
sum(street1CheckCallRaiseChance) AS ccr_opp_1,
sum(street1CheckCallRaiseDone) AS ccr_1,
sum(street2CheckCallRaiseChance) AS ccr_opp_2,
sum(street2CheckCallRaiseDone) AS ccr_2,
sum(street3CheckCallRaiseChance) AS ccr_opp_3,
sum(street3CheckCallRaiseDone) AS ccr_3,
sum(street4CheckCallRaiseChance) AS ccr_opp_4,
sum(street4CheckCallRaiseDone) AS ccr_4
FROM Hands
INNER JOIN HandsPlayers ON (HandsPlayers.handId = %s)
INNER JOIN HudCache ON ( HudCache.PlayerId = HandsPlayers.PlayerId+0
AND HudCache.gametypeId+0 = Hands.gametypeId+0)
INNER JOIN Players ON (Players.id = HandsPlayers.PlayerId+0)
WHERE Hands.id = %s
GROUP BY HudCache.PlayerId, Players.name, seatNo
SELECT hc.playerId AS player_id,
hp.seatNo AS seat,
p.name AS screen_name,
sum(hc.HDs) AS n,
sum(hc.street0VPI) AS vpip,
sum(hc.street0Aggr) AS pfr,
sum(hc.street0_3BChance) AS TB_opp_0,
sum(hc.street0_3BDone) AS TB_0,
sum(hc.street1Seen) AS saw_f,
sum(hc.street1Seen) AS saw_1,
sum(hc.street2Seen) AS saw_2,
sum(hc.street3Seen) AS saw_3,
sum(hc.street4Seen) AS saw_4,
sum(hc.sawShowdown) AS sd,
sum(hc.street1Aggr) AS aggr_1,
sum(hc.street2Aggr) AS aggr_2,
sum(hc.street3Aggr) AS aggr_3,
sum(hc.street4Aggr) AS aggr_4,
sum(hc.otherRaisedStreet1) AS was_raised_1,
sum(hc.otherRaisedStreet2) AS was_raised_2,
sum(hc.otherRaisedStreet3) AS was_raised_3,
sum(hc.otherRaisedStreet4) AS was_raised_4,
sum(hc.foldToOtherRaisedStreet1) AS f_freq_1,
sum(hc.foldToOtherRaisedStreet2) AS f_freq_2,
sum(hc.foldToOtherRaisedStreet3) AS f_freq_3,
sum(hc.foldToOtherRaisedStreet4) AS f_freq_4,
sum(hc.wonWhenSeenStreet1) AS w_w_s_1,
sum(hc.wonAtSD) AS wmsd,
sum(hc.stealAttemptChance) AS steal_opp,
sum(hc.stealAttempted) AS steal,
sum(hc.foldSbToStealChance) AS SBstolen,
sum(hc.foldedSbToSteal) AS SBnotDef,
sum(hc.foldBbToStealChance) AS BBstolen,
sum(hc.foldedBbToSteal) AS BBnotDef,
sum(hc.street1CBChance) AS CB_opp_1,
sum(hc.street1CBDone) AS CB_1,
sum(hc.street2CBChance) AS CB_opp_2,
sum(hc.street2CBDone) AS CB_2,
sum(hc.street3CBChance) AS CB_opp_3,
sum(hc.street3CBDone) AS CB_3,
sum(hc.street4CBChance) AS CB_opp_4,
sum(hc.street4CBDone) AS CB_4,
sum(hc.foldToStreet1CBChance) AS f_cb_opp_1,
sum(hc.foldToStreet1CBDone) AS f_cb_1,
sum(hc.foldToStreet2CBChance) AS f_cb_opp_2,
sum(hc.foldToStreet2CBDone) AS f_cb_2,
sum(hc.foldToStreet3CBChance) AS f_cb_opp_3,
sum(hc.foldToStreet3CBDone) AS f_cb_3,
sum(hc.foldToStreet4CBChance) AS f_cb_opp_4,
sum(hc.foldToStreet4CBDone) AS f_cb_4,
sum(hc.totalProfit) AS net,
sum(hc.street1CheckCallRaiseChance) AS ccr_opp_1,
sum(hc.street1CheckCallRaiseDone) AS ccr_1,
sum(hc.street2CheckCallRaiseChance) AS ccr_opp_2,
sum(hc.street2CheckCallRaiseDone) AS ccr_2,
sum(hc.street3CheckCallRaiseChance) AS ccr_opp_3,
sum(hc.street3CheckCallRaiseDone) AS ccr_3,
sum(hc.street4CheckCallRaiseChance) AS ccr_opp_4,
sum(hc.street4CheckCallRaiseDone) AS ccr_4
FROM Hands h
INNER JOIN HandsPlayers hp ON (hp.handId = %s)
INNER JOIN HudCache hc ON ( hc.PlayerId = hp.PlayerId+0
AND hc.gametypeId+0 = h.gametypeId+0)
INNER JOIN Players p ON (p.id = hp.PlayerId+0)
WHERE h.id = %s
AND hc.styleKey > %s
/* styleKey is currently 'd' (for date) followed by a yyyymmdd
date key. Set it to 0000000 or similar to get all records */
/* also check activeseats here? even if only 3 groups eg 2-3/4-6/7+ ??
e.g. could use a multiplier:
AND h.seats > X / 1.25 and hp.seats < X * 1.25
where X is the number of active players at the current table (and
1.25 would be a config value so user could change it)
*/
GROUP BY hc.PlayerId, hp.seatNo, p.name
"""
# same as above except stats are aggregated for all blind/limit levels
self.query['get_stats_from_hand_aggregated'] = """
SELECT HudCache.playerId AS player_id,
sum(HDs) AS n,
sum(street0VPI) AS vpip,
sum(street0Aggr) AS pfr,
sum(street0_3B4BChance) AS TB_opp_0,
sum(street0_3B4BDone) AS TB_0,
sum(street1Seen) AS saw_f,
sum(street1Seen) AS saw_1,
sum(street2Seen) AS saw_2,
sum(street3Seen) AS saw_3,
sum(street4Seen) AS saw_4,
sum(sawShowdown) AS sd,
sum(street1Aggr) AS aggr_1,
sum(street2Aggr) AS aggr_2,
sum(street3Aggr) AS aggr_3,
sum(street4Aggr) AS aggr_4,
sum(otherRaisedStreet1) AS was_raised_1,
sum(otherRaisedStreet2) AS was_raised_2,
sum(otherRaisedStreet3) AS was_raised_3,
sum(otherRaisedStreet4) AS was_raised_4,
sum(foldToOtherRaisedStreet1) AS f_freq_1,
sum(foldToOtherRaisedStreet2) AS f_freq_2,
sum(foldToOtherRaisedStreet3) AS f_freq_3,
sum(foldToOtherRaisedStreet4) AS f_freq_4,
sum(wonWhenSeenStreet1) AS w_w_s_1,
sum(wonAtSD) AS wmsd,
sum(stealAttemptChance) AS steal_opp,
sum(stealAttempted) AS steal,
sum(foldSbToStealChance) AS SBstolen,
sum(foldedSbToSteal) AS SBnotDef,
sum(foldBbToStealChance) AS BBstolen,
sum(foldedBbToSteal) AS BBnotDef,
sum(street1CBChance) AS CB_opp_1,
sum(street1CBDone) AS CB_1,
sum(street2CBChance) AS CB_opp_2,
sum(street2CBDone) AS CB_2,
sum(street3CBChance) AS CB_opp_3,
sum(street3CBDone) AS CB_3,
sum(street4CBChance) AS CB_opp_4,
sum(street4CBDone) AS CB_4,
sum(foldToStreet1CBChance) AS f_cb_opp_1,
sum(foldToStreet1CBDone) AS f_cb_1,
sum(foldToStreet2CBChance) AS f_cb_opp_2,
sum(foldToStreet2CBDone) AS f_cb_2,
sum(foldToStreet3CBChance) AS f_cb_opp_3,
sum(foldToStreet3CBDone) AS f_cb_3,
sum(foldToStreet4CBChance) AS f_cb_opp_4,
sum(foldToStreet4CBDone) AS f_cb_4,
sum(totalProfit) AS net,
sum(street1CheckCallRaiseChance) AS ccr_opp_1,
sum(street1CheckCallRaiseDone) AS ccr_1,
sum(street2CheckCallRaiseChance) AS ccr_opp_2,
sum(street2CheckCallRaiseDone) AS ccr_2,
sum(street3CheckCallRaiseChance) AS ccr_opp_3,
sum(street3CheckCallRaiseDone) AS ccr_3,
sum(street4CheckCallRaiseChance) AS ccr_opp_4,
sum(street4CheckCallRaiseDone) AS ccr_4
FROM HudCache, Hands
WHERE HudCache.PlayerId in
(SELECT PlayerId FROM HandsPlayers
WHERE handId = %s)
AND Hands.id = %s
AND HudCache.gametypeId in
(SELECT gt1.id from Gametypes gt1, Gametypes gt2, Hands
WHERE gt1.siteid = gt2.siteid
AND gt1.type = gt2.type
AND gt1.category = gt2.category
AND gt1.limittype = gt2.limittype
AND gt2.id = Hands.gametypeId
AND Hands.id = %s)
GROUP BY HudCache.PlayerId, Players.name, seatNo
SELECT hc.playerId AS player_id,
max(case when hc.gametypeId = h.gametypeId
then hp.seatNo
else -1
end) AS seat,
p.name AS screen_name,
sum(hc.HDs) AS n,
sum(hc.street0VPI) AS vpip,
sum(hc.street0Aggr) AS pfr,
sum(hc.street0_3BChance) AS TB_opp_0,
sum(hc.street0_3BDone) AS TB_0,
sum(hc.street1Seen) AS saw_f,
sum(hc.street1Seen) AS saw_1,
sum(hc.street2Seen) AS saw_2,
sum(hc.street3Seen) AS saw_3,
sum(hc.street4Seen) AS saw_4,
sum(hc.sawShowdown) AS sd,
sum(hc.street1Aggr) AS aggr_1,
sum(hc.street2Aggr) AS aggr_2,
sum(hc.street3Aggr) AS aggr_3,
sum(hc.street4Aggr) AS aggr_4,
sum(hc.otherRaisedStreet1) AS was_raised_1,
sum(hc.otherRaisedStreet2) AS was_raised_2,
sum(hc.otherRaisedStreet3) AS was_raised_3,
sum(hc.otherRaisedStreet4) AS was_raised_4,
sum(hc.foldToOtherRaisedStreet1) AS f_freq_1,
sum(hc.foldToOtherRaisedStreet2) AS f_freq_2,
sum(hc.foldToOtherRaisedStreet3) AS f_freq_3,
sum(hc.foldToOtherRaisedStreet4) AS f_freq_4,
sum(hc.wonWhenSeenStreet1) AS w_w_s_1,
sum(hc.wonAtSD) AS wmsd,
sum(hc.stealAttemptChance) AS steal_opp,
sum(hc.stealAttempted) AS steal,
sum(hc.foldSbToStealChance) AS SBstolen,
sum(hc.foldedSbToSteal) AS SBnotDef,
sum(hc.foldBbToStealChance) AS BBstolen,
sum(hc.foldedBbToSteal) AS BBnotDef,
sum(hc.street1CBChance) AS CB_opp_1,
sum(hc.street1CBDone) AS CB_1,
sum(hc.street2CBChance) AS CB_opp_2,
sum(hc.street2CBDone) AS CB_2,
sum(hc.street3CBChance) AS CB_opp_3,
sum(hc.street3CBDone) AS CB_3,
sum(hc.street4CBChance) AS CB_opp_4,
sum(hc.street4CBDone) AS CB_4,
sum(hc.foldToStreet1CBChance) AS f_cb_opp_1,
sum(hc.foldToStreet1CBDone) AS f_cb_1,
sum(hc.foldToStreet2CBChance) AS f_cb_opp_2,
sum(hc.foldToStreet2CBDone) AS f_cb_2,
sum(hc.foldToStreet3CBChance) AS f_cb_opp_3,
sum(hc.foldToStreet3CBDone) AS f_cb_3,
sum(hc.foldToStreet4CBChance) AS f_cb_opp_4,
sum(hc.foldToStreet4CBDone) AS f_cb_4,
sum(hc.totalProfit) AS net,
sum(hc.street1CheckCallRaiseChance) AS ccr_opp_1,
sum(hc.street1CheckCallRaiseDone) AS ccr_1,
sum(hc.street2CheckCallRaiseChance) AS ccr_opp_2,
sum(hc.street2CheckCallRaiseDone) AS ccr_2,
sum(hc.street3CheckCallRaiseChance) AS ccr_opp_3,
sum(hc.street3CheckCallRaiseDone) AS ccr_3,
sum(hc.street4CheckCallRaiseChance) AS ccr_opp_4,
sum(hc.street4CheckCallRaiseDone) AS ccr_4
FROM Hands h
INNER JOIN HandsPlayers hp ON (hp.handId = %s)
INNER JOIN HudCache hc ON (hc.playerId = hp.playerId)
INNER JOIN Players p ON (p.id = hc.playerId)
WHERE h.id = %s
AND hc.styleKey > %s
/* styleKey is currently 'd' (for date) followed by a yyyymmdd
date key. Set it to 0000000 or similar to get all records */
/* also check activeseats here? even if only 3 groups eg 2-3/4-6/7+ ??
e.g. could use a multiplier:
AND h.seats > %s / 1.25 and hp.seats < %s * 1.25
where %s is the number of active players at the current table (and
1.25 would be a config value so user could change it)
*/
AND hc.gametypeId+0 in
(SELECT gt1.id from Gametypes gt1, Gametypes gt2
WHERE gt1.siteid = gt2.siteid
AND gt1.type = gt2.type
AND gt1.category = gt2.category
AND gt1.limittype = gt2.limittype
AND gt2.id = h.gametypeId)
GROUP BY hc.PlayerId, p.name, hc.styleKey
"""
if db_server == 'mysql':
self.query['get_stats_from_hand_session'] = """
SELECT hp.playerId AS player_id,
hp.handId AS hand_id,
hp.seatNo AS seat,
p.name AS screen_name,
h.seats AS seats,
1 AS n,
cast(hp2.street0VPI as <signed>integer) AS vpip,
cast(hp2.street0Aggr as <signed>integer) AS pfr,
cast(hp2.street0_3BChance as <signed>integer) AS TB_opp_0,
cast(hp2.street0_3BDone as <signed>integer) AS TB_0,
cast(hp2.street1Seen as <signed>integer) AS saw_f,
cast(hp2.street1Seen as <signed>integer) AS saw_1,
cast(hp2.street2Seen as <signed>integer) AS saw_2,
cast(hp2.street3Seen as <signed>integer) AS saw_3,
cast(hp2.street4Seen as <signed>integer) AS saw_4,
cast(hp2.sawShowdown as <signed>integer) AS sd,
cast(hp2.street1Aggr as <signed>integer) AS aggr_1,
cast(hp2.street2Aggr as <signed>integer) AS aggr_2,
cast(hp2.street3Aggr as <signed>integer) AS aggr_3,
cast(hp2.street4Aggr as <signed>integer) AS aggr_4,
cast(hp2.otherRaisedStreet1 as <signed>integer) AS was_raised_1,
cast(hp2.otherRaisedStreet2 as <signed>integer) AS was_raised_2,
cast(hp2.otherRaisedStreet3 as <signed>integer) AS was_raised_3,
cast(hp2.otherRaisedStreet4 as <signed>integer) AS was_raised_4,
cast(hp2.foldToOtherRaisedStreet1 as <signed>integer) AS f_freq_1,
cast(hp2.foldToOtherRaisedStreet2 as <signed>integer) AS f_freq_2,
cast(hp2.foldToOtherRaisedStreet3 as <signed>integer) AS f_freq_3,
cast(hp2.foldToOtherRaisedStreet4 as <signed>integer) AS f_freq_4,
cast(hp2.wonWhenSeenStreet1 as <signed>integer) AS w_w_s_1,
cast(hp2.wonAtSD as <signed>integer) AS wmsd,
cast(hp2.stealAttemptChance as <signed>integer) AS steal_opp,
cast(hp2.stealAttempted as <signed>integer) AS steal,
cast(hp2.foldSbToStealChance as <signed>integer) AS SBstolen,
cast(hp2.foldedSbToSteal as <signed>integer) AS SBnotDef,
cast(hp2.foldBbToStealChance as <signed>integer) AS BBstolen,
cast(hp2.foldedBbToSteal as <signed>integer) AS BBnotDef,
cast(hp2.street1CBChance as <signed>integer) AS CB_opp_1,
cast(hp2.street1CBDone as <signed>integer) AS CB_1,
cast(hp2.street2CBChance as <signed>integer) AS CB_opp_2,
cast(hp2.street2CBDone as <signed>integer) AS CB_2,
cast(hp2.street3CBChance as <signed>integer) AS CB_opp_3,
cast(hp2.street3CBDone as <signed>integer) AS CB_3,
cast(hp2.street4CBChance as <signed>integer) AS CB_opp_4,
cast(hp2.street4CBDone as <signed>integer) AS CB_4,
cast(hp2.foldToStreet1CBChance as <signed>integer) AS f_cb_opp_1,
cast(hp2.foldToStreet1CBDone as <signed>integer) AS f_cb_1,
cast(hp2.foldToStreet2CBChance as <signed>integer) AS f_cb_opp_2,
cast(hp2.foldToStreet2CBDone as <signed>integer) AS f_cb_2,
cast(hp2.foldToStreet3CBChance as <signed>integer) AS f_cb_opp_3,
cast(hp2.foldToStreet3CBDone as <signed>integer) AS f_cb_3,
cast(hp2.foldToStreet4CBChance as <signed>integer) AS f_cb_opp_4,
cast(hp2.foldToStreet4CBDone as <signed>integer) AS f_cb_4,
cast(hp2.totalProfit as <signed>integer) AS net,
cast(hp2.street1CheckCallRaiseChance as <signed>integer) AS ccr_opp_1,
cast(hp2.street1CheckCallRaiseDone as <signed>integer) AS ccr_1,
cast(hp2.street2CheckCallRaiseChance as <signed>integer) AS ccr_opp_2,
cast(hp2.street2CheckCallRaiseDone as <signed>integer) AS ccr_2,
cast(hp2.street3CheckCallRaiseChance as <signed>integer) AS ccr_opp_3,
cast(hp2.street3CheckCallRaiseDone as <signed>integer) AS ccr_3,
cast(hp2.street4CheckCallRaiseChance as <signed>integer) AS ccr_opp_4,
cast(hp2.street4CheckCallRaiseDone as <signed>integer) AS ccr_4
FROM
Hands h /* players in this hand */
INNER JOIN Hands h2 ON (h2.id > %s AND h2.tableName = h.tableName)
INNER JOIN HandsPlayers hp ON (h.id = hp.handId)
INNER JOIN HandsPlayers hp2 ON (hp2.playerId+0 = hp.playerId+0 AND (hp2.handId = h2.id+0)) /* other hands by these players */
INNER JOIN Players p ON (p.id = hp2.PlayerId+0)
WHERE hp.handId = %s
/* check activeseats once this data returned? (don't want to do that here as it might
assume a session ended just because the number of seats dipped for a few hands)
*/
ORDER BY h.handStart desc, hp2.PlayerId
/* order rows by handstart descending so that we can stop reading rows when
there's a gap over X minutes between hands (ie. when we get back to start of
the session */
"""
else: # assume postgresql
self.query['get_stats_from_hand_session'] = """
SELECT hp.playerId AS player_id,
hp.handId AS hand_id,
hp.seatNo AS seat,
p.name AS screen_name,
h.seats AS seats,
1 AS n,
cast(hp2.street0VPI as <signed>integer) AS vpip,
cast(hp2.street0Aggr as <signed>integer) AS pfr,
cast(hp2.street0_3BChance as <signed>integer) AS TB_opp_0,
cast(hp2.street0_3BDone as <signed>integer) AS TB_0,
cast(hp2.street1Seen as <signed>integer) AS saw_f,
cast(hp2.street1Seen as <signed>integer) AS saw_1,
cast(hp2.street2Seen as <signed>integer) AS saw_2,
cast(hp2.street3Seen as <signed>integer) AS saw_3,
cast(hp2.street4Seen as <signed>integer) AS saw_4,
cast(hp2.sawShowdown as <signed>integer) AS sd,
cast(hp2.street1Aggr as <signed>integer) AS aggr_1,
cast(hp2.street2Aggr as <signed>integer) AS aggr_2,
cast(hp2.street3Aggr as <signed>integer) AS aggr_3,
cast(hp2.street4Aggr as <signed>integer) AS aggr_4,
cast(hp2.otherRaisedStreet1 as <signed>integer) AS was_raised_1,
cast(hp2.otherRaisedStreet2 as <signed>integer) AS was_raised_2,
cast(hp2.otherRaisedStreet3 as <signed>integer) AS was_raised_3,
cast(hp2.otherRaisedStreet4 as <signed>integer) AS was_raised_4,
cast(hp2.foldToOtherRaisedStreet1 as <signed>integer) AS f_freq_1,
cast(hp2.foldToOtherRaisedStreet2 as <signed>integer) AS f_freq_2,
cast(hp2.foldToOtherRaisedStreet3 as <signed>integer) AS f_freq_3,
cast(hp2.foldToOtherRaisedStreet4 as <signed>integer) AS f_freq_4,
cast(hp2.wonWhenSeenStreet1 as <signed>integer) AS w_w_s_1,
cast(hp2.wonAtSD as <signed>integer) AS wmsd,
cast(hp2.stealAttemptChance as <signed>integer) AS steal_opp,
cast(hp2.stealAttempted as <signed>integer) AS steal,
cast(hp2.foldSbToStealChance as <signed>integer) AS SBstolen,
cast(hp2.foldedSbToSteal as <signed>integer) AS SBnotDef,
cast(hp2.foldBbToStealChance as <signed>integer) AS BBstolen,
cast(hp2.foldedBbToSteal as <signed>integer) AS BBnotDef,
cast(hp2.street1CBChance as <signed>integer) AS CB_opp_1,
cast(hp2.street1CBDone as <signed>integer) AS CB_1,
cast(hp2.street2CBChance as <signed>integer) AS CB_opp_2,
cast(hp2.street2CBDone as <signed>integer) AS CB_2,
cast(hp2.street3CBChance as <signed>integer) AS CB_opp_3,
cast(hp2.street3CBDone as <signed>integer) AS CB_3,
cast(hp2.street4CBChance as <signed>integer) AS CB_opp_4,
cast(hp2.street4CBDone as <signed>integer) AS CB_4,
cast(hp2.foldToStreet1CBChance as <signed>integer) AS f_cb_opp_1,
cast(hp2.foldToStreet1CBDone as <signed>integer) AS f_cb_1,
cast(hp2.foldToStreet2CBChance as <signed>integer) AS f_cb_opp_2,
cast(hp2.foldToStreet2CBDone as <signed>integer) AS f_cb_2,
cast(hp2.foldToStreet3CBChance as <signed>integer) AS f_cb_opp_3,
cast(hp2.foldToStreet3CBDone as <signed>integer) AS f_cb_3,
cast(hp2.foldToStreet4CBChance as <signed>integer) AS f_cb_opp_4,
cast(hp2.foldToStreet4CBDone as <signed>integer) AS f_cb_4,
cast(hp2.totalProfit as <signed>integer) AS net,
cast(hp2.street1CheckCallRaiseChance as <signed>integer) AS ccr_opp_1,
cast(hp2.street1CheckCallRaiseDone as <signed>integer) AS ccr_1,
cast(hp2.street2CheckCallRaiseChance as <signed>integer) AS ccr_opp_2,
cast(hp2.street2CheckCallRaiseDone as <signed>integer) AS ccr_2,
cast(hp2.street3CheckCallRaiseChance as <signed>integer) AS ccr_opp_3,
cast(hp2.street3CheckCallRaiseDone as <signed>integer) AS ccr_3,
cast(hp2.street4CheckCallRaiseChance as <signed>integer) AS ccr_opp_4,
cast(hp2.street4CheckCallRaiseDone as <signed>integer) AS ccr_4
FROM Hands h /* this hand */
INNER JOIN Hands h2 ON ( h2.id > %s /* other hands */
AND h2.tableName = h.tableName)
INNER JOIN HandsPlayers hp ON (h.id = hp.handId) /* players in this hand */
INNER JOIN HandsPlayers hp2 ON ( hp2.playerId+0 = hp.playerId+0
AND hp2.handId = h2.id) /* other hands by these players */
INNER JOIN Players p ON (p.id = hp2.PlayerId+0)
WHERE h.id = %s
/* check activeseats once this data returned? (don't want to do that here as it might
assume a session ended just because the number of seats dipped for a few hands)
*/
ORDER BY h.handStart desc, hp2.PlayerId
/* order rows by handstart descending so that we can stop reading rows when
there's a gap over X minutes between hands (ie. when we get back to start of
the session */
"""
self.query['get_players_from_hand'] = """
SELECT HandsPlayers.playerId, seatNo, name
FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id)
@ -349,20 +529,13 @@ class Sql:
select
seatNo AS seat_number,
name AS screen_name,
card1Value AS card1value,
card1Suit AS card1suit,
card2Value AS card2value,
card2Suit AS card2suit,
card3Value AS card3value,
card3Suit AS card3suit,
card4Value AS card4value,
card4Suit AS card4suit,
card5Value AS card5value,
card5Suit AS card5suit,
card6Value AS card6value,
card6Suit AS card6suit,
card7Value AS card7value,
card7Suit AS card7suit
card1, /*card1Value, card1Suit, */
card2, /*card2Value, card2Suit, */
card3, /*card3Value, card3Suit, */
card4, /*card4Value, card4Suit, */
card5, /*card5Value, card5Suit, */
card6, /*card6Value, card6Suit, */
card7 /*card7Value, card7Suit */
from HandsPlayers, Players
where handID = %s and HandsPlayers.playerId = Players.id
order by seatNo
@ -389,9 +562,21 @@ class Sql:
FROM Players, HandsActions, HandsPlayers
WHERE HandsPlayers.handid = %s
AND HandsPlayers.playerid = Players.id
AND HandsActions.handPlayerId = HandsPlayers.id
AND HandsActions.handsPlayerId = HandsPlayers.id
ORDER BY street, actionno
"""
if db_server == 'mysql':
self.query['get_hand_1day_ago'] = """
select coalesce(max(id),0)
from Hands
where handStart < date_sub(utc_timestamp(), interval '1' day)"""
else: # assume postgresql
self.query['get_hand_1day_ago'] = """
select coalesce(max(id),0)
from Hands
where handStart < now() at time zone 'UTC' - interval '1 day'"""
if __name__== "__main__":
# just print the default queries and exit
s = Sql(game = 'razz', type = 'ptracks')

View File

@ -32,6 +32,8 @@
# float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']). You can see how the
# keys of stat_dict relate to the column names in HudCache by inspecting
# the proper section of the SQL.py module.
# The stat_dict keys should be in lower case, i.e. vpip not VPIP, since
# postgres returns the column names in lower case.
# 3 You have to write a small function for each stat you want to add. See
# the vpip() function for example. This function has to be protected from
# exceptions, using something like the try:/except: paragraphs in vpip.
@ -189,6 +191,27 @@ def wtsd(stat_dict, player):
'% went to showdown'
)
def wtsd_0(stat_dict, player):
""" Went to SD when saw flop/4th."""
stat = 0.0
try:
stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wtsd=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']),
'% went to showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wtsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% went to showdown'
)
def wmsd(stat_dict, player):
""" Won $ at showdown."""
stat = 0.0
@ -210,6 +233,27 @@ def wmsd(stat_dict, player):
'% won money at showdown'
)
def wmsd_0(stat_dict, player):
""" Won $ at showdown."""
stat = 0.0
try:
stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wmsd=%2.0f' % (100*stat) + '%',
'(%5.1f/%d)' % (float(stat_dict[player]['wmsd']), stat_dict[player]['sd']),
'% won money at showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wmsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% won money at showdown'
)
def profit100_0(stat_dict, player):
""" Profit won per 100 hands (no decimal places)."""
stat = 0.0
@ -312,16 +356,37 @@ def steal(stat_dict, player):
except:
return (stat, 'NA', 'st=NA', 'steal=NA', '(0/0)', '% steal attempted')
def steal_0(stat_dict, player):
""" Steal %."""
stat = 0.0
try:
stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp'])
return (stat,
'%2.0f' % (100*stat) + '%',
'st=%2.0f' % (100*stat) + '%',
'steal=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']),
'% steal attempted'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'st=%2.0f' % (0) + '%',
'steal=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% steal attempted'
)
def f_SB_steal(stat_dict, player):
""" Folded SB to steal."""
stat = 0.0
try:
stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen'])
stat = float(stat_dict[player]['sbnotdef'])/float(stat_dict[player]['sbstolen'])
return (stat,
'%3.1f' % (100*stat) + '%',
'fSB=%3.1f' % (100*stat) + '%',
'fSB_s=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['SBnotDef'], stat_dict[player]['SBstolen']),
'(%d/%d)' % (stat_dict[player]['sbnotdef'], stat_dict[player]['sbstolen']),
'% folded SB to steal'
)
except:
@ -336,12 +401,32 @@ def f_BB_steal(stat_dict, player):
""" Folded BB to steal."""
stat = 0.0
try:
stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen'])
stat = float(stat_dict[player]['bbnotdef'])/float(stat_dict[player]['bbstolen'])
return (stat,
'%3.1f' % (100*stat) + '%',
'fBB=%3.1f' % (100*stat) + '%',
'fBB_s=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['BBnotDef'], stat_dict[player]['BBstolen']),
'(%d/%d)' % (stat_dict[player]['bbnotdef'], stat_dict[player]['bbstolen']),
'% folded BB to steal'
)
except:
return (stat,
'NA',
'fBB=NA',
'fBB_s=NA',
'(0/0)',
'% folded BB to steal')
def f_BB_steal_0(stat_dict, player):
""" Folded BB to steal."""
stat = 0.0
try:
stat = float(stat_dict[player]['bbnotdef'])/float(stat_dict[player]['bbstolen'])
return (stat,
'%2.0f' % (100*stat) + '%',
'fBB=%2.0f' % (100*stat) + '%',
'fBB_s=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['bbnotdef'], stat_dict[player]['bbstolen']),
'% folded BB to steal'
)
except:
@ -356,12 +441,12 @@ def three_B_0(stat_dict, player):
""" Three bet preflop/3rd."""
stat = 0.0
try:
stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0'])
stat = float(stat_dict[player]['tb_0'])/float(stat_dict[player]['tb_opp_0'])
return (stat,
'%3.1f' % (100*stat) + '%',
'3B=%3.1f' % (100*stat) + '%',
'3B_pf=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['TB_0'], stat_dict[player]['TB_opp_0']),
'(%d/%d)' % (stat_dict[player]['tb_0'], stat_dict[player]['tb_opp_0']),
'% 3/4 Bet preflop/3rd'
)
except:
@ -537,12 +622,12 @@ def cb_1(stat_dict, player):
""" Flop continuation bet."""
stat = 0.0
try:
stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1'])
stat = float(stat_dict[player]['cb_1'])/float(stat_dict[player]['cb_opp_1'])
return (stat,
'%3.1f' % (100*stat) + '%',
'cb1=%3.1f' % (100*stat) + '%',
'cb_1=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['CB_1'], stat_dict[player]['CB_opp_1']),
'(%d/%d)' % (stat_dict[player]['cb_1'], stat_dict[player]['cb_opp_1']),
'% continuation bet flop/4th'
)
except:
@ -558,12 +643,12 @@ def cb_2(stat_dict, player):
""" Turn continuation bet."""
stat = 0.0
try:
stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2'])
stat = float(stat_dict[player]['cb_2'])/float(stat_dict[player]['cb_opp_2'])
return (stat,
'%3.1f' % (100*stat) + '%',
'cb2=%3.1f' % (100*stat) + '%',
'cb_2=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['CB_2'], stat_dict[player]['CB_opp_2']),
'(%d/%d)' % (stat_dict[player]['cb_2'], stat_dict[player]['cb_opp_2']),
'% continuation bet turn/5th'
)
except:
@ -579,12 +664,12 @@ def cb_3(stat_dict, player):
""" River continuation bet."""
stat = 0.0
try:
stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3'])
stat = float(stat_dict[player]['cb_3'])/float(stat_dict[player]['cb_opp_3'])
return (stat,
'%3.1f' % (100*stat) + '%',
'cb3=%3.1f' % (100*stat) + '%',
'cb_3=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['CB_3'], stat_dict[player]['CB_opp_3']),
'(%d/%d)' % (stat_dict[player]['cb_3'], stat_dict[player]['cb_opp_3']),
'% continuation bet river/6th'
)
except:
@ -600,12 +685,12 @@ def cb_4(stat_dict, player):
""" 7th street continuation bet."""
stat = 0.0
try:
stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4'])
stat = float(stat_dict[player]['cb_4'])/float(stat_dict[player]['cb_opp_4'])
return (stat,
'%3.1f' % (100*stat) + '%',
'cb4=%3.1f' % (100*stat) + '%',
'cb_4=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['CB_4'], stat_dict[player]['CB_opp_4']),
'(%d/%d)' % (stat_dict[player]['cb_4'], stat_dict[player]['cb_opp_4']),
'% continuation bet 7th'
)
except:
@ -719,10 +804,10 @@ if __name__== "__main__":
print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_sb_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_bb_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_b_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsf')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3')

330
pyfpdb/UltimateBetToFpdb.py Executable file
View File

@ -0,0 +1,330 @@
#!/usr/bin/env 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 General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
import sys
from HandHistoryConverter import *
class UltimateBet(HandHistoryConverter):
# Static regexes
re_GameInfo = re.compile("Stage #(?P<HID>[0-9]+):\s+\(?(?P<GAME>Hold\'em|Razz|Seven Card|Omaha|Omaha Hi/Lo|Badugi) (?P<LIMIT>No Limit|Normal|Pot Limit),? \(?(?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\) - (?P<DATETIME>.*$)", re.MULTILINE)
re_SplitHands = re.compile('(\n\n\n+)')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+) - (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\
in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="UltimateBet", follow=follow)
logging.info("Initialising UltimateBetconverter class")
self.filetype = "text"
self.codepage = "cp1252"
self.siteId = 6 # Needs to match id entry in Sites database
if autostart:
self.start()
def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%s: posts small blind \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s: posts big blind \$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s - Ante \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s - Bring-In \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s - Pocket (\[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s -(?P<ATYPE> Bets| Checks| raises| Calls| Folds)( \$(?P<BET>[.\d]+))?( to \$(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s - Shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*|)" % player_re, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return [ ["ring", "stud", "fl"]
]
def determineGameType(self, handText):
info = {'type':'ring'}
m = self.re_GameInfo.search(handText)
if not m:
return None
mg = m.groupdict()
# translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category
"Hold'em" : ('hold','holdem'),
'Omaha' : ('hold','omahahi'),
'Omaha Hi/Lo' : ('hold','omahahilo'),
'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'),
'Badugi' : ('draw','badugi')
}
currencies = { u'':'EUR', '$':'USD', '':'T$' }
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
(info['base'], info['category']) = games[mg['GAME']]
if 'SB' in mg:
info['sb'] = mg['SB']
if 'BB' in mg:
info['bb'] = mg['BB']
if 'CURRENCY' in mg:
info['currency'] = currencies[mg['CURRENCY']]
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
def readHandInfo(self, hand):
info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m:
info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex
if m.group('TABLEATTRIBUTES'):
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(1))
m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
for key in info:
if key == 'DATETIME':
#2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]
#2008/08/17 - 01:14:43 (ET)
#2008/09/07 06:23:14 ET
m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
datetime = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), m2.group('M'),m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
hand.starttime = time.strptime(datetime, "%Y/%m/%d %H:%M:%S")
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
hand.tablename = info[key]
if key == 'BUTTON':
hand.buttonpos = info[key]
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
if m:
hand.buttonpos = int(m.group('BUTTON'))
else:
logging.info('readButton: not found')
def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
if hand.gametype['base'] in ("hold"):
m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
elif hand.gametype['base'] in ("stud"):
m = re.search(r"(?P<ANTES>.+(?=\*\*\* 3rd STREET \*\*\*)|.+)"
r"(\*\*\* 3rd STREET \*\*\*(?P<THIRD>.+(?=\*\*\* 4th STREET \*\*\*)|.+))?"
r"(\*\*\* 4th STREET \*\*\*(?P<FOURTH>.+(?=\*\*\* 5th STREET \*\*\*)|.+))?"
r"(\*\*\* 5th STREET \*\*\*(?P<FIFTH>.+(?=\*\*\* 6th STREET \*\*\*)|.+))?"
r"(\*\*\* 6th STREET \*\*\*(?P<SIXTH>.+(?=\*\*\* RIVER \*\*\*)|.+))?"
r"(\*\*\* RIVER \*\*\*(?P<SEVENTH>.+))?", hand.handText,re.DOTALL)
elif hand.gametype['base'] in ("draw"):
m = re.search(r"(?P<PREDEAL>.+(?=\*\*\* DEALING HANDS \*\*\*)|.+)"
r"(\*\*\* DEALING HANDS \*\*\*(?P<DEAL>.+(?=\*\*\* FIRST DRAW \*\*\*)|.+))?"
r"(\*\*\* FIRST DRAW \*\*\*(?P<DRAWONE>.+(?=\*\*\* SECOND DRAW \*\*\*)|.+))?"
r"(\*\*\* SECOND DRAW \*\*\*(?P<DRAWTWO>.+(?=\*\*\* THIRD DRAW \*\*\*)|.+))?"
r"(\*\*\* THIRD DRAW \*\*\*(?P<DRAWTHREE>.+))?", hand.handText,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
m = self.re_Board.search(hand.streets[street])
hand.setCommunityCards(street, m.group('CARDS').split(' '))
def readAntes(self, hand):
logging.debug("reading antes")
m = self.re_Antes.finditer(hand.handText)
for player in m:
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
#~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = set(cards.split(' '))
hand.addHoleCards(cards, m.group('PNAME'))
def readDrawCards(self, hand, street):
logging.debug("readDrawCards")
m = self.re_HeroCards.finditer(hand.streets[street])
if m == None:
hand.involved = False
else:
for player in m:
hand.hero = player.group('PNAME') # Only really need to do this once
newcards = player.group('NEWCARDS')
oldcards = player.group('OLDCARDS')
if newcards == None:
newcards = set()
else:
newcards = set(newcards.split(' '))
if oldcards == None:
oldcards = set()
else:
oldcards = set(oldcards.split(' '))
hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
def readStudPlayerCards(self, hand, street):
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
#~ logging.debug(player.groupdict())
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
if action.group('ATYPE') == ' Raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' Calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' Bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' Folds':
hand.addFold( street, action.group('PNAME'))
elif action.group('ATYPE') == ' Checks':
hand.addCheck( street, action.group('PNAME'))
#elif action.group('ATYPE') == ' discards':
# hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED'))
#elif action.group('ATYPE') == ' stands pat':
# hand.addStandsPat( street, action.group('PNAME'))
else:
print "DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),)
def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText):
hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT'))
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/pokerstars/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
LOG_FILENAME = './logging.out'
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = UltimateBet(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -44,6 +44,7 @@ import GuiPositionalStats
import GuiTableViewer
import GuiAutoImport
import GuiGraphViewer
import GuiSessionViewer
import FpdbSQLQueries
import Configuration
@ -105,30 +106,36 @@ class fpdb:
#end def delete_event
def destroy(self, widget, data=None):
self.quit(widget, data)
self.quit(widget)
#end def destroy
def dia_about(self, widget, data):
def dia_about(self, widget, data=None):
print "todo: implement dia_about",
print " version = %s, requires database version %s" % (VERSION, "118")
#end def dia_about
def dia_create_del_database(self, widget, data):
def dia_create_del_database(self, widget, data=None):
print "todo: implement dia_create_del_database"
self.obtain_global_lock()
#end def dia_create_del_database
def dia_create_del_user(self, widget, data):
def dia_create_del_user(self, widget, data=None):
print "todo: implement dia_create_del_user"
self.obtain_global_lock()
#end def dia_create_del_user
def dia_database_stats(self, widget, data):
def dia_database_stats(self, widget, data=None):
print "todo: implement dia_database_stats"
#string=fpdb_db.getDbStats(db, cursor)
#end def dia_database_stats
def dia_delete_db_parts(self, widget, data):
def dia_database_sessions(self, widget, data=None):
new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.querydict)
self.threads.append(new_sessions_thread)
sessions_tab=new_sessions_thread.get_vbox()
self.add_and_display_tab(sessions_tab, "Sessions")
def dia_delete_db_parts(self, widget, data=None):
print "todo: implement dia_delete_db_parts"
self.obtain_global_lock()
#end def dia_delete_db_parts
@ -138,7 +145,7 @@ class fpdb:
self.obtain_global_lock()
#end def dia_edit_profile
def dia_export_db(self, widget, data):
def dia_export_db(self, widget, data=None):
print "todo: implement dia_export_db"
self.obtain_global_lock()
#end def dia_export_db
@ -163,54 +170,73 @@ class fpdb:
# return (user, pw, response)
#end def dia_get_db_root_credentials
def dia_import_db(self, widget, data):
def dia_import_db(self, widget, data=None):
print "todo: implement dia_import_db"
self.obtain_global_lock()
#end def dia_import_db
def dia_licensing(self, widget, data):
def dia_licensing(self, widget, data=None):
print "todo: implement dia_licensing"
#end def dia_licensing
def dia_load_profile(self, widget, data):
def dia_load_profile(self, widget, data=None):
"""Dialogue to select a file to load a profile from"""
self.obtain_global_lock()
chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile)
if self.obtain_global_lock():
try:
chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile)
response = chooser.run()
chooser.destroy()
if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile'
response = chooser.run()
chooser.destroy()
if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile'
except:
pass
self.release_global_lock()
#end def dia_load_profile
def dia_recreate_tables(self, widget, data):
def dia_recreate_tables(self, widget, data=None):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
self.obtain_global_lock()
if self.obtain_global_lock():
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted."
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
lock_released = False
try:
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted."
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
response = dia_confirm.run()
dia_confirm.destroy()
if response == gtk.RESPONSE_YES:
self.db.recreate_tables()
elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables'
response = dia_confirm.run()
dia_confirm.destroy()
if response == gtk.RESPONSE_YES:
if self.db.backend == self.fdb_lock.MYSQL_INNODB:
# mysql requires locks on all tables or none - easier to release this lock
# than lock all the other tables
# ToDo: lock all other tables so that lock doesn't have to be released
self.release_global_lock()
lock_released = True
self.db.recreate_tables()
else:
# for other dbs use same connection as holds global lock
self.fdb_lock.recreate_tables()
elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables'
except:
pass
if not lock_released:
self.release_global_lock()
#end def dia_recreate_tables
def dia_regression_test(self, widget, data):
def dia_regression_test(self, widget, data=None):
print "todo: implement dia_regression_test"
self.obtain_global_lock()
#end def dia_regression_test
def dia_save_profile(self, widget, data):
def dia_save_profile(self, widget, data=None):
print "todo: implement dia_save_profile"
#end def dia_save_profile
@ -237,11 +263,89 @@ class fpdb:
def get_menu(self, window):
"""returns the menu for this program"""
accel_group = gtk.AccelGroup()
self.item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group)
self.item_factory.create_items(self.menu_items)
fpdbmenu = """
<ui>
<menubar name="MenuBar">
<menu action="main">
<menuitem action="LoadProf"/>
<menuitem action="EditProf"/>
<menuitem action="SaveProf"/>
<separator/>
<menuitem action="Quit"/>
</menu>
<menu action="import">
<menuitem action="bulkimp"/>
<menuitem action="autoimp"/>
<menuitem action="autorate"/>
</menu>
<menu action="viewers">
<menuitem action="autoimp"/>
<menuitem action="graphs"/>
<menuitem action="handreplay"/>
<menuitem action="playerdetails"/>
<menuitem action="playerstats"/>
<menuitem action="posnstats"/>
<menuitem action="sessionreplay"/>
<menuitem action="tableviewer"/>
</menu>
<menu action="database">
<menuitem action="createdb"/>
<menuitem action="createuser"/>
<menuitem action="createtabs"/>
<menuitem action="stats"/>
<menuitem action="sessions"/>
</menu>
<menu action="help">
<menuitem action="Abbrev"/>
<separator/>
<menuitem action="About"/>
<menuitem action="License"/>
</menu>
</menubar>
</ui>"""
uimanager = gtk.UIManager()
accel_group = uimanager.get_accel_group()
actiongroup = gtk.ActionGroup('UIManagerExample')
# Create actions
actiongroup.add_actions([('main', None, '_Main'),
('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', self.quit),
('LoadProf', None, '_Load Profile (broken)', '<control>L', 'Load your profile', self.dia_load_profile),
('EditProf', None, '_Edit Profile (todo)', '<control>E', 'Edit your profile', self.dia_edit_profile),
('SaveProf', None, '_Save Profile (todo)', '<control>S', 'Save your profile', self.dia_save_profile),
('import', None, '_Import'),
('bulkimp', None, '_Bulk Import', '<control>B', 'Bulk Import', self.tab_bulk_import),
('autorate', None, 'Auto _Rating (todo)', '<control>R', 'Auto Rating (todo)', self.not_implemented),
('viewers', None, '_Viewers'),
('autoimp', None, '_Auto Import and HUD', '<control>A', 'Auto Import and HUD', self.tab_auto_import),
('graphs', None, '_Graphs', '<control>G', 'Graphs', self.tabGraphViewer),
('handreplay', None, 'Hand _Replayer (todo)', None, 'Hand Replayer (todo)', self.not_implemented),
('playerdetails', None, 'Player _Details (todo)', None, 'Player Details (todo)', self.not_implemented),
('playerstats', None, '_Player Stats (tabulated view)', '<control>P', 'Player Stats (tabulated view)', self.tab_player_stats),
('posnstats', None, 'P_ositional Stats (tabulated view)', '<control>O', 'Positional Stats (tabulated view)', self.tab_positional_stats),
('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),
('database', None, '_Database'),
('createdb', None, 'Create or Delete _Database (todo)', None, 'Create or Delete Database', self.dia_create_del_database),
('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),
('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats),
('sessions', None, 'Sessions', None, 'View Sessions', self.dia_database_sessions),
('help', None, '_Help'),
('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations),
('About', None, 'A_bout', None, 'About the program', self.dia_about),
('License', None, '_License and Copying (todo)', None, 'License and Copying', self.dia_licensing),
])
actiongroup.get_action('Quit').set_property('short-label', '_Quit')
uimanager.insert_action_group(actiongroup, 0)
merge_id = uimanager.add_ui_from_string(fpdbmenu)
# Create a MenuBar
menubar = uimanager.get_widget('/MenuBar')
window.add_accel_group(accel_group)
return self.item_factory.get_widget("<main>")
return menubar
#end def get_menu
def load_profile(self):
@ -287,17 +391,25 @@ class fpdb:
# Database connected to successfully, load queries to pass on to other classes
self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
self.db.db.rollback()
#end def load_profile
def not_implemented(self):
def not_implemented(self, widget, data=None):
print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented
#end def not_implemented
def obtain_global_lock(self):
print "todo: implement obtain_global_lock (users: pls ignore this)"
print "\nTaking global lock ..."
self.fdb_lock = fpdb_db.fpdb_db()
self.fdb_lock.connect(self.settings['db-backend'],
self.settings['db-host'],
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
return fpdb_simple.get_global_lock(self.fdb_lock)
#end def obtain_global_lock
def quit(self, widget, data):
def quit(self, widget):
print "Quitting normally"
#check if current settings differ from profile, if so offer to save or abort
self.db.disconnect()
@ -305,14 +417,16 @@ class fpdb:
#end def quit_cliecked
def release_global_lock(self):
print "todo: implement release_global_lock"
self.fdb_lock.db.rollback()
self.fdb_lock.disconnect()
print "Global lock released."
#end def release_global_lock
def tab_abbreviations(self, widget, data):
def tab_abbreviations(self, widget, data=None):
print "todo: implement tab_abbreviations"
#end def tab_abbreviations
def tab_auto_import(self, widget, data):
def tab_auto_import(self, widget, data=None):
"""opens the auto import tab"""
new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config)
self.threads.append(new_aimp_thread)
@ -320,29 +434,29 @@ class fpdb:
self.add_and_display_tab(aimp_tab, "Auto Import")
#end def tab_auto_import
def tab_bulk_import(self, widget, data):
def tab_bulk_import(self, widget, data=None):
"""opens a tab for bulk importing"""
#print "start of tab_bulk_import"
new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config)
new_import_thread=GuiBulkImport.GuiBulkImport(self.settings, self.config)
self.threads.append(new_import_thread)
bulk_tab=new_import_thread.get_vbox()
self.add_and_display_tab(bulk_tab, "Bulk Import")
#end def tab_bulk_import
def tab_player_stats(self, widget, data):
new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.db, self.config, self.querydict)
def tab_player_stats(self, widget, data=None):
new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.config, self.querydict, self.window)
self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Player Stats")
def tab_positional_stats(self, widget, data):
new_ps_thread=GuiPositionalStats.GuiPositionalStats(self.db, self.config, self.querydict)
def tab_positional_stats(self, widget, data=None):
new_ps_thread=GuiPositionalStats.GuiPositionalStats(self.config, self.querydict)
self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Positional Stats")
def tab_main_help(self, widget, data):
def tab_main_help(self, widget, data=None):
"""Displays a tab with the main fpdb help screen"""
#print "start of tab_main_help"
mh_tab=gtk.Label("""Welcome to Fpdb!
@ -352,7 +466,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.add_and_display_tab(mh_tab, "Help")
#end def tab_main_help
def tab_table_viewer(self, widget, data):
def tab_table_viewer(self, widget, data=None):
"""opens a table viewer tab"""
#print "start of tab_table_viewer"
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings)
@ -361,7 +475,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.add_and_display_tab(tv_tab, "Table Viewer")
#end def tab_table_viewer
def tabGraphViewer(self, widget, data):
def tabGraphViewer(self, widget, data=None):
"""opens a graph viewer tab"""
#print "start of tabGraphViewer"
new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config)
@ -381,49 +495,9 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.window.connect("destroy", self.destroy)
self.window.set_title("Free Poker DB - v%s or higher" % (VERSION, ))
self.window.set_border_width(1)
self.window.set_size_request(1020,400)
self.window.set_default_size(900,720)
self.window.set_resizable(True)
self.menu_items = (
( "/_Main", None, None, 0, "<Branch>" ),
( "/Main/_Load Profile (broken)", "<control>L", self.dia_load_profile, 0, None ),
( "/Main/_Edit Profile (todo)", "<control>E", self.dia_edit_profile, 0, None ),
( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ),
("/Main/sep1", None, None, 0, "<Separator>" ),
("/Main/_Quit", "<control>Q", self.quit, 0, None ),
("/_Import", None, None, 0, "<Branch>" ),
("/Import/_Bulk Import", "<control>B", self.tab_bulk_import, 0, None ),
("/Import/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
("/Import/Auto _Rating (todo)", "<control>R", self.not_implemented, 0, None ),
("/_Viewers", None, None, 0, "<Branch>" ),
("/_Viewers/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
("/Viewers/_Graphs", "<control>G", self.tabGraphViewer, 0, None ),
("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ),
("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ),
("/Viewers/_Player Stats (tabulated view)", None, self.tab_player_stats, 0, None ),
("/Viewers/Positional Stats (tabulated view)", None, self.tab_positional_stats, 0, None ),
("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ),
("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ),
("/Viewers/Poker_table Viewer (mostly obselete)", "<control>T", self.tab_table_viewer, 0, None ),
#( "/Viewers/Tourney Replayer
( "/_Database", None, None, 0, "<Branch>" ),
( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ),
( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ),
( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ),
( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ),
( "/D_ebugging", None, None, 0, "<Branch>" ),
( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ),
( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ),
( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ),
( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ),
( "/_Help", None, None, 0, "<LastBranch>" ),
( "/Help/_Main Help", "<control>H", self.tab_main_help, 0, None ),
( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ),
( "/Help/sep1", None, None, 0, "<Separator>" ),
( "/Help/A_bout (todo)", None, self.dia_about, 0, None ),
( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None )
)
self.main_vbox = gtk.VBox(False, 1)
self.main_vbox.set_border_width(1)
self.window.add(self.main_vbox)
@ -437,7 +511,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tabs=[]
self.tab_names=[]
self.tab_buttons=[]
self.tab_box = gtk.HBox(False,1)
self.tab_box = gtk.HBox(True,1)
self.main_vbox.pack_start(self.tab_box, False, True, 0)
self.tab_box.show()
#done tab bar
@ -449,11 +523,12 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None)
self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host)
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.database, self.db.host))
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
self.window.show()
sys.stderr.write("fpdb starting ...")
#end def __init__
def main(self):

View File

@ -59,23 +59,33 @@ class fpdb_db:
self.database=database
if backend==self.MYSQL_INNODB:
import MySQLdb
self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
try:
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
except:
raise fpdb_simple.FpdbError("MySQL connection failed")
elif backend==self.PGSQL:
import psycopg2
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
# If DB connection is made over TCP, then the variables
# host, user and password are required
print "host=%s user=%s pass=%s." % (host, user, password)
if self.host and self.user and self.password:
try:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
except:
raise fpdb_simple.FpdbError("PostgreSQL connection failed")
# For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or
# flat out wrong
if self.host == "localhost" or self.host == "127.0.0.1":
self.db = psycopg2.connect(database = database)
else:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
try:
self.db = psycopg2.connect(database = database)
except:
raise fpdb_simple.FpdbError("PostgreSQL connection failed")
else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor()
@ -86,7 +96,7 @@ class fpdb_db:
try:
self.cursor.execute("SELECT * FROM Settings")
settings=self.cursor.fetchone()
if settings[0]!=118:
if settings[0]!=119:
print "outdated or too new database version - please recreate tables"
self.wrongDbVersion=True
except:# _mysql_exceptions.ProgrammingError:
@ -137,7 +147,7 @@ class fpdb_db:
if(self.get_backend_name() == 'MySQL InnoDB'):
#Databases with FOREIGN KEY support need this switched of before you can drop tables
self.drop_referencial_integrity()
self.drop_referential_integrity()
# Query the DB to see what tables exist
self.cursor.execute(self.sql.query['list_tables'])
@ -156,7 +166,7 @@ class fpdb_db:
self.db.commit()
#end def drop_tables
def drop_referencial_integrity(self):
def drop_referential_integrity(self):
"""Update all tables to remove foreign keys"""
self.cursor.execute(self.sql.query['list_tables'])
@ -174,7 +184,7 @@ class fpdb_db:
key = "`" + inner[j][0] + "_" + m.group() + "`"
self.cursor.execute("ALTER TABLE " + inner[j][0] + " DROP FOREIGN KEY " + key)
self.db.commit()
#end drop_referencial_inegrity
#end drop_referential_inegrity
def get_backend_name(self):
"""Returns the name of the currently used backend"""
@ -191,10 +201,14 @@ class fpdb_db:
#end def get_db_info
def fillDefaultData(self):
self.cursor.execute("INSERT INTO Settings VALUES (118);")
self.cursor.execute("INSERT INTO Settings VALUES (119);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Carbon', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'OnGame', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'UltimateBet', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Betfair', 'USD');")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#end def fillDefaultData
@ -207,4 +221,47 @@ class fpdb_db:
self.db.commit()
print "Finished recreating tables"
#end def recreate_tables
#end class fpdb_db
def getSqlPlayerIDs(names, site_id):
result = []
notfound = []
self.cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
tmp = dict(self.cursor.fetchall())
for n in names:
if n not in tmp:
notfound.append(n)
else:
result.append(tmp[n])
if notfound:
cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
tmp = cursor.fetchall()
for n in tmp:
result.append(n[0])
#We proabably want to cache this
return result
def storeHand(self, p):
#stores into table hands:
self.cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises
,street3Raises, street4Raises, street1Pot
,street2Pot, street3Pot, street4Pot
,showdownPot
)
VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
,(p['siteHandNo'], gametype_id, p['handStart'], len(names), p['tableName'], datetime.datetime.today(), p['maxSeats']
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2']
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown']
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises']
,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot']
,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot']
,hudCache['showdownPot']
)
)
#return getLastInsertId(backend, conn, cursor)

View File

@ -61,13 +61,14 @@ class Importer:
self.cursor = None
self.filelist = {}
self.dirlist = {}
self.siteIds = {}
self.addToDirList = {}
self.removeFromFileList = {} # to remove deleted files
self.monitor = False
self.updated = {} #Time last import was run {file:mtime}
self.updated = {} #Time last import was run {file:mtime}
self.lines = None
self.faobs = None #File as one big string
self.pos_in_file = {} # dict to remember how far we have read in the file
self.faobs = None # File as one big string
self.pos_in_file = {} # dict to remember how far we have read in the file
#Set defaults
self.callHud = self.config.get_import_parameters().get("callFpdbHud")
@ -76,6 +77,7 @@ class Importer:
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(self.config)
self.fdb.db.rollback()
#Set functions
def setCallHud(self, value):
@ -112,20 +114,32 @@ class Importer:
def addImportFile(self, filename, site = "default", filter = "passthrough"):
#TODO: test it is a valid file -> put that in config!!
self.filelist[filename] = [site] + [filter]
if site not in self.siteIds:
# Get id from Sites table in DB
self.fdb.cursor.execute(self.fdb.sql.query['getSiteId'], (site,))
result = self.fdb.cursor.fetchall()
if len(result) == 1:
self.siteIds[site] = result[0][0]
else:
if len(result) == 0:
print "[ERROR] Database ID for %s not found" % site
else:
print "[ERROR] More than 1 Database ID found for %s - Multiple currencies not implemented yet" % site
# Called from GuiBulkImport to add a file or directory.
def addBulkImportImportFileOrDir(self, inputPath,filter = "passthrough"):
def addBulkImportImportFileOrDir(self, inputPath, site = "PokerStars"):
"""Add a file or directory for bulk import"""
filter = self.config.hhcs[site].converter
# Bulk import never monitors
# if directory, add all files in it. Otherwise add single file.
# TODO: only add sane files?
if os.path.isdir(inputPath):
for subdir in os.walk(inputPath):
for file in subdir[2]:
self.addImportFile(os.path.join(inputPath, subdir[0], file), site="default", filter=filter)
self.addImportFile(os.path.join(inputPath, subdir[0], file), site=site, filter=filter)
else:
self.addImportFile(inputPath, site="default", filter=filter)
self.addImportFile(inputPath, site=site, filter=filter)
#Add a directory of files to filelist
#Only one import directory per site supported.
#dirlist is a hash of lists:
@ -215,7 +229,7 @@ class Importer:
#if os.path.isdir(file):
#self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
else:
removeFromFileList[file] = True
self.removeFromFileList[file] = True
self.addToDirList = filter(lambda x: self.addImportDirectory(x, True, self.addToDirList[x][0], self.addToDirList[x][1]), self.addToDirList)
for file in self.removeFromFileList:
@ -224,6 +238,8 @@ class Importer:
self.addToDirList = {}
self.removeFromFileList = {}
self.fdb.db.rollback()
# This is now an internal function that should not be called directly.
def import_file_dict(self, file, site, filter):
@ -297,10 +313,9 @@ class Importer:
print "TODO: implement importing tournament summaries"
#self.faobs = readfile(inputFile)
#self.parseTourneyHistory()
return 0
return (0,0,0,1,0)
site = fpdb_simple.recogniseSite(firstline)
category = fpdb_simple.recogniseCategory(firstline)
category=fpdb_simple.recogniseCategory(firstline)
startpos = 0
stored = 0 #counter
@ -308,59 +323,28 @@ class Importer:
partial = 0 #counter
errors = 0 #counter
for i in xrange (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method
if len(self.lines[i]) < 2:
endpos = i
hand = self.lines[startpos:endpos]
for i in xrange (len(self.lines)):
if (len(self.lines[i])<2): #Wierd way to detect for '\r\n' or '\n'
endpos=i
hand=self.lines[startpos:endpos]
if len(hand[0]) < 2:
hand = hand[1:]
if (len(hand[0])<2):
hand=hand[1:]
cancelled=False
damaged=False
if (site=="ftp"):
for i in range (len(hand)):
if hand[i].endswith(" has been canceled"): #this is their typo. this is a typo, right?
cancelled = True
#FTP generates lines looking like:
#Seat 1: IOS Seat 2: kashman59 (big blind) showed [8c 9d] and won ($3.25) with a pair of Eights
#ie. Seat X multiple times on the same line in the summary section, when a new player sits down in the
#middle of the hand.
#TODO: Deal with this properly, either fix the file or make the parsing code work with this line.
if "Seat" in hand[i]:
mo = re.search(" Seat [0-9]+: ", hand[i])
if mo:
print "mo=", mo, "\nmo.start=", mo.start(),"\nhand[i]=",hand[i]
hand.insert(i+1, hand[i][mo.start()+1:])
hand[i] = hand[i][0:mo.start()]
if len(hand) < 3:
if (len(hand)<3):
pass
#todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work.
elif hand[0].endswith(" (partial)"): #partial hand - do nothing
partial += 1
elif "Seat" not in hand[1] and "Seat" not in hand[2] and "Seat" not in hand[3]:
partial += 1
elif cancelled or damaged:
partial += 1
if damaged:
print """
DEBUG: Partial hand triggered by a line containing 'Seat X:' twice. This is a
bug in the FTP software when a player sits down in the middle of a hand.
Adding a newline after the player name will fix the issue
"""
print "File: %s" %(file)
print "Line: %s" %(startpos)
else: #normal processing
isTourney = fpdb_simple.isTourney(hand[0])
#TODO: This is ugly - we didn't actually find the start of the
# hand with the outer loop so we test again...
else:
isTourney=fpdb_simple.isTourney(hand[0])
if not isTourney:
hand = fpdb_simple.filterAnteBlindFold(site,hand)
hand = fpdb_simple.filterAnteBlindFold(hand)
self.hand=hand
try:
handsId = fpdb_parse_logic.mainParser(self.settings['db-backend'], self.fdb.db
,self.fdb.cursor, site, category, hand, self.config)
,self.fdb.cursor, self.siteIds[site], category, hand, self.config)
self.fdb.db.commit()
stored += 1
@ -370,6 +354,7 @@ class Importer:
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError:
duplicates += 1
self.fdb.db.rollback()
except (ValueError), fe:
errors += 1
self.printEmailErrorMessage(errors, file, hand)
@ -377,11 +362,12 @@ class Importer:
if (self.settings['failOnError']):
self.fdb.db.commit() #dont remove this, in case hand processing was cancelled.
raise
else:
self.fdb.db.rollback()
except (fpdb_simple.FpdbError), fe:
errors += 1
self.printEmailErrorMessage(errors, file, hand)
#fe.printStackTrace() #todo: get stacktrace
self.fdb.db.rollback()
if self.settings['failOnError']:
@ -389,18 +375,18 @@ class Importer:
raise
if self.settings['minPrint']:
if not ((stored+duplicates+partial+errors) % self.settings['minPrint']):
print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors
if not ((stored+duplicates+errors) % self.settings['minPrint']):
print "stored:", stored, "duplicates:", duplicates, "errors:", errors
if self.settings['handCount']:
if ((stored+duplicates+partial+errors) >= self.settings['handCount']):
if ((stored+duplicates+errors) >= self.settings['handCount']):
if not self.settings['quiet']:
print "quitting due to reaching the amount of hands to be imported"
print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime)
print "Total stored:", stored, "duplicates:", duplicates, "errors:", errors, " time:", (time() - starttime)
sys.exit(0)
startpos = endpos
ttime = time() - starttime
print "\rTotal stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time:", ttime
print "\rTotal stored:", stored, "duplicates:", duplicates, "errors:", errors, " time:", ttime
if not stored:
if duplicates:
@ -411,16 +397,11 @@ class Importer:
else:
print "failed to read a single hand from file:", inputFile
handsId=0
#todo: this will cause return of an unstored hand number if the last hand was error or partial
#todo: this will cause return of an unstored hand number if the last hand was error
self.fdb.db.commit()
self.handsId=handsId
return (stored, duplicates, partial, errors, ttime)
def parseTourneyHistory(self):
print "Tourney history parser stub"
#Find tournament boundaries.
#print self.foabs
def printEmailErrorMessage(self, errors, filename, line):
traceback.print_exc(file=sys.stderr)
print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."

View File

@ -21,7 +21,7 @@ import fpdb_simple
import fpdb_save_to_db
#parses a holdem hand
def mainParser(backend, db, cursor, site, category, hand, config):
def mainParser(backend, db, cursor, siteID, category, hand, config):
category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud"
@ -34,9 +34,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
#part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
handStartTime = fpdb_simple.parseHandStartTime(hand[0], site)
siteID = fpdb_simple.recogniseSiteID(cursor, site)
#print "parse logic, siteID:",siteID,"site:",site
handStartTime = fpdb_simple.parseHandStartTime(hand[0])
isTourney = fpdb_simple.isTourney(hand[0])
smallBlindLine = 0
@ -49,8 +47,6 @@ def mainParser(backend, db, cursor, site, category, hand, config):
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney)
if isTourney:
if site != "ps":
raise fpdb_simple.FpdbError("tourneys are only supported on PS right now")
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0])
fee = fpdb_simple.parseFee(hand[0])
@ -64,7 +60,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo)
hand = fpdb_simple.filterCrap(site, hand, isTourney)
hand = fpdb_simple.filterCrap(hand, isTourney)
#part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street
fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets)
@ -77,7 +73,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
names = fpdb_simple.parseNames(seatLines)
playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID)
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines, site)
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines)
startCashes = tmp['startCashes']
seatNos = tmp['seatNos']
@ -90,30 +86,27 @@ def mainParser(backend, db, cursor, site, category, hand, config):
#part 4: take appropriate action for each line based on linetype
for i, line in enumerate(hand):
if lineTypes[i] == "cards":
fpdb_simple.parseCardLine(site, category, lineStreets[i], line, names, cardValues, cardSuits, boardValues, boardSuits)
fpdb_simple.parseCardLine(category, lineStreets[i], line, names, cardValues, cardSuits, boardValues, boardSuits)
#if category=="studhilo":
# print "hand[i]:", hand[i]
# print "cardValues:", cardValues
# print "cardSuits:", cardSuits
elif lineTypes[i] == "action":
fpdb_simple.parseActionLine(site, base, isTourney, line, lineStreets[i], playerIDs, names, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo)
fpdb_simple.parseActionLine(base, isTourney, line, lineStreets[i], playerIDs, names, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo)
elif lineTypes[i] == "win":
fpdb_simple.parseWinLine(line, site, names, winnings, isTourney)
fpdb_simple.parseWinLine(line, names, winnings, isTourney)
elif lineTypes[i] == "rake":
totalRake = 0 if isTourney else fpdb_simple.parseRake(line)
fpdb_simple.splitRake(winnings, rakes, totalRake)
elif lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore":
pass
elif lineTypes[i]=="ante":
fpdb_simple.parseAnteLine(line, site, isTourney, names, antes)
fpdb_simple.parseAnteLine(line, isTourney, names, antes)
elif lineTypes[i]=="table":
tableResult=fpdb_simple.parseTableLine(site, base, line)
tableResult=fpdb_simple.parseTableLine(base, line)
else:
raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i])
if site == "ftp":
tableResult = fpdb_simple.parseTableLine(site, base, hand[0])
maxSeats = tableResult['maxSeats']
tableName = tableResult['tableName']
#print "before part5, antes:", antes
@ -128,7 +121,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, ))
limit_type = cursor.fetchone()[0]
fpdb_simple.convert3B4B(site, category, limit_type, actionTypes, actionAmounts)
fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts)
totalWinnings = sum(winnings)
@ -165,7 +158,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
, allIns, actionAmounts, actionNos, hudImportData, maxSeats
, tableName, seatNos)
else:
raise fpdb_simple.FpdbError("unrecognised category") # it's impossible to get here, but w/e
raise fpdb_simple.FpdbError("unrecognised category")
else:
if base == "hold":
result = fpdb_save_to_db.ring_holdem_omaha(
@ -183,7 +176,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
, actionAmounts, actionNos, hudImportData, maxSeats, tableName
, seatNos)
else:
raise fpdb_simple.FpdbError ("unrecognised category") # also impossible to get here
raise fpdb_simple.FpdbError ("unrecognised category")
db.commit()
return result
#end def mainParser

View File

@ -26,14 +26,13 @@ MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
#fastStoreHudCache = False # set this to True to test the new storeHudCache routine
#
#saveActions = True # set this to False to avoid storing action data
# # Pros: speeds up imports
# # Cons: no action data is saved, so you need to keep the hand histories
# # variance not available on stats page
# # no graphs
fastStoreHudCache = False # set this to True to test the new storeHudCache routine
saveActions = True # set this to False to avoid storing action data
# Pros: speeds up imports
# Cons: no action data is saved, so you need to keep the hand histories
# variance not available on stats page
# : No graphs
#stores a stud/razz hand into the database
def ring_stud(config, backend, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
@ -55,7 +54,7 @@ def ring_stud(config, backend, db, cursor, base, category, site_hand_no, gametyp
,start_cashes, antes, card_values
,card_suits, winnings, rakes, seatNos)
fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
@ -83,17 +82,17 @@ def ring_holdem_omaha(config, backend, db, cursor, base, category, site_hand_no,
t2 = time()
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats)
,hand_start_time, names, tableName, maxSeats, hudImportData)
t3 = time()
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha(
backend, db, cursor, category, hands_id, player_ids, start_cashes
, positions, card_values, card_suits, winnings, rakes, seatNos)
, positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData)
t4 = time()
#print "ring holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
t5 = time()
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
t6 = time()
@ -132,9 +131,9 @@ def tourney_holdem_omaha(config, backend, db, cursor, base, category, siteTourne
#print "tourney holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(cursor, base, category, gametype_id, player_ids, hudImportData)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
@ -166,7 +165,7 @@ def tourney_stud(config, backend, db, cursor, base, category, siteTourneyNo, buy
, playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids)
fpdb_simple.storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@ def testGameInfo():
def testHandInfo():
text = u""""PokerStars Game #20461877044: Hold'em No Limit ($1/$2) - 2008/09/16 18:58:01 ET"""
text = u"""PokerStars Game #20461877044: Hold'em No Limit ($1/$2) - 2008/09/16 18:58:01 ET"""
hhc = PokerStarsToFpdb.PokerStars(autostart=False)
h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test")
hhc.readHandInfo(h)

View File

@ -16,25 +16,24 @@ def testPokerStarsHHDate():
datetime.datetime(2008,9,7,11,23,14))
)
#def testFullTiltHHDate(self):
# sitngo1 = "Full Tilt Poker Game #10311865543: $1 + $0.25 Sit & Go (78057629), Table 1 - 25/50 - No Limit Hold'em - 0:07:45 ET - 2009/01/29"
# cash1 = "Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09"
# cash2 = "Full Tilt Poker Game #9468383505: Table Bike (deep 6) - $0.05/$0.10 - No Limit Hold'em - 5:09:36 ET - 2008/12/13"
def testFullTiltHHDate():
sitngo1 = "Full Tilt Poker Game #10311865543: $1 + $0.25 Sit & Go (78057629), Table 1 - 25/50 - No Limit Hold'em - 0:07:45 ET - 2009/01/29"
cash1 = "Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09"
cash2 = "Full Tilt Poker Game #9468383505: Table Bike (deep 6) - $0.05/$0.10 - No Limit Hold'em - 5:09:36 ET - 2008/12/13"
# result = fpdb_simple.parseHandStartTime(sitngo1,"ftp")
# self.failUnless(result==datetime.datetime(2009,1,29,05,07,45),
# "Date incorrect, expected: 2009-01-29 05:07:45 got: " + str(result))
# result = fpdb_simple.parseHandStartTime(cash1,"ftp")
# self.failUnless(result==datetime.datetime(2008,12,9,14,40,20),
# "Date incorrect, expected: 2008-12-09 14:40:20 got: " + str(result))
# result = fpdb_simple.parseHandStartTime(cash2,"ftp")
# self.failUnless(result==datetime.datetime(2008,12,13,10,9,36),
# "Date incorrect, expected: 2008-12-13 10:09:36 got: " + str(result))
result = fpdb_simple.parseHandStartTime(sitngo1,"ftp")
assert result==datetime.datetime(2009,1,29,05,07,45)
result = fpdb_simple.parseHandStartTime(cash1,"ftp")
assert result==datetime.datetime(2008,12,9,14,40,20)
result = fpdb_simple.parseHandStartTime(cash2,"ftp")
assert result==datetime.datetime(2008,12,13,10,9,36)
# def testTableDetection(self):
# result = Tables.clean_title("French (deep)")
# self.failUnless(result == "French", "French (deep) parsed incorrectly. Expected 'French' got: " + str(result))
# result = ("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
def testTableDetection():
result = Tables.clean_title("French (deep)")
assert result == "French"
result = Tables.clean_title("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
assert result == "French"
for (header, site, result) in tuples:
yield checkDateParse, header, site, result
for (header, site, result) in tuples:
yield checkDateParse, header, site, result