pull and merge from fpdboz

This commit is contained in:
sqlcoder 2009-05-02 21:02:26 +01:00
commit 7d28f70a98
22 changed files with 1295 additions and 864 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

@ -197,6 +197,15 @@ class Aux_window:
temp = temp + "%s" % self.layout[layout] temp = temp + "%s" % self.layout[layout]
return temp 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: class Popup:
def __init__(self, node): def __init__(self, node):
self.name = node.getAttribute("pu_name") self.name = node.getAttribute("pu_name")
@ -277,6 +286,7 @@ class Config:
self.supported_games = {} self.supported_games = {}
self.supported_databases = {} self.supported_databases = {}
self.aux_windows = {} self.aux_windows = {}
self.hhcs = {}
self.popup_windows = {} self.popup_windows = {}
# s_sites = doc.getElementsByTagName("supported_sites") # s_sites = doc.getElementsByTagName("supported_sites")
@ -299,6 +309,11 @@ class Config:
aw = Aux_window(node = aw_node) aw = Aux_window(node = aw_node)
self.aux_windows[aw.name] = aw 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") # s_dbs = doc.getElementsByTagName("popup_windows")
for pu_node in doc.getElementsByTagName("pu"): for pu_node in doc.getElementsByTagName("pu"):
pu = Popup(node = pu_node) pu = Popup(node = pu_node)
@ -703,6 +718,11 @@ if __name__== "__main__":
for w in c.aux_windows.keys(): for w in c.aux_windows.keys():
print c.aux_windows[w] print c.aux_windows[w]
print "----------- END AUX WINDOW FORMATS -----------" 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 -----------" print "\n----------- POPUP WINDOW FORMATS -----------"
for w in c.popup_windows.keys(): for w in c.popup_windows.keys():

View File

@ -201,6 +201,9 @@ class Database:
t_dict = {} t_dict = {}
for name, val in zip(colnames, row): for name, val in zip(colnames, row):
t_dict[name.lower()] = val t_dict[name.lower()] = val
# print t_dict
t_dict['screen_name'] = names[t_dict['player_id']]
t_dict['seat'] = seats[t_dict['player_id']]
stat_dict[t_dict['player_id']] = t_dict stat_dict[t_dict['player_id']] = t_dict
return stat_dict return stat_dict

View File

@ -27,7 +27,8 @@ from HandHistoryConverter import *
class Everleaf(HandHistoryConverter): class Everleaf(HandHistoryConverter):
# Static regexes # 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_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.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) 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)

393
pyfpdb/Filters.py Normal file
View File

@ -0,0 +1,393 @@
#!/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.sites = {}
self.games = {}
self.limits = {}
self.siteid = {}
self.heroes = {}
# 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)
sitesFrame = gtk.Frame("Sites:")
sitesFrame.set_label_align(0.0, 0.0)
vbox = gtk.VBox(False, 0)
self.fillSitesFrame(vbox)
sitesFrame.add(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)
# Limits
limitsFrame = gtk.Frame("Limits:")
limitsFrame.set_label_align(0.0, 0.0)
limitsFrame.show()
vbox = gtk.VBox(False, 0)
self.fillLimitsFrame(vbox)
limitsFrame.add(vbox)
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.Button1=gtk.Button("Unamed 1")
self.Button2=gtk.Button("Unamed 2")
#self.exportButton.connect("clicked", self.exportGraph, "show clicked")
self.Button2.set_sensitive(False)
self.mainVBox.add(playerFrame)
self.mainVBox.add(sitesFrame)
self.mainVBox.add(gamesFrame)
self.mainVBox.add(limitsFrame)
self.mainVBox.add(dateFrame)
self.mainVBox.add(self.Button1)
self.mainVBox.add(self.Button2)
self.mainVBox.show_all()
# Should do this cleaner
if display["Heroes"] == False:
playerFrame.hide()
if display["Sites"] == False:
sitesFrame.hide()
if display["Games"] == False:
gamesFrame.hide()
if display["Limits"] == False:
limitsFrame.hide()
if display["Dates"] == False:
dateFrame.hide()
if display["Button1"] == False:
self.Button1.hide()
if 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 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")
def registerButton2Name(self, title):
self.Button2.set_label(title)
def registerButton2Callback(self, callback):
self.Button2.connect("clicked", callback, "clicked")
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):
cb = gtk.CheckButton(str(limit))
cb.connect('clicked', self.__set_limit_select, limit)
hbox.pack_start(cb, False, False, 0)
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])
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):
self.cursor.execute(self.sql.query['getLimits'])
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.createLimitLine(hbox, line[0])
else:
print "INFO: No games returned from database"
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 __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())

View File

@ -41,8 +41,8 @@ class FpdbSQLQueries:
self.query['list_tables'] = """ """ self.query['list_tables'] = """ """
################################################################## ##################################################################
# Drop Tables - MySQL, PostgreSQL and SQLite all share same syntax # Drop Tables - MySQL, PostgreSQL and SQLite all share same syntax
################################################################## ##################################################################
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
self.query['drop_table'] = """DROP TABLE IF EXISTS """ self.query['drop_table'] = """DROP TABLE IF EXISTS """
@ -609,7 +609,7 @@ class FpdbSQLQueries:
elif(self.dbname == 'SQLite'): elif(self.dbname == 'SQLite'):
self.query['getSiteId'] = """SELECT id from Sites where name = %s""" self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
self.query['getRingProfitAllHandsPlayerIdSite'] = """ self.query['getRingProfitAllHandsPlayerIdSite'] = """
SELECT hp.handId, hp.winnings, coalesce(hp.ante,0) + SUM(ha.amount) SELECT hp.handId, hp.winnings, coalesce(hp.ante,0) + SUM(ha.amount)
, hp.winnings - (coalesce(hp.ante,0) + SUM(ha.amount)) , hp.winnings - (coalesce(hp.ante,0) + SUM(ha.amount))
@ -617,28 +617,15 @@ class FpdbSQLQueries:
INNER JOIN Players pl ON hp.playerId = pl.id INNER JOIN Players pl ON hp.playerId = pl.id
INNER JOIN Hands h ON h.id = hp.handId INNER JOIN Hands h ON h.id = hp.handId
INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id
INNER JOIN Gametypes g ON h.gametypeId = g.id
where pl.id in <player_test> where pl.id in <player_test>
AND pl.siteId in <site_test> AND pl.siteId in <site_test>
AND h.handStart > '<startdate_test>' AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>' AND h.handStart < '<enddate_test>'
AND g.bigBlind in <limit_test>
AND hp.tourneysPlayersId IS NULL AND hp.tourneysPlayersId IS NULL
GROUP BY hp.handId, hp.winnings, h.handStart, hp.ante GROUP BY hp.handId, hp.winnings, h.handStart, hp.ante
ORDER BY h.handStart""" ORDER BY h.handStart"""
elif(self.dbname == 'SQLite'):
#Probably doesn't work.
self.query['getRingProfitAllHandsPlayerIdSite'] = """
SELECT hp.handId, hp.winnings, SUM(ha.amount), hp.winnings - SUM(ha.amount)
FROM HandsPlayers hp
INNER JOIN Players pl ON hp.playerId = pl.id
INNER JOIN Hands h ON h.id = hp.handId
INNER JOIN HandsActions ha ON ha.handPlayerId = hp.id
where pl.id in <player_test>
AND pl.siteId in <site_test>
AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>'
AND hp.tourneysPlayersId IS NULL
GROUP BY hp.handId, hp.winnings, h.handStart
ORDER BY h.handStart"""
if(self.dbname == 'MySQL InnoDB'): if(self.dbname == 'MySQL InnoDB'):
self.query['playerStats'] = """ self.query['playerStats'] = """
@ -1153,6 +1140,12 @@ class FpdbSQLQueries:
and hprof2.PlPosition = stats.PlPosition) and hprof2.PlPosition = stats.PlPosition)
order by stats.category, stats.limittype, stats.bigBlind, cast(stats.PlPosition as signed) order by stats.category, stats.limittype, stats.bigBlind, cast(stats.PlPosition as signed)
""" """
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
self.query['getGames'] = """SELECT DISTINCT category from Gametypes"""
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC"""
if __name__== "__main__": if __name__== "__main__":
from optparse import OptionParser from optparse import OptionParser

View File

@ -28,11 +28,13 @@ class Fulltilt(HandHistoryConverter):
# Static regexes # 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_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_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_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_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n')
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") 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): def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True):
"""\ """\

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

@ -66,10 +66,10 @@ class GuiBulkImport():
self.importer.setDropIndexes(cb_model[cb_index][0]) self.importer.setDropIndexes(cb_model[cb_index][0])
else: else:
self.importer.setDropIndexes("auto") 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.lab_info.set_text("Importing")
self.importer.addBulkImportImportFileOrDir(self.inputFile,filter=hhc) self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename)
self.importer.setCallHud(False) self.importer.setCallHud(False)
starttime = time() starttime = time()
(stored, dups, partial, errs, ttime) = self.importer.runImport() (stored, dups, partial, errs, ttime) = self.importer.runImport()
@ -175,11 +175,9 @@ class GuiBulkImport():
# ComboBox - filter # ComboBox - filter
self.cbfilter = gtk.combo_box_new_text() self.cbfilter = gtk.combo_box_new_text()
self.cbfilter.append_text("passthrough") for w in self.config.hhcs:
self.cbfilter.append_text("BetfairToFpdb") print w
self.cbfilter.append_text("EverleafToFpdb") self.cbfilter.append_text(w)
self.cbfilter.append_text("FulltiltToFpdb")
self.cbfilter.append_text("PokerStarsToFpdb")
self.cbfilter.set_active(0) self.cbfilter.set_active(0)
self.table.attach(self.cbfilter, 3, 4, 2, 3, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK) self.table.attach(self.cbfilter, 3, 4, 2, 3, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK)
self.cbfilter.show() self.cbfilter.show()
@ -220,8 +218,8 @@ def main(argv=None):
help="Input file in quiet mode") help="Input file in quiet mode")
parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True, parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True,
help="don't start gui; deprecated (just give a filename with -f).") help="don't start gui; deprecated (just give a filename with -f).")
parser.add_option("-c", "--convert", dest="filtername", default="passthrough", metavar="FILTER", parser.add_option("-c", "--convert", dest="filtername", default="PokerStars", metavar="FILTER",
help="Conversion filter (*passthrough, FullTiltToFpdb, PokerStarsToFpdb, EverleafToFpdb)") help="Conversion filter (*Full Tilt Poker, PokerStars, Everleaf)")
parser.add_option("-x", "--failOnError", action="store_true", default=False, parser.add_option("-x", "--failOnError", action="store_true", default=False,
help="If this option is passed it quits when it encounters any error") 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", parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int",
@ -256,7 +254,7 @@ def main(argv=None):
importer = fpdb_import.Importer(False,settings, config) importer = fpdb_import.Importer(False,settings, config)
importer.setDropIndexes("auto") importer.setDropIndexes("auto")
importer.setFailOnError(options.failOnError) 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.setCallHud(False)
importer.runImport() importer.runImport()
importer.clearFileList() importer.clearFileList()

View File

@ -39,8 +39,83 @@ except:
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
import Filters
class GuiGraphViewer (threading.Thread): class GuiGraphViewer (threading.Thread):
def __init__(self, db, settings, querylist, config, debug=True):
"""Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.conf = config
filters_display = { "Heroes" : True,
"Sites" : True,
"Games" : True,
"Limits" : True,
"Dates" : True,
"Button1" : True,
"Button2" : True
}
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 = self.filters.get_vbox()
self.graphBox = gtk.VBox(False, 0)
self.hpane = gtk.HPaned()
self.hpane.pack1(self.leftPanelBox)
self.hpane.pack2(self.graphBox)
self.mainHBox.add(self.hpane)
self.fig = None
#self.exportButton.set_sensitive(False)
self.fig = Figure(figsize=(5,4), dpi=100)
self.canvas = None
self.mainHBox.show_all()
#################################
#
# 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
#################################
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainHBox return self.mainHBox
@ -59,11 +134,15 @@ class GuiGraphViewer (threading.Thread):
sitenos = [] sitenos = []
playerids = [] playerids = []
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
# Which sites are selected? # Which sites are selected?
for site in self.sites: for site in sites:
if self.sites[site] == True: if sites[site] == True:
sitenos.append(self.siteid[site]) sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (self.heroes[site],)) self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
result = self.db.cursor.fetchall() result = self.db.cursor.fetchall()
if len(result) == 1: if len(result) == 1:
playerids.append(result[0][0]) playerids.append(result[0][0])
@ -73,18 +152,20 @@ class GuiGraphViewer (threading.Thread):
print "No sites selected - defaulting to PokerStars" print "No sites selected - defaulting to PokerStars"
sitenos = [2] sitenos = [2]
if not playerids: if not playerids:
print "No player ids found" print "No player ids found"
return return
if not limits:
print "No limits found"
return
#Set graph properties #Set graph properties
self.ax = self.fig.add_subplot(111) self.ax = self.fig.add_subplot(111)
#Get graph data from DB #Get graph data from DB
starttime = time() starttime = time()
line = self.getRingProfitGraph(playerids, sitenos) line = self.getRingProfitGraph(playerids, sitenos, limits)
print "Graph generated in: %s" %(time() - starttime) print "Graph generated in: %s" %(time() - starttime)
self.ax.set_title("Profit graph for ring games") self.ax.set_title("Profit graph for ring games")
@ -114,33 +195,32 @@ class GuiGraphViewer (threading.Thread):
self.exportButton.set_sensitive(True) self.exportButton.set_sensitive(True)
#end of def showClicked #end of def showClicked
def getRingProfitGraph(self, names, sites): def getRingProfitGraph(self, names, sites, limits):
tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
# print "DEBUG: getRingProfitGraph" # print "DEBUG: getRingProfitGraph"
start_date, end_date = self.__get_dates() start_date, end_date = self.filters.getDates()
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 #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. # and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829) # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names)) nametest = str(tuple(names))
sitetest = str(tuple(sites)) sitetest = str(tuple(sites))
limittest = str(tuple(limits))
nametest = nametest.replace("L", "") nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")") nametest = nametest.replace(",)",")")
sitetest = sitetest.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 #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("<player_test>", nametest)
tmp = tmp.replace("<site_test>", sitetest) tmp = tmp.replace("<site_test>", sitetest)
tmp = tmp.replace("<startdate_test>", start_date) tmp = tmp.replace("<startdate_test>", start_date)
tmp = tmp.replace("<enddate_test>", end_date) tmp = tmp.replace("<enddate_test>", end_date)
tmp = tmp.replace("<limit_test>", limittest)
# print "DEBUG: sql query:" #print "DEBUG: sql query:"
# print tmp #print tmp
self.cursor.execute(tmp) self.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit) #returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall() winnings = self.db.cursor.fetchall()
@ -153,136 +233,6 @@ class GuiGraphViewer (threading.Thread):
return line/100 return line/100
#end of def getRingProfitGraph #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): def exportGraph (self, widget, data):
if self.fig is None: if self.fig is None:
return # Might want to disable export button until something has been generated. return # Might want to disable export button until something has been generated.
@ -303,112 +253,4 @@ class GuiGraphViewer (threading.Thread):
#TODO: This asks for a directory but will take a filename and overwrite it. #TODO: This asks for a directory but will take a filename and overwrite it.
self.fig.savefig(self.exportDir, format="png") self.fig.savefig(self.exportDir, format="png")
def __init__(self, db, settings, querylist, config, debug=True):
"""Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.conf = config
self.sites = {}
self.siteid = {}
self.heroes = {}
# 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.mainHBox = gtk.HBox(False, 0)
self.mainHBox.show()
self.leftPanelBox = gtk.VBox(False, 0)
self.graphBox = gtk.VBox(False, 0)
self.hpane = gtk.HPaned()
self.hpane.pack1(self.leftPanelBox)
self.hpane.pack2(self.graphBox)
self.hpane.show()
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.fig = Figure(figsize=(5,4), dpi=100)
self.canvas = None
#################################
#
# 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
#################################

View File

@ -23,146 +23,43 @@ import os
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
import Filters
import FpdbSQLQueries import FpdbSQLQueries
class GuiPlayerStats (threading.Thread): 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, db, config, querylist, debug=True):
self.debug=debug self.debug=debug
self.conf=config self.conf=config
# create new db connection to avoid conflicts with other threads # create new db connection to avoid conflicts with other threads
self.fdb = fpdb_db.fpdb_db() self.db = fpdb_db.fpdb_db()
self.fdb.do_connect(self.conf) self.db.do_connect(self.conf)
self.cursor=self.fdb.cursor self.cursor=self.db.cursor
self.sql = querylist self.sql = querylist
self.activesite = None settings = {}
self.buttongroup = None 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" : False,
"Dates" : False,
"Button1" : False,
"Button2" : False
}
self.filters = Filters.Filters(db, settings, config, querylist, display = filters_display)
self.heroes = {}
self.stat_table = None self.stat_table = None
self.stats_frame = None self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0) self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show() 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 = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0) statsFrame.set_label_align(0.0, 0.0)
statsFrame.show() statsFrame.show()
@ -172,6 +69,92 @@ class GuiPlayerStats (threading.Thread):
self.fillStatsFrame(self.stats_frame) self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame) statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(playerFrame) self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(statsFrame) self.main_hbox.pack_start(statsFrame)
def get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
def fillStatsFrame(self, vbox):
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
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
self.createStatsTable(vbox, playerids, sitenos)
def createStatsTable(self, vbox, playerids, sitenos):
tmp = self.sql.query['playerStats']
nametest = str(tuple(playerids))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
tmp = tmp.replace("<player_test>", nametest)
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.db.db.commit()
#end def fillStatsFrame(self, vbox):

View File

@ -23,180 +23,43 @@ import os
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
import Filters
import FpdbSQLQueries import FpdbSQLQueries
class GuiPositionalStats (threading.Thread): class GuiPositionalStats (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 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"
tmp = self.sql.query['playerStatsByPosition']
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", "Position", "#", "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 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
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, db, config, querylist, debug=True):
self.debug=debug self.debug=debug
self.conf=config self.conf=config
# create new db connection to avoid conflicts with other threads # create new db connection to avoid conflicts with other threads
self.fdb = fpdb_db.fpdb_db() self.db = fpdb_db.fpdb_db()
self.fdb.do_connect(self.conf) self.db.do_connect(self.conf)
self.cursor=self.fdb.cursor self.cursor=self.db.cursor
self.sql = querylist self.sql = querylist
self.activesite = None settings = {}
self.buttongroup = None 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" : False,
"Dates" : False,
"Button1" : False,
"Button2" : False
}
self.filters = Filters.Filters(db, settings, config, querylist, display = filters_display)
self.heroes = {}
self.stat_table = None self.stat_table = None
self.stats_frame = None self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0) self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show() 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 = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0) statsFrame.set_label_align(0.0, 0.0)
statsFrame.show() statsFrame.show()
@ -206,6 +69,97 @@ class GuiPositionalStats (threading.Thread):
self.fillStatsFrame(self.stats_frame) self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame) statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(playerFrame) self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(statsFrame) self.main_hbox.pack_start(statsFrame)
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):
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
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
self.createStatsTable(vbox, playerids, sitenos)
def createStatsTable(self, vbox, playerids, sitenos):
tmp = self.sql.query['playerStatsByPosition']
nametest = str(tuple(playerids))
nametest = nametest.replace("L", "")
nametest = nametest.replace(",)",")")
tmp = tmp.replace("<player_test>", nametest)
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")
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.db.db.commit()
#end def fillStatsFrame(self, vbox):

View File

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

View File

@ -112,7 +112,9 @@ class HandHistoryConverter():
def start(self): def start(self):
"""process a hand at a time from the input specified by in_path. """process a hand at a time from the input specified by in_path.
If in follow mode, wait for more data to turn up. If in follow mode, wait for more data to turn up.
Otherwise, finish at eof...""" Otherwise, finish at eof...
"""
starttime = time.time() starttime = time.time()
if not self.sanityCheck(): if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check" print "Cowardly refusing to continue after failed sanity check"
@ -137,7 +139,11 @@ Otherwise, finish at eof..."""
def tailHands(self): def tailHands(self):
"""Generator of handTexts from a tailed file: """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 if self.in_path == '-': raise StopIteration
interval = 1.0 # seconds to sleep between reads for new data interval = 1.0 # seconds to sleep between reads for new data
fd = codecs.open(self.in_path,'r', self.codepage) 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: else:
# yield hands # yield hands
data = data + newdata data = data + newdata
result = self.re_SplitHands.split(data) result = self.re_TailSplitHands.split(data)
result = iter(result) result = iter(result)
data = '' data = ''
# --x data (- is bit of splitter, x is paragraph) yield,...,keep # --x data (- is bit of splitter, x is paragraph) yield,...,keep

View File

@ -27,7 +27,8 @@ class PokerStars(HandHistoryConverter):
# Static regexes # 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_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_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_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_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
@ -72,7 +73,7 @@ follow : whether to tail -f the input"""
["ring", "hold", "pl"], ["ring", "hold", "pl"],
["ring", "hold", "fl"], ["ring", "hold", "fl"],
["ring", "stud", "fl"], ["ring", "stud", "fl"],
["ring", "draw", "fl"], #["ring", "draw", "fl"],
["ring", "omaha", "pl"] ["ring", "omaha", "pl"]
] ]

329
pyfpdb/UltimateBetToFpdb.py Executable file
View File

@ -0,0 +1,329 @@
#!/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"
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

@ -382,7 +382,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.window.connect("destroy", self.destroy) self.window.connect("destroy", self.destroy)
self.window.set_title("Free Poker DB - v%s or higher" % (VERSION, )) self.window.set_title("Free Poker DB - v%s or higher" % (VERSION, ))
self.window.set_border_width(1) 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.window.set_resizable(True)
self.menu_items = ( self.menu_items = (
@ -450,7 +450,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None) self.tab_main_help(None, None)
self.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.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show() self.status_bar.show()

View File

@ -58,13 +58,14 @@ class Importer:
self.cursor = None self.cursor = None
self.filelist = {} self.filelist = {}
self.dirlist = {} self.dirlist = {}
self.siteIds = {}
self.addToDirList = {} self.addToDirList = {}
self.removeFromFileList = {} # to remove deleted files self.removeFromFileList = {} # to remove deleted files
self.monitor = False 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.lines = None
self.faobs = None #File as one big string self.faobs = None # File as one big string
self.pos_in_file = {} # dict to remember how far we have read in the file self.pos_in_file = {} # dict to remember how far we have read in the file
#Set defaults #Set defaults
self.callHud = self.config.get_import_parameters().get("callFpdbHud") self.callHud = self.config.get_import_parameters().get("callFpdbHud")
@ -110,20 +111,32 @@ class Importer:
def addImportFile(self, filename, site = "default", filter = "passthrough"): def addImportFile(self, filename, site = "default", filter = "passthrough"):
#TODO: test it is a valid file -> put that in config!! #TODO: test it is a valid file -> put that in config!!
self.filelist[filename] = [site] + [filter] 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. # 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""" """Add a file or directory for bulk import"""
filter = self.config.hhcs[site].converter
# Bulk import never monitors # Bulk import never monitors
# if directory, add all files in it. Otherwise add single file. # if directory, add all files in it. Otherwise add single file.
# TODO: only add sane files? # TODO: only add sane files?
if os.path.isdir(inputPath): if os.path.isdir(inputPath):
for subdir in os.walk(inputPath): for subdir in os.walk(inputPath):
for file in subdir[2]: 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: else:
self.addImportFile(inputPath, site="default", filter=filter) self.addImportFile(inputPath, site=site, filter=filter)
#Add a directory of files to filelist #Add a directory of files to filelist
#Only one import directory per site supported. #Only one import directory per site supported.
#dirlist is a hash of lists: #dirlist is a hash of lists:
@ -194,7 +207,7 @@ class Importer:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1]) self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
for file in self.filelist: for file in self.filelist:
if os.path.exists(file): if os.path.exists(file):
stat_info = os.stat(file) stat_info = os.stat(file)
try: try:
lastupdate = self.updated[file] lastupdate = self.updated[file]
@ -214,8 +227,8 @@ class Importer:
#self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) #self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
else: else:
removeFromFileList[file] = True removeFromFileList[file] = True
self.addToDirList = filter(lambda x: self.addImportDirectory(x, True, self.addToDirList[x][0], self.addToDirList[x][1]), self.addToDirList) self.addToDirList = filter(lambda x: self.addImportDirectory(x, True, self.addToDirList[x][0], self.addToDirList[x][1]), self.addToDirList)
for file in self.removeFromFileList: for file in self.removeFromFileList:
if file in self.filelist: if file in self.filelist:
del self.filelist[file] del self.filelist[file]
@ -295,10 +308,9 @@ class Importer:
print "TODO: implement importing tournament summaries" print "TODO: implement importing tournament summaries"
#self.faobs = readfile(inputFile) #self.faobs = readfile(inputFile)
#self.parseTourneyHistory() #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 startpos = 0
stored = 0 #counter stored = 0 #counter
@ -306,61 +318,30 @@ class Importer:
partial = 0 #counter partial = 0 #counter
errors = 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 for i in xrange (len(self.lines)):
if len(self.lines[i]) < 2: if (len(self.lines[i])<2): #Wierd way to detect for '\r\n' or '\n'
endpos = i endpos=i
hand = self.lines[startpos:endpos] hand=self.lines[startpos:endpos]
if len(hand[0]) < 2: if (len(hand[0])<2):
hand = hand[1:] 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 if (len(hand)<3):
#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:
pass 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. #TODO: This is ugly - we didn't actually find the start of the
elif hand[0].endswith(" (partial)"): #partial hand - do nothing # hand with the outer loop so we test again...
partial += 1 else:
elif "Seat" not in hand[1] and "Seat" not in hand[2] and "Seat" not in hand[3]: isTourney=fpdb_simple.isTourney(hand[0])
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])
if not isTourney: if not isTourney:
hand = fpdb_simple.filterAnteBlindFold(site,hand) hand = fpdb_simple.filterAnteBlindFold(hand)
self.hand=hand self.hand=hand
try: try:
handsId = fpdb_parse_logic.mainParser(self.settings['db-backend'], self.fdb.db 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() self.fdb.db.commit()
stored += 1 stored += 1
if self.callHud: if self.callHud:
#print "call to HUD here. handsId:",handsId #print "call to HUD here. handsId:",handsId
@ -368,10 +349,11 @@ class Importer:
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError: except fpdb_simple.DuplicateError:
duplicates += 1 duplicates += 1
self.fdb.db.rollback()
except (ValueError), fe: except (ValueError), fe:
errors += 1 errors += 1
self.printEmailErrorMessage(errors, file, hand) self.printEmailErrorMessage(errors, file, hand)
if (self.settings['failOnError']): if (self.settings['failOnError']):
self.fdb.db.commit() #dont remove this, in case hand processing was cancelled. self.fdb.db.commit() #dont remove this, in case hand processing was cancelled.
raise raise
@ -381,26 +363,25 @@ class Importer:
errors += 1 errors += 1
self.printEmailErrorMessage(errors, file, hand) self.printEmailErrorMessage(errors, file, hand)
#fe.printStackTrace() #todo: get stacktrace
self.fdb.db.rollback() self.fdb.db.rollback()
if self.settings['failOnError']: if self.settings['failOnError']:
self.fdb.db.commit() #dont remove this, in case hand processing was cancelled. self.fdb.db.commit() #dont remove this, in case hand processing was cancelled.
raise raise
if self.settings['minPrint']: if self.settings['minPrint']:
if not ((stored+duplicates+partial+errors) % self.settings['minPrint']): if not ((stored+duplicates+errors) % self.settings['minPrint']):
print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors print "stored:", stored, "duplicates:", duplicates, "errors:", errors
if self.settings['handCount']: if self.settings['handCount']:
if ((stored+duplicates+partial+errors) >= self.settings['handCount']): if ((stored+duplicates+errors) >= self.settings['handCount']):
if not self.settings['quiet']: if not self.settings['quiet']:
print "quitting due to reaching the amount of hands to be imported" 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) sys.exit(0)
startpos = endpos startpos = endpos
ttime = time() - starttime 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 not stored:
if duplicates: if duplicates:
@ -411,16 +392,11 @@ class Importer:
else: else:
print "failed to read a single hand from file:", inputFile print "failed to read a single hand from file:", inputFile
handsId=0 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.fdb.db.commit()
self.handsId=handsId self.handsId=handsId
return (stored, duplicates, partial, errors, ttime) 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): def printEmailErrorMessage(self, errors, filename, line):
traceback.print_exc(file=sys.stderr) 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." print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."

View File

@ -21,11 +21,11 @@ import fpdb_simple
import fpdb_save_to_db import fpdb_save_to_db
#parses a holdem hand #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]) category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud" base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud"
#part 0: create the empty arrays #part 0: create the empty arrays
lineTypes = [] #char, valid values: header, name, cards, action, win, rake, ignore lineTypes = [] #char, valid values: header, name, cards, action, win, rake, ignore
lineStreets = [] #char, valid values: (predeal, preflop, flop, turn, river) lineStreets = [] #char, valid values: (predeal, preflop, flop, turn, river)
@ -34,9 +34,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
#part 1: read hand no and check for duplicate #part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0]) siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
handStartTime = fpdb_simple.parseHandStartTime(hand[0], site) handStartTime = fpdb_simple.parseHandStartTime(hand[0])
siteID = fpdb_simple.recogniseSiteID(cursor, site)
#print "parse logic, siteID:",siteID,"site:",site
isTourney = fpdb_simple.isTourney(hand[0]) isTourney = fpdb_simple.isTourney(hand[0])
smallBlindLine = 0 smallBlindLine = 0
@ -46,11 +44,9 @@ def mainParser(backend, db, cursor, site, category, hand, config):
smallBlindLine = i smallBlindLine = i
break break
#print "small blind line:",smallBlindLine #print "small blind line:",smallBlindLine
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney) gametypeID = fpdb_simple.recogniseGametypeID(backend, db, cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney)
if isTourney: if isTourney:
if site != "ps":
raise fpdb_simple.FpdbError("tourneys are only supported on PS right now")
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0]) siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0]) buyin = fpdb_simple.parseBuyin(hand[0])
fee = fpdb_simple.parseFee(hand[0]) fee = fpdb_simple.parseFee(hand[0])
@ -61,10 +57,10 @@ def mainParser(backend, db, cursor, site, category, hand, config):
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0]) rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(cursor, siteID, buyin, fee, knockout, rebuyOrAddon) tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(cursor, siteID, buyin, fee, knockout, rebuyOrAddon)
fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo) 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 #part 2: classify lines by type (e.g. cards, action, win, sectionchange) and street
fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets) fpdb_simple.classifyLines(hand, category, lineTypes, lineStreets)
@ -74,10 +70,10 @@ def mainParser(backend, db, cursor, site, category, hand, config):
for i, line in enumerate(hand): for i, line in enumerate(hand):
if lineTypes[i] == "name": if lineTypes[i] == "name":
seatLines.append(line) seatLines.append(line)
names = fpdb_simple.parseNames(seatLines) names = fpdb_simple.parseNames(seatLines)
playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID)
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines, site) tmp = fpdb_simple.parseCashesAndSeatNos(seatLines)
startCashes = tmp['startCashes'] startCashes = tmp['startCashes']
seatNos = tmp['seatNos'] 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 #part 4: take appropriate action for each line based on linetype
for i, line in enumerate(hand): for i, line in enumerate(hand):
if lineTypes[i] == "cards": 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": #if category=="studhilo":
# print "hand[i]:", hand[i] # print "hand[i]:", hand[i]
# print "cardValues:", cardValues # print "cardValues:", cardValues
# print "cardSuits:", cardSuits # print "cardSuits:", cardSuits
elif lineTypes[i] == "action": 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": elif lineTypes[i] == "win":
fpdb_simple.parseWinLine(line, site, names, winnings, isTourney) fpdb_simple.parseWinLine(line, names, winnings, isTourney)
elif lineTypes[i] == "rake": elif lineTypes[i] == "rake":
totalRake = 0 if isTourney else fpdb_simple.parseRake(line) totalRake = 0 if isTourney else fpdb_simple.parseRake(line)
fpdb_simple.splitRake(winnings, rakes, totalRake) fpdb_simple.splitRake(winnings, rakes, totalRake)
elif lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore": elif lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore":
pass pass
elif lineTypes[i]=="ante": elif lineTypes[i]=="ante":
fpdb_simple.parseAnteLine(line, site, isTourney, names, antes) fpdb_simple.parseAnteLine(line, isTourney, names, antes)
elif lineTypes[i]=="table": elif lineTypes[i]=="table":
tableResult=fpdb_simple.parseTableLine(site, base, line) tableResult=fpdb_simple.parseTableLine(base, line)
else: else:
raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i]) raise fpdb_simple.FpdbError("unrecognised lineType:"+lineTypes[i])
if site == "ftp":
tableResult = fpdb_simple.parseTableLine(site, base, hand[0])
maxSeats = tableResult['maxSeats'] maxSeats = tableResult['maxSeats']
tableName = tableResult['tableName'] tableName = tableResult['tableName']
#print "before part5, antes:", antes #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, )) cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, ))
limit_type = cursor.fetchone()[0] 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) totalWinnings = sum(winnings)
@ -141,7 +134,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes
, allIns, actionTypeByNo, winnings, totalWinnings, None , allIns, actionTypeByNo, winnings, totalWinnings, None
, actionTypes, actionAmounts, antes) , actionTypes, actionAmounts, antes)
if isTourney: if isTourney:
ranks = map(lambda x: 0, names) # create an array of 0's equal to the length of names ranks = map(lambda x: 0, names) # create an array of 0's equal to the length of names
payin_amounts = fpdb_simple.calcPayin(len(names), buyin, fee) payin_amounts = fpdb_simple.calcPayin(len(names), buyin, fee)
@ -165,7 +158,7 @@ def mainParser(backend, db, cursor, site, category, hand, config):
, allIns, actionAmounts, actionNos, hudImportData, maxSeats , allIns, actionAmounts, actionNos, hudImportData, maxSeats
, tableName, seatNos) , tableName, seatNos)
else: else:
raise fpdb_simple.FpdbError("unrecognised category") # it's impossible to get here, but w/e raise fpdb_simple.FpdbError("unrecognised category")
else: else:
if base == "hold": if base == "hold":
result = fpdb_save_to_db.ring_holdem_omaha( 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 , actionAmounts, actionNos, hudImportData, maxSeats, tableName
, seatNos) , seatNos)
else: else:
raise fpdb_simple.FpdbError ("unrecognised category") # also impossible to get here raise fpdb_simple.FpdbError ("unrecognised category")
db.commit() db.commit()
return result return result
#end def mainParser #end def mainParser

View File

@ -32,8 +32,7 @@ saveActions = True # set this to False to avoid storing action data
# Pros: speeds up imports # Pros: speeds up imports
# Cons: no action data is saved, so you need to keep the hand histories # Cons: no action data is saved, so you need to keep the hand histories
# variance not available on stats page # variance not available on stats page
# no graphs # : No graphs
#stores a stud/razz hand into the database #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 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 ,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes

View File

@ -187,10 +187,10 @@ def prepareBulkImport(fdb):
# mod to use tab_col for index name? # mod to use tab_col for index name?
try: try:
fdb.cursor.execute( "drop index %s_%s_idx" % (idx['tab'],idx['col']) ) fdb.cursor.execute( "drop index %s_%s_idx" % (idx['tab'],idx['col']) )
print "drop index %s_%s_idx" % (idx['tab'],idx['col']) print "drop index %s_%s_idx" % (idx['tab'],idx['col'])
#print "dropped pg index ", idx['tab'], idx['col'] #print "dropped pg index ", idx['tab'], idx['col']
except: except:
print "! failed drop index %s_%s_idx" % (idx['tab'],idx['col']) print "! failed drop index %s_%s_idx" % (idx['tab'],idx['col'])
else: else:
print "Only MySQL and Postgres supported so far" print "Only MySQL and Postgres supported so far"
return -1 return -1
@ -392,10 +392,6 @@ def checkPositions(positions):
""" verify positions are valid """ """ verify positions are valid """
if any(not (p == "B" or p == "S" or (p >= 0 and p <= 9)) for p in positions): if any(not (p == "B" or p == "S" or (p >= 0 and p <= 9)) for p in positions):
raise FpdbError("invalid position '"+p+"' found in checkPositions") raise FpdbError("invalid position '"+p+"' found in checkPositions")
# for p in positions:
# if not (p == "B" or p == "S" or (p >= 0 and p <= 9)):
# raise FpdbError("invalid position '" + p + "' found in checkPositions")
### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB ### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB
### eric - position 8 could be valid - if only one blind is posted, but there's still 10 people, ie a sitout is present, and the small is dead... ### eric - position 8 could be valid - if only one blind is posted, but there's still 10 people, ie a sitout is present, and the small is dead...
@ -446,7 +442,7 @@ def classifyLines(hand, category, lineTypes, lineStreets):
currentStreet=3 currentStreet=3
elif line.startswith("*** 7") or line == "*** RIVER ***": elif line.startswith("*** 7") or line == "*** RIVER ***":
lineTypes.append("ignore") lineTypes.append("ignore")
currentStreet=4 currentStreet=4
elif isWinLine(line): elif isWinLine(line):
lineTypes.append("win") lineTypes.append("win")
elif line.startswith("Total pot ") and "Rake" in line: elif line.startswith("Total pot ") and "Rake" in line:
@ -462,7 +458,7 @@ def classifyLines(hand, category, lineTypes, lineStreets):
lineStreets.append(currentStreet) lineStreets.append(currentStreet)
#end def classifyLines #end def classifyLines
def convert3B4B(site, category, limit_type, actionTypes, actionAmounts): def convert3B4B(category, limit_type, actionTypes, actionAmounts):
"""calculates the actual bet amounts in the given amount array and changes it accordingly.""" """calculates the actual bet amounts in the given amount array and changes it accordingly."""
for i in xrange(len(actionTypes)): for i in xrange(len(actionTypes)):
for j in xrange(len(actionTypes[i])): for j in xrange(len(actionTypes[i])):
@ -572,7 +568,7 @@ def fillCardArrays(player_count, base, category, card_values, card_suits):
#filters out a player that folded before paying ante or blinds. This should be called #filters out a player that folded before paying ante or blinds. This should be called
#before calling the actual hand parser. manipulates hand, no return. #before calling the actual hand parser. manipulates hand, no return.
def filterAnteBlindFold(site,hand): def filterAnteBlindFold(hand):
#todo: this'll only get rid of one ante folder, not multiple ones #todo: this'll only get rid of one ante folder, not multiple ones
#todo: in tourneys this should not be removed but #todo: in tourneys this should not be removed but
#print "start of filterAnteBlindFold" #print "start of filterAnteBlindFold"
@ -610,7 +606,7 @@ def stripEOLspaces(str):
return str.rstrip() return str.rstrip()
#removes useless lines as well as trailing spaces #removes useless lines as well as trailing spaces
def filterCrap(site, hand, isTourney): def filterCrap(hand, isTourney):
#remove two trailing spaces at end of line #remove two trailing spaces at end of line
hand = [line.rstrip() for line in hand] hand = [line.rstrip() for line in hand]
@ -643,18 +639,8 @@ def filterCrap(site, hand, isTourney):
hand[i] = False hand[i] = False
elif hand[i].endswith("is disconnected"): elif hand[i].endswith("is disconnected"):
hand[i] = False hand[i] = False
elif hand[i].endswith(" is feeling angry"): elif hand[i].find(" is low with [")!=-1:
hand[i] = False hand[i] = False
elif hand[i].endswith(" is feeling confused"):
hand[i] = False
elif hand[i].endswith(" is feeling happy"):
hand[i] = False
elif hand[i].endswith(" is feeling normal"):
hand[i] = False
elif " is low with [" in hand[i]:
hand[i] = False
#elif (hand[i].find("-max Seat #")!=-1 and hand[i].find(" is the button")!=-1):
# toRemove.append(hand[i])
elif hand[i].endswith(" mucks"): elif hand[i].endswith(" mucks"):
hand[i] = False hand[i] = False
elif hand[i].endswith(": mucks hand"): elif hand[i].endswith(": mucks hand"):
@ -680,13 +666,9 @@ def filterCrap(site, hand, isTourney):
hand[i] = False hand[i] = False
elif "joins the table at seat " in hand[i]: elif "joins the table at seat " in hand[i]:
hand[i] = False hand[i] = False
elif (hand[i].endswith(" sits down")):
hand[i] = False
elif (hand[i].endswith("leaves the table")): elif (hand[i].endswith("leaves the table")):
hand[i] = False hand[i] = False
elif (hand[i].endswith(" stands up")): elif "is high with " in hand[i]:
hand[i] = False
elif "is high with" in hand[i]:
hand[i] = False hand[i] = False
elif hand[i].endswith("doesn't show hand"): elif hand[i].endswith("doesn't show hand"):
hand[i] = False hand[i] = False
@ -696,11 +678,9 @@ def filterCrap(site, hand, isTourney):
hand[i] = False hand[i] = False
elif hand[i] == "Betting is capped": elif hand[i] == "Betting is capped":
hand[i] = False hand[i] = False
#site specific variable position filter elif (hand[i].find(" said, \"")!=-1):
elif 'said, "' in hand[i]:
hand[i] = False
elif site == "ftp" and ":" in hand[i] and "Seat " not in hand[i] and ": Table" not in hand[i]: # FTP chat
hand[i] = False hand[i] = False
if isTourney and not hand[i] == False: if isTourney and not hand[i] == False:
if (hand[i].endswith(" is sitting out") and (not hand[i].startswith("Seat "))): if (hand[i].endswith(" is sitting out") and (not hand[i].startswith("Seat "))):
hand[i] = False hand[i] = False
@ -781,7 +761,7 @@ def isWinLine(line):
#end def isWinLine #end def isWinLine
#returns the amount of cash/chips put into the put in the given action line #returns the amount of cash/chips put into the put in the given action line
def parseActionAmount(line, atype, site, isTourney): def parseActionAmount(line, atype, isTourney):
#if (line.endswith(" and is all-in")): #if (line.endswith(" and is all-in")):
# line=line[:-14] # line=line[:-14]
#elif (line.endswith(", and is all in")): #elif (line.endswith(", and is all in")):
@ -795,19 +775,14 @@ def parseActionAmount(line, atype, site, isTourney):
if atype == "fold" or atype == "check": if atype == "fold" or atype == "check":
amount = 0 amount = 0
elif atype == "unbet": elif atype == "unbet":
if site == "ftp": pos1 = line.find("$") + 1
pos1 = line.find("$") + 1 if pos1 == 0:
pos2 = line.find(" returned to") pos1 = line.find("(") + 1
amount = float2int(line[pos1:pos2]) pos2 = line.find(")")
elif site == "ps": amount = float2int(line[pos1:pos2])
pos1 = line.find("$") + 1 elif atype == "bet" and line.find(": raises $")!=-1 and line.find("to $")!=-1:
if pos1 == 0: pos=line.find("to $")+4
pos1 = line.find("(") + 1 amount=float2int(line[pos:])
pos2 = line.find(")")
amount = float2int(line[pos1:pos2])
elif atype == "bet" and site == "ps" and line.find(": raises $")!=-1 and line.find("to $")!=-1:
pos = line.find("to $")+4
amount = float2int(line[pos:])
else: else:
if not isTourney: if not isTourney:
pos = line.rfind("$")+1 pos = line.rfind("$")+1
@ -828,7 +803,7 @@ def parseActionAmount(line, atype, site, isTourney):
#doesnt return anything, simply changes the passed arrays action_types and #doesnt return anything, simply changes the passed arrays action_types and
# action_amounts. For stud this expects numeric streets (3-7), for # action_amounts. For stud this expects numeric streets (3-7), for
# holdem/omaha it expects predeal, preflop, flop, turn or river # holdem/omaha it expects predeal, preflop, flop, turn or river
def parseActionLine(site, base, isTourney, line, street, playerIDs, names, action_types, allIns, action_amounts, actionNos, actionTypeByNo): def parseActionLine(base, isTourney, line, street, playerIDs, names, action_types, allIns, action_amounts, actionNos, actionTypeByNo):
if street == "predeal" or street == "preflop": if street == "predeal" or street == "preflop":
street = 0 street = 0
elif street == "flop": elif street == "flop":
@ -847,7 +822,7 @@ def parseActionLine(site, base, isTourney, line, street, playerIDs, names, actio
(line, allIn) = goesAllInOnThisLine(line) (line, allIn) = goesAllInOnThisLine(line)
atype = parseActionType(line) atype = parseActionType(line)
playerno = recognisePlayerNo(line, names, atype) playerno = recognisePlayerNo(line, names, atype)
amount = parseActionAmount(line, atype, site, isTourney) amount = parseActionAmount(line, atype, isTourney)
action_types[street][playerno].append(atype) action_types[street][playerno].append(atype)
allIns[street][playerno].append(allIn) allIns[street][playerno].append(allIn)
@ -899,7 +874,7 @@ def parseActionType(line):
#end def parseActionType #end def parseActionType
#parses the ante out of the given line and checks which player paid it, updates antes accordingly. #parses the ante out of the given line and checks which player paid it, updates antes accordingly.
def parseAnteLine(line, site, isTourney, names, antes): def parseAnteLine(line, isTourney, names, antes):
for i, name in enumerate(names): for i, name in enumerate(names):
if line.startswith(name.encode("latin-1")): if line.startswith(name.encode("latin-1")):
pos = line.rfind("$") + 1 pos = line.rfind("$") + 1
@ -925,7 +900,7 @@ def parseBuyin(topline):
#parses a card line and changes the passed arrays accordingly #parses a card line and changes the passed arrays accordingly
#todo: reorganise this messy method #todo: reorganise this messy method
def parseCardLine(site, category, street, line, names, cardValues, cardSuits, boardValues, boardSuits): def parseCardLine(category, street, line, names, cardValues, cardSuits, boardValues, boardSuits):
if line.startswith("Dealt to") or " shows [" in line or "mucked [" in line: if line.startswith("Dealt to") or " shows [" in line or "mucked [" in line:
playerNo = recognisePlayerNo(line, names, "card") #anything but unbet will be ok for that string playerNo = recognisePlayerNo(line, names, "card") #anything but unbet will be ok for that string
@ -999,7 +974,7 @@ def parseCardLine(site, category, street, line, names, cardValues, cardSuits, bo
raise FpdbError ("unrecognised line:"+line) raise FpdbError ("unrecognised line:"+line)
#end def parseCardLine #end def parseCardLine
def parseCashesAndSeatNos(lines, site): def parseCashesAndSeatNos(lines):
"""parses the startCashes and seatNos of each player out of the given lines and returns them as a dictionary of two arrays""" """parses the startCashes and seatNos of each player out of the given lines and returns them as a dictionary of two arrays"""
cashes = [] cashes = []
seatNos = [] seatNos = []
@ -1010,10 +985,7 @@ def parseCashesAndSeatNos(lines, site):
pos1=lines[i].rfind("($")+2 pos1=lines[i].rfind("($")+2
if pos1==1: #for tourneys - it's 1 instead of -1 due to adding 2 above if pos1==1: #for tourneys - it's 1 instead of -1 due to adding 2 above
pos1=lines[i].rfind("(")+1 pos1=lines[i].rfind("(")+1
if (site=="ftp"): pos2=lines[i].find(" in chips")
pos2=lines[i].rfind(")")
elif (site=="ps"):
pos2=lines[i].find(" in chips")
cashes.append(float2int(lines[i][pos1:pos2])) cashes.append(float2int(lines[i][pos1:pos2]))
return {'startCashes':cashes, 'seatNos':seatNos} return {'startCashes':cashes, 'seatNos':seatNos}
#end def parseCashesAndSeatNos #end def parseCashesAndSeatNos
@ -1027,7 +999,7 @@ def parseFee(topline):
#end def parsefee #end def parsefee
#returns a datetime object with the starttime indicated in the given topline #returns a datetime object with the starttime indicated in the given topline
def parseHandStartTime(topline, site): def parseHandStartTime(topline):
#convert x:13:35 to 0x:13:35 #convert x:13:35 to 0x:13:35
counter=0 counter=0
while counter < 10: while counter < 10:
@ -1038,41 +1010,25 @@ def parseHandStartTime(topline, site):
counter += 1 counter += 1
isUTC=False isUTC=False
if site=="ftp": if topline.find("UTC")!=-1:
# Full Tilt Sit'n'Go pos1 = topline.find("-")+2
# 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 pos2 = topline.find("UTC")
# Cash Game: tmp=topline[pos1:pos2]
# Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09 isUTC=True
# Full Tilt Poker Game #9468383505: Table Bike (deep 6) - $0.05/$0.10 - No Limit Hold'em - 5:09:36 ET - 2008/12/13
pos = topline.find(" ", len(topline)-26)+1
tmp = topline[pos:]
rexx = '(?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+) ET [\- ]+(?P<YEAR>[0-9]{4})\/(?P<MON>[0-9]{2})\/(?P<DAY>[0-9]{2})'
m = re.search(rexx,tmp)
result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
elif site=="ps":
if topline.find("UTC")!=-1:
pos1 = topline.find("-")+2
pos2 = topline.find("UTC")
tmp=topline[pos1:pos2]
isUTC=True
else:
tmp=topline
#print "parsehandStartTime, tmp:", tmp
pos = tmp.find("-")+2
tmp = tmp[pos:]
#Need to match either
# 2008/09/07 06:23:14 ET or
# 2008/08/17 - 01:14:43 (ET) or
# 2008/11/12 9:33:31 CET [2008/11/12 3:33:31 ET]
rexx = '(?P<YEAR>[0-9]{4})\/(?P<MON>[0-9]{2})\/(?P<DAY>[0-9]{2})[\- ]+(?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)'
m = re.search(rexx,tmp)
#print "year:", int(m.group('YEAR')), "month", int(m.group('MON')), "day", int(m.group('DAY')), "hour", int(m.group('HR')), "minute", int(m.group('MIN')), "second", int(m.group('SEC'))
result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
else: else:
raise FpdbError("invalid site in parseHandStartTime") tmp=topline
#print "parsehandStartTime, tmp:", tmp
pos = tmp.find("-")+2
tmp = tmp[pos:]
#Need to match either
# 2008/09/07 06:23:14 ET or
# 2008/08/17 - 01:14:43 (ET) or
# 2008/11/12 9:33:31 CET [2008/11/12 3:33:31 ET]
rexx = '(?P<YEAR>[0-9]{4})\/(?P<MON>[0-9]{2})\/(?P<DAY>[0-9]{2})[\- ]+(?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)'
m = re.search(rexx,tmp)
result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
if (site=="ftp" or site=="ps") and not isUTC: #these use US ET if not isUTC: #these use US ET
result+=datetime.timedelta(hours=5) result+=datetime.timedelta(hours=5)
return result return result
@ -1109,7 +1065,7 @@ def parsePositions(hand, names):
if bb != -1: if bb != -1:
bb = recognisePlayerNo(bb, names, "bet") bb = recognisePlayerNo(bb, names, "bet")
# print "sb = ", sb, "bb = ", bb # print "sb = ", sb, "bb = ", bb
if bb == sb: # if big and small are same, then don't duplicate the small if bb == sb: # if big and small are same, then don't duplicate the small
sbExists = False sbExists = False
sb = -1 sb = -1
@ -1138,7 +1094,7 @@ def parsePositions(hand, names):
while positions[i] < 0 and i != sb: while positions[i] < 0 and i != sb:
positions[i] = 9 positions[i] = 9
i -= 1 i -= 1
### RHH - Changed to set the null seats before BB to "9" ### RHH - Changed to set the null seats before BB to "9"
if sbExists: if sbExists:
i = sb-1 i = sb-1
else: else:
@ -1158,7 +1114,7 @@ def parsePositions(hand, names):
print "parsePositions names:",names print "parsePositions names:",names
print "result:",positions print "result:",positions
raise FpdbError ("failed to read positions") raise FpdbError ("failed to read positions")
# print str(positions), "\n" # print str(positions), "\n"
return positions return positions
#end def parsePositions #end def parsePositions
@ -1176,42 +1132,15 @@ def parseSiteHandNo(topline):
return topline[pos1:pos2] return topline[pos1:pos2]
#end def parseSiteHandNo #end def parseSiteHandNo
def parseTableLine(site, base, line): def parseTableLine(base, line):
"""returns a dictionary with maxSeats and tableName""" """returns a dictionary with maxSeats and tableName"""
if site=="ps": pos1=line.find('\'')+1
pos1=line.find('\'')+1 pos2=line.find('\'', pos1)
pos2=line.find('\'', pos1) #print "table:",line[pos1:pos2]
#print "table:",line[pos1:pos2] pos3=pos2+2
pos3=pos2+2 pos4=line.find("-max")
pos4=line.find("-max") #print "seats:",line[pos3:pos4]
#print "seats:",line[pos3:pos4] return {'maxSeats':int(line[pos3:pos4]), 'tableName':line[pos1:pos2]}
return {'maxSeats':int(line[pos3:pos4]), 'tableName':line[pos1:pos2]}
elif site=="ftp":
pos1=line.find("Table ")+6
pos2=line.find("-")-1
if base=="hold":
maxSeats=9
elif base=="stud":
maxSeats=8
if line.find("6 max")!=-1:
maxSeats=6
elif line.find("4 max")!=-1:
maxSeats=4
elif line.find("heads up")!=-1:
maxSeats=2
tableName = line[pos1:pos2]
for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
' \(speed\)',
' no all-in', ' fast', ',', ' 50BB min', '\s+$']:
tableName = re.sub(pattern, '', tableName)
tableName = tableName.rstrip()
return {'maxSeats':maxSeats, 'tableName':tableName}
else:
raise FpdbError("invalid site ID")
#end def parseTableLine #end def parseTableLine
#returns the hand no assigned by the poker site #returns the hand no assigned by the poker site
@ -1223,24 +1152,18 @@ def parseTourneyNo(topline):
#end def parseTourneyNo #end def parseTourneyNo
#parses a win/collect line. manipulates the passed array winnings, no explicit return #parses a win/collect line. manipulates the passed array winnings, no explicit return
def parseWinLine(line, site, names, winnings, isTourney): def parseWinLine(line, names, winnings, isTourney):
#print "parseWinLine: line:",line #print "parseWinLine: line:",line
for i,n in enumerate(names): for i,n in enumerate(names):
n = n.encode("latin-1") n = n.encode("latin-1")
if line.startswith(n): if line.startswith(n):
if isTourney: if isTourney:
pos1 = line.rfind("collected ") + 10 pos1 = line.rfind("collected ") + 10
if site == "ftp": pos2 = line.find(" ", pos1)
pos2 = line.find(")", pos1) winnings[i]+=int(line[pos1:pos2])
elif site == "ps":
pos2 = line.find(" ", pos1)
winnings[i] += int(line[pos1:pos2])
else: else:
pos1 = line.rfind("$") + 1 pos1 = line.rfind("$") + 1
if site == "ftp": pos2 = line.find(" ", pos1)
pos2 = line.find(")", pos1)
elif site == "ps":
pos2 = line.find(" ", pos1)
winnings[i] += float2int(line[pos1:pos2]) winnings[i] += float2int(line[pos1:pos2])
#end def parseWinLine #end def parseWinLine
@ -1286,10 +1209,7 @@ def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, c
pos1=pos2+2 pos1=pos2+2
if isTourney: if isTourney:
pos1-=1 pos1-=1
if (site_id==1): #ftp pos2=topline.find(")")
pos2=topline.find(" ", pos1)
elif (site_id==2): #ps
pos2=topline.find(")")
if pos2<=pos1: if pos2<=pos1:
pos2=topline.find(")", pos1) pos2=topline.find(")", pos1)
@ -1472,28 +1392,6 @@ def recognisePlayerNo(line, names, atype):
raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype) raise FpdbError ("failed to recognise player in: "+line+" atype:"+atype)
#end def recognisePlayerNo #end def recognisePlayerNo
#returns the site abbreviation for the given site
def recogniseSite(line):
if (line.startswith("Full Tilt Poker") or line.startswith("FullTiltPoker")):
return "ftp"
elif (line.startswith("PokerStars")):
return "ps"
else:
raise FpdbError("failed to recognise site, line:"+line)
#end def recogniseSite
#returns the ID of the given site
def recogniseSiteID(cursor, site):
if (site=="ftp"):
return 1
#cursor.execute("SELECT id FROM Sites WHERE name = ('Full Tilt Poker')")
elif (site=="ps"):
return 2
#cursor.execute("SELECT id FROM Sites WHERE name = ('PokerStars')")
else:
raise FpdbError("invalid site in recogniseSiteID: "+site)
return cursor.fetchall()[0][0]
#end def recogniseSiteID
#removes trailing \n from the given array #removes trailing \n from the given array
def removeTrailingEOL(arr): def removeTrailingEOL(arr):
@ -1810,7 +1708,7 @@ sure to also change the following storage method and table_viewer.prepare_data i
myStreet0_3B4BDone = True myStreet0_3B4BDone = True
#steal calculations #steal calculations
if base == "hold": if base=="hold":
if len(player_ids)>=3: # no point otherwise # was 5, use 3 to match pokertracker definition if len(player_ids)>=3: # no point otherwise # was 5, use 3 to match pokertracker definition
if positions[player]==1: if positions[player]==1:
if firstPfRaiserId==player_ids[player] \ if firstPfRaiserId==player_ids[player] \
@ -2285,7 +2183,7 @@ def storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData):
except TypeError: except TypeError:
row=[] row=[]
if not row: if not row:
#print "new huddata row" #print "new huddata row"
doInsert=True doInsert=True
row=[] row=[]

View File

@ -46,7 +46,7 @@ def testGameInfo():
def testHandInfo(): 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) hhc = PokerStarsToFpdb.PokerStars(autostart=False)
h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test") h = HoldemOmahaHand(None, "PokerStars", gametype, text, builtFrom = "Test")
hhc.readHandInfo(h) hhc.readHandInfo(h)
@ -78,4 +78,4 @@ Table 'Caia II' 6-max Seat #2 is the button"""
assert h.buttonpos == '2' # TODO: should this be an int? assert h.buttonpos == '2' # TODO: should this be an int?
assert h.starttime == (2008,11 , 15, 19, 22, 21, 5, 320, -1) assert h.starttime == (2008,11 , 15, 19, 22, 21, 5, 320, -1)

View File

@ -16,25 +16,24 @@ def testPokerStarsHHDate():
datetime.datetime(2008,9,7,11,23,14)) datetime.datetime(2008,9,7,11,23,14))
) )
#def testFullTiltHHDate(self): 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" 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" 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" 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") result = fpdb_simple.parseHandStartTime(sitngo1,"ftp")
# self.failUnless(result==datetime.datetime(2009,1,29,05,07,45), assert 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")
# result = fpdb_simple.parseHandStartTime(cash1,"ftp") assert result==datetime.datetime(2008,12,9,14,40,20)
# self.failUnless(result==datetime.datetime(2008,12,9,14,40,20), result = fpdb_simple.parseHandStartTime(cash2,"ftp")
# "Date incorrect, expected: 2008-12-09 14:40:20 got: " + str(result)) assert result==datetime.datetime(2008,12,13,10,9,36)
# 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))
# def testTableDetection(self): def testTableDetection():
# result = Tables.clean_title("French (deep)") result = Tables.clean_title("French (deep)")
# self.failUnless(result == "French", "French (deep) parsed incorrectly. Expected 'French' got: " + str(result)) assert result == "French"
# result = ("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx") 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