diff --git a/create-release.sh b/create-release.sh index d4b7bfad..a4fa00d5 100755 --- a/create-release.sh +++ b/create-release.sh @@ -22,6 +22,8 @@ rm pyfpdb/*.pyc mkdir fpdb-$1 cp -R docs fpdb-$1/ cp -R pyfpdb fpdb-$1/ +rm fpdb-$1/HUD_config.* +cp pyfpdb/HUD_config.xml.example fpdb-$1/ cp -R regression-test fpdb-$1/ cp -R utils fpdb-$1/ cd fpdb-$1 diff --git a/docs/known-bugs-and-planned-features.txt b/docs/known-bugs-and-planned-features.txt index 7ff766cb..6d1c2689 100644 --- a/docs/known-bugs-and-planned-features.txt +++ b/docs/known-bugs-and-planned-features.txt @@ -2,9 +2,8 @@ todolist (db=database, imp=importer, tv=tableviewer) Everything is subject to change and the order does not indicate priority. Patches for any of these or other features are very welcome, see readme-overview.txt for contacts. Please also see db-todo.txt -alpha3 (release 1-2Sep?) +alpha3 (release 15Sep) ====== -(fixed by ray) auto import only runs on one file per start find correct sf logo link windows integrated installer @@ -20,7 +19,7 @@ fill check-/call-raise cache fields printhand each and the 2/3 relevant printplayerflags respectively on ps-lhe-ring-successful-steal-by-cutoff.txt and ps-lhe-ring-call-3B-preflop-cb-no2b.txt -alpha4 (release 8Sep?) +alpha4 (release 22-29Sep) ====== change to savannah? implement steal and positions in stud @@ -33,9 +32,25 @@ change tabledesign VALIGN finish updating filelist finish todos in git instructions debian/ubuntu package http://www.debian.org/doc/maint-guide/ch-start.en.html +howto remote DB +move all user docs to webpage before beta =========== +No Full Tilt support in HUD +No river stats for stud games +hole/board cards are not correctly stored in the db for stud games +HORSE (and presumably other mixed games) hand history files not handled correctly +HUD stat windows are too big on Windows +HUD task bar entries on Windows won't go away +Some MTTs won't import (rebuys??) +Many STTs won't import +MTT/STT not tested in HUD +HUD stats not aggregated +Player names with non-Latin chars throw warnings in HUD +HUD doesn't start when fpdb is started from the Windows "Start Menu" +Exiting HUD on Windows doesn't properly clean up stat windows + finish bringing back tourney ebuild: USE gtk, set permissions in it, copy docs to correct place, use games eclass or whatever to get games group notice, git-ebuild, get it into sunrise make hud display W$SD etc as fraction. @@ -49,6 +64,7 @@ split hud data generation into separate for loops and make it more efficient fix bug that sawFlop/Turn/River/CBChance/etc gets miscalculated if someone is allin - might as well add all-in recognition for this make 3 default HUD configs (easy (4-5 fields), advanced (10ish fields), crazy (20 or so)) make it work with postgres +gentoo ebuild: USE postgresql expand instructions for profile file maybe remove siteId from gametypes ?change most cache fields to bigint to allow extremely big databases in excess of 2 or 4 million hands per stake and position? diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py old mode 100644 new mode 100755 index 849d595a..55b8ed2b --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -23,6 +23,7 @@ Handles HUD configuration files. ######################################################################## # Standard Library modules +import shutil import xml.dom.minidom from xml.dom.minidom import Node @@ -38,7 +39,7 @@ class Layout: for i in range(1, len(self.location)): temp = temp + "(%d,%d)" % self.location[i] - return temp + return temp + "\n" class Site: def __init__(self, node): @@ -81,7 +82,7 @@ class Stat: pass def __str__(self): - temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click) + temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s, popup = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click, self.popup) return temp class Game: @@ -99,6 +100,7 @@ class Game: stat.col = int( stat_node.getAttribute("col") ) stat.tip = stat_node.getAttribute("tip") stat.click = stat_node.getAttribute("click") + stat.popup = stat_node.getAttribute("popup") self.stats[stat.stat_name] = stat @@ -150,15 +152,31 @@ class Mucked: temp = temp + ' ' + key + " = " + value + "\n" return temp +class Popup: + def __init__(self, node): + self.name = node.getAttribute("pu_name") + self.pu_stats = [] + for stat_node in node.getElementsByTagName('pu_stat'): + self.pu_stats.append(stat_node.getAttribute("pu_stat_name")) + + def __str__(self): + temp = "Popup = " + self.name + "\n" + for stat in self.pu_stats: + temp = temp + " " + stat + return temp + "\n" + class Config: def __init__(self, file = 'HUD_config.xml'): doc = xml.dom.minidom.parse(file) + self.doc = doc + self.file = file self.supported_sites = {} self.supported_games = {} self.supported_databases = {} self.mucked_windows = {} + self.popup_windows = {} # s_sites = doc.getElementsByTagName("supported_sites") for site_node in doc.getElementsByTagName("site"): @@ -180,6 +198,47 @@ class Config: mw = Mucked(node = mw_node) self.mucked_windows[mw.name] = mw + s_dbs = doc.getElementsByTagName("popup_windows") + for pu_node in doc.getElementsByTagName("pu"): + pu = Popup(node = pu_node) + self.popup_windows[pu.name] = pu + + def get_site_node(self, site): + for site_node in self.doc.getElementsByTagName("site"): + if site_node.getAttribute("site_name") == site: + return site_node + + def get_layout_node(self, site_node, layout): + for layout_node in site_node.getElementsByTagName("layout"): + if int( layout_node.getAttribute("max") ) == int( layout ): + return layout_node + + def get_location_node(self, layout_node, seat): + for location_node in layout_node.getElementsByTagName("location"): + if int( location_node.getAttribute("seat") ) == int( seat ): + return location_node + + def save(self, file = None): + if not file == None: + f = open(file, 'w') + self.doc.writexml(f) + f.close() + else: + shutil.move(self.file, self.file+".backup") + f = open(self.file, 'w') + self.doc.writexml(f) + f.close + + def edit_layout(self, site_name, max, width = None, height = None, + fav_seat = None, locations = None): + site_node = self.get_site_node(site_name) + layout_node = self.get_layout_node(site_node, max) + for i in range(1, max + 1): + location_node = self.get_location_node(layout_node, i) + location_node.setAttribute("x", str( locations[i-1][0] )) + location_node.setAttribute("y", str( locations[i-1][1] )) + self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] ) + if __name__== "__main__": c = Config() @@ -208,3 +267,12 @@ if __name__== "__main__": print c.mucked_windows[w] print "----------- END MUCKED WINDOW FORMATS -----------" + + print "\n----------- POPUP WINDOW FORMATS -----------" + for w in c.popup_windows.keys(): + print c.popup_windows[w] + + print "----------- END MUCKED WINDOW FORMATS -----------" + + c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) + c.save(file="testout.xml") \ No newline at end of file diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 8d10e8a1..d634e134 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -24,6 +24,7 @@ Create and manage the database objects. # postmaster -D /var/lib/pgsql/data # Standard Library modules +import sys # pyGTK modules @@ -32,7 +33,7 @@ import Configuration import SQL # pgdb database module for posgres via DB-API -#import pgdb +import psycopg2 # pgdb uses pyformat. is that fixed or an option? # mysql bindings @@ -41,7 +42,7 @@ import MySQLdb class Database: def __init__(self, c, db_name, game): if c.supported_databases[db_name].db_server == 'postgresql': - self.connection = pgdb.connect(dsn = c.supported_databases[db_name].db_ip, + self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip, user = c.supported_databases[db_name].db_user, password = c.supported_databases[db_name].db_pass, database = c.supported_databases[db_name].db_name) @@ -59,12 +60,12 @@ class Database: self.type = c.supported_databases[db_name].db_type self.sql = SQL.Sql(game = game, type = self.type) - def close(self): - self.connection.close + def close_connection(self): + self.connection.close() def get_table_name(self, hand_id): c = self.connection.cursor() - c.execute(self.sql.query['get_table_name'], (hand_id)) + c.execute(self.sql.query['get_table_name'], (hand_id, )) row = c.fetchone() return row @@ -90,7 +91,21 @@ class Database: c.execute(self.sql.query['get_hand_info'], new_hand_id) return c.fetchall() +# def get_cards(self, hand): +# this version is for the PTrackSv2 db +# c = self.connection.cursor() +# c.execute(self.sql.query['get_cards'], hand) +# colnames = [desc[0] for desc in c.description] +# cards = {} +# for row in c.fetchall(): +# s_dict = {} +# for name, val in zip(colnames, row): +# s_dict[name] = val +# cards[s_dict['seat_number']] = s_dict +# return (cards) + def get_cards(self, hand): +# this version is for the fpdb db c = self.connection.cursor() c.execute(self.sql.query['get_cards'], hand) colnames = [desc[0] for desc in c.description] @@ -101,12 +116,14 @@ class Database: s_dict[name] = val cards[s_dict['seat_number']] = s_dict return (cards) - - def get_stats_from_hand(self, hand, hero): + + def get_stats_from_hand(self, hand, player_id = False): c = self.connection.cursor() + if not player_id: player_id = "%" # get the players in the hand and their seats - c.execute(self.sql.query['get_players_from_hand'], (hand)) +# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id)) + c.execute(self.sql.query['get_players_from_hand'], (hand, )) names = {} seats = {} for row in c.fetchall(): @@ -114,6 +131,7 @@ class Database: seats[row[0]] = row[1] # now get the stats +# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) c.execute(self.sql.query['get_stats_from_hand'], (hand, hand)) colnames = [desc[0] for desc in c.description] stat_dict = {} @@ -137,7 +155,8 @@ class Database: if __name__=="__main__": c = Configuration.Config() - db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem +# db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem + db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem # db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz # db_connection = Database(c, 'ptracks', 'razz') # postgres print "database connection object = ", db_connection.connection @@ -149,7 +168,16 @@ if __name__=="__main__": hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') print "nutOmatic is id_player = %d" % hero + stat_dict = db_connection.get_stats_from_hand(h) + for p in stat_dict.keys(): + print p, " ", stat_dict[p] + + print "nutOmatics stats:" stat_dict = db_connection.get_stats_from_hand(h, hero) for p in stat_dict.keys(): print p, " ", stat_dict[p] - db_connection.close + + db_connection.close_connection + + print "press enter to continue" + sys.stdin.readline() diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 42f5bf6d..5081022f 100644 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -33,12 +33,12 @@ class GuiAutoImport (threading.Thread): current_path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", - action=gtk.FILE_CHOOSER_ACTION_OPEN, + action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) #dia_chooser.set_current_folder(pathname) dia_chooser.set_filename(current_path) #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import - + response = dia_chooser.run() if response == gtk.RESPONSE_OK: #print dia_chooser.get_filename(), 'selected' @@ -50,8 +50,18 @@ class GuiAutoImport (threading.Thread): def do_import(self): """Callback for timer to do an import iteration.""" - fpdb_import.import_file_dict(self, self.settings) - return(1) + for file in os.listdir(self.path): + if os.path.isdir(file): + print "AutoImport is not recursive - please select the final directory in which the history files are" + else: + self.inputFile = os.path.join(self.path, file) + stat_info = os.stat(self.inputFile) + if not self.import_files.has_key(self.inputFile) or stat_info.st_mtime > self.import_files[self.inputFile]: + fpdb_import.import_file_dict(self, self.settings, callHud = True) + self.import_files[self.inputFile] = stat_info.st_mtime + + print "GuiAutoImport.import_dir done" + return True def startClicked(self, widget, data): """runs when user clicks start on auto import tab""" @@ -59,24 +69,48 @@ class GuiAutoImport (threading.Thread): # Check to see if we have an open file handle to the HUD and open one if we do not. # bufsize = 1 means unbuffered # We need to close this file handle sometime. + +# TODO: Allow for importing from multiple dirs - REB 29AUG2008 +# As presently written this function does nothing if there is already a pipe open. +# That is not correct. It should open another dir for importing while piping the +# results to the same pipe. This means that self.path should be a a list of dirs +# to watch. try: #uhhh, I don't this this is the best way to check for the existence of an attr getattr(self, "pipe_to_hud") except AttributeError: - cwd = os.getcwd() - command = os.path.join(cwd, 'HUD_main.py') - self.pipe_to_hud = subprocess.Popen(command, bufsize = 1, stdin = subprocess.PIPE) - - self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) - for file in os.listdir(self.path): - if os.path.isdir(file): - print "AutoImport is not recursive - please select the final directory in which the history files are" + if os.name == 'nt': + command = "python HUD_main.py" + " %s" % (self.database) + bs = 0 # windows is not happy with line buffing here + self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, + universal_newlines=True) else: - self.inputFile=self.path+os.sep+file - self.do_import() - print "GuiAutoImport.import_dir done" + cwd = os.getcwd() + command = os.path.join(cwd, 'HUD_main.py') + bs = 1 + self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, + universal_newlines=True) +# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, +# universal_newlines=True) +# command = command + " %s" % (self.database) +# print "command = ", command +# self.pipe_to_hud = os.popen(command, 'w') + self.path=self.pathTBuffer.get_text(self.pathTBuffer.get_start_iter(), self.pathTBuffer.get_end_iter()) + +# Iniitally populate the self.import_files dict, which keeps mtimes for the files watched + + self.import_files = {} + for file in os.listdir(self.path): + if os.path.isdir(file): + pass # skip subdirs for now + else: + inputFile = os.path.join(self.path, file) + stat_info = os.stat(inputFile) + self.import_files[inputFile] = stat_info.st_mtime + + self.do_import() - interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) - gobject.timeout_add(interval*1000, self.do_import) + interval=int(self.intervalTBuffer.get_text(self.intervalTBuffer.get_start_iter(), self.intervalTBuffer.get_end_iter())) + gobject.timeout_add(interval*1000, self.do_import) #end def GuiAutoImport.browseClicked def get_vbox(self): @@ -140,3 +174,22 @@ class GuiAutoImport (threading.Thread): self.mainVBox.add(self.startButton) self.startButton.show() #end of GuiAutoImport.__init__ +if __name__== "__main__": + def destroy(*args): # call back for terminating the main eventloop + gtk.main_quit() + + settings = {} + settings['db-host'] = "192.168.1.100" + settings['db-user'] = "mythtv" + settings['db-password'] = "mythtv" + settings['db-databaseName'] = "fpdb" + settings['hud-defaultInterval'] = 10 + settings['hud-defaultPath'] = 'C:/Program Files/PokerStars/HandHistory/nutOmatic' + settings['imp-callFpdbHud'] = True + + i = GuiAutoImport(settings) + main_window = gtk.Window() + main_window.connect("destroy", destroy) + main_window.add(i.mainVBox) + main_window.show() + gtk.main() diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 99ad9f5c..15bbd034 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -26,15 +26,17 @@ Main for FreePokerTools HUD. # to do adjust for preferred seat # to do allow window resizing # to do hud to echo, but ignore non numbers -# to do kill a hud # to do no hud window for hero -# to do single click to display detailed stats # to do things to add to config.xml # to do font and size +# to do bg and fg color +# to do opacity # Standard Library modules import sys import os +import thread +import Queue # pyGTK modules import pygtk @@ -49,19 +51,23 @@ import Hud # global dict for keeping the huds hud_dict = {} + db_connection = 0; config = 0; def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() - -def process_new_hand(source, condition): + +def process_new_hand(new_hand_id, db_name): # there is a new hand_id to be processed # read the hand_id from stdin and strip whitespace - new_hand_id = sys.stdin.readline() - new_hand_id = new_hand_id.rstrip() - db_connection = Database.Database(config, 'fpdb', 'holdem') + global hud_dict + for h in hud_dict.keys(): + if hud_dict[h].deleted: + del(hud_dict[h]) + + db_connection = Database.Database(config, db_name, 'temp') (table_name, max, poker_game) = db_connection.get_table_name(new_hand_id) # if a hud for this table exists, just update it if hud_dict.has_key(table_name): @@ -71,29 +77,61 @@ def process_new_hand(source, condition): table_windows = Tables.discover(config) for t in table_windows.keys(): if table_windows[t].name == table_name: - hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_connection) + hud_dict[table_name] = Hud.Hud(table_windows[t], max, poker_game, config, db_name) hud_dict[table_name].create(new_hand_id, config) hud_dict[table_name].update(new_hand_id, db_connection, config) break # print "table name \"%s\" not identified, no hud created" % (table_name) - return(1) + db_connection.close_connection() + return(1) + +def check_stdin(db_name): + try: + hand_no = dataQueue.get(block=False) + process_new_hand(hand_no, db_name) + except: + pass + + return True + +def read_stdin(source, condition, db_name): + new_hand_id = sys.stdin.readline() + process_new_hand(new_hand_id, db_name) + return True + +def producer(): # This is the thread function + while True: + hand_no = sys.stdin.readline() # reads stdin + dataQueue.put(hand_no) # and puts result on the queue if __name__== "__main__": - - if not os.name == 'posix': - print "This version of the HUD only works with Linux or compatible.\nHUD exiting." + print "HUD_main starting" + + try: + db_name = sys.argv[1] + except: + db_name = 'fpdb-p' + print "Using db name = ", db_name + + config = Configuration.Config() +# db_connection = Database.Database(config, 'fpdb', 'holdem') + + if os.name == 'posix': + s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, read_stdin, db_name) + elif os.name == 'nt': + dataQueue = Queue.Queue() # shared global. infinite size + gobject.threads_init() # this is required + thread.start_new_thread(producer, ()) # starts the thread + gobject.timeout_add(1000, check_stdin, db_name) + else: + print "Sorry your operating system is not supported." sys.exit() main_window = gtk.Window() main_window.connect("destroy", destroy) - label = gtk.Label('Fake main window, blah blah, blah\nblah, blah') + label = gtk.Label('Closing this window will exit from the HUD.') main_window.add(label) + main_window.set_title("HUD Main Window") main_window.show_all() - config = Configuration.Config() - - db_connection = Database.Database(config, 'fpdb', 'holdem') - - s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) - gtk.main() diff --git a/pyfpdb/Hud.py b/pyfpdb/Hud.py old mode 100644 new mode 100755 index 2e0c7cdd..debfaa67 --- a/pyfpdb/Hud.py +++ b/pyfpdb/Hud.py @@ -22,6 +22,7 @@ Create and manage the hud overlays. ######################################################################## # Standard Library modules +import os # pyGTK modules import pygtk @@ -29,26 +30,85 @@ import gtk import pango import gobject +# win32 modules -- only imported on windows systems +if os.name == 'nt': + import win32gui + import win32con + # FreePokerTools modules import Tables # needed for testing only import Configuration import Stats import Mucked import Database +import HUD_main class Hud: - def __init__(self, table, max, poker_game, config, db_connection): + def __init__(self, table, max, poker_game, config, db_name): self.table = table self.config = config self.poker_game = poker_game self.max = max - self.db_connection = db_connection + self.db_name = db_name + self.deleted = False self.stat_windows = {} + self.popup_windows = {} self.font = pango.FontDescription("Sans 8") - +# Set up a main window for this this instance of the HUD + self.main_window = gtk.Window() +# self.window.set_decorated(0) + self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.main_window.set_keep_above(1) + self.main_window.set_title(table.name) + self.main_window.connect("destroy", self.kill_hud) + + self.ebox = gtk.EventBox() + self.label = gtk.Label("Close this window to\nkill the HUD for\n %s" % (table.name)) + self.main_window.add(self.ebox) + self.ebox.add(self.label) + self.main_window.move(self.table.x, self.table.y) + +# A popup window for the main window + self.menu = gtk.Menu() + self.item1 = gtk.MenuItem('Kill this HUD') + self.menu.append(self.item1) + self.item1.connect("activate", self.kill_hud) + self.item1.show() + self.item2 = gtk.MenuItem('Save Layout') + self.menu.append(self.item2) + self.item2.connect("activate", self.save_layout) + self.item2.show() + self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) + + self.main_window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.main_window) + + def on_button_press(self, widget, event): + if event.button == 3: + widget.popup(None, None, None, event.button, event.time) + return True + return False + + def kill_hud(self, args): + for k in self.stat_windows.keys(): + self.stat_windows[k].window.destroy() + self.main_window.destroy() + self.deleted = True + + def save_layout(self, *args): + new_layout = [] + for sw in self.stat_windows: + loc = self.stat_windows[sw].window.get_position() + new_loc = (loc[0] - self.table.x, loc[1] - self.table.y) + new_layout.append(new_loc) + print new_layout + self.config.edit_layout(self.table.site, self.table.max, locations = new_layout) + self.config.save() + def create(self, hand, config): # update this hud, to the stats and players as of "hand" # hand is the hand id of the most recent hand played at this table @@ -58,6 +118,7 @@ class Hud: for i in range(1, self.max + 1): (x, y) = config.supported_sites[self.table.site].layout[self.max].location[i] self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game], + parent = self, table = self.table, x = x, y = y, @@ -79,10 +140,10 @@ class Hud: # self.mucked_window.show_all() def update(self, hand, db, config): - stat_dict = db.get_stats_from_hand(hand, 3) + self.hand = hand # this is the last hand, so it is available later + stat_dict = db.get_stats_from_hand(hand) for s in stat_dict.keys(): -# for r in range(0, 2): -# for c in range(0, 3): + self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id'] for r in range(0, config.supported_games[self.poker_game].rows): for c in range(0, config.supported_games[self.poker_game].cols): number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) @@ -91,7 +152,33 @@ class Hud: number[3] + ", " + number[4] Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) # self.m.update(hand) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + class Stat_Window: def button_press_cb(self, widget, event, *args): @@ -101,7 +188,7 @@ class Stat_Window: if event.button == 1: # left button event if event.type == gtk.gdk.BUTTON_PRESS: # left button single click if self.sb_click > 0: return - self.sb_click = gobject.timeout_add(250, self.single_click) + self.sb_click = gobject.timeout_add(250, self.single_click, widget) elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click if self.sb_click > 0: gobject.source_remove(self.sb_click) @@ -109,17 +196,20 @@ class Stat_Window: self.double_click(widget, event, *args) if event.button == 2: # middle button event - print "middle button clicked" + pass +# print "middle button clicked" if event.button == 3: # right button event - print "right button clicked" + pass +# print "right button clicked" - def single_click(self): + def single_click(self, widget): # Callback from the timeout in the single-click finding part of the # button press call back. This needs to be modified to get all the # arguments from the call. - print "left button clicked" +# print "left button clicked" self.sb_click = 0 + Popup_window(widget, self) return False def double_click(self, widget, event, *args): @@ -136,13 +226,14 @@ class Stat_Window: top.set_decorated(1) top.move(x, y) - def __init__(self, game, table, seat, x, y, player_id, font): - self.game = game - self.table = table - self.x = x + table.x - self.y = y + table.y - self.player_id = player_id - self.sb_click = 0 + def __init__(self, parent, game, table, seat, x, y, player_id, font): + self.parent = parent # Hud object that this stat window belongs to + self.game = game # Configuration object for the curren + self.table = table # Table object where this is going + self.x = x + table.x # table.x and y are the location of the table + self.y = y + table.y # x and y are the location relative to table.x & y + self.player_id = player_id # looks like this isn't used ;) + self.sb_click = 0 # used to figure out button clicks self.window = gtk.Window() self.window.set_decorated(0) @@ -163,7 +254,7 @@ class Stat_Window: for c in range(self.game.cols): self.e_box[r].append( gtk.EventBox() ) Stats.do_tip(self.e_box[r][c], 'farts') - self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 1, ypadding = 1) + self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) self.label[r].append( gtk.Label('xxx') ) self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].connect("button_press_event", self.button_press_cb) @@ -172,10 +263,173 @@ class Stat_Window: self.window.realize self.window.move(self.x, self.y) self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() + +class Popup_window: + def __init__(self, parent, stat_window): + self.sb_click = 0 + +# create the popup window + self.window = gtk.Window() + self.window.set_decorated(0) + self.window.set_gravity(gtk.gdk.GRAVITY_STATIC) + self.window.set_keep_above(1) + self.window.set_title("popup") + self.window.set_property("skip-taskbar-hint", True) + self.window.set_transient_for(parent.get_toplevel()) + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.ebox = gtk.EventBox() + self.ebox.connect("button_press_event", self.button_press_cb) + self.lab = gtk.Label("stuff\nstuff\nstuff") + +# need an event box so we can respond to clicks + self.window.add(self.ebox) + self.ebox.add(self.lab) + self.window.realize + +# figure out the row, col address of the click that activated the popup + row = 0 + col = 0 + for r in range(0, stat_window.game.rows): + for c in range(0, stat_window.game.cols): + if stat_window.e_box[r][c] == parent: + row = r + col = c + break + +# figure out what popup format we're using + popup_format = "default" + for stat in stat_window.game.stats.keys(): + if stat_window.game.stats[stat].row == row and stat_window.game.stats[stat].col == col: + popup_format = stat_window.game.stats[stat].popup + break + +# get the list of stats to be presented from the config + stat_list = [] + for w in stat_window.parent.config.popup_windows.keys(): + if w == popup_format: + stat_list = stat_window.parent.config.popup_windows[w].pu_stats + break + +# get a database connection + db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp') + +# calculate the stat_dict and then create the text for the pu +# stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id) + stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand) + db_connection.close_connection() + + pu_text = "" + for s in stat_list: + number = Stats.do_stat(stat_dict, player = int(stat_window.player_id), stat = s) + pu_text += number[3] + "\n" + + self.lab.set_text(pu_text) + self.window.show_all() +# set_keep_above(1) for windows + if os.name == 'nt': self.topify_window(self.window) + + def button_press_cb(self, widget, event, *args): +# This handles all callbacks from button presses on the event boxes in +# the popup windows. There is a bit of an ugly kludge to separate single- +# and double-clicks. This is the same code as in the Stat_window class + if event.button == 1: # left button event + if event.type == gtk.gdk.BUTTON_PRESS: # left button single click + if self.sb_click > 0: return + self.sb_click = gobject.timeout_add(250, self.single_click, widget) + elif event.type == gtk.gdk._2BUTTON_PRESS: # left button double click + if self.sb_click > 0: + gobject.source_remove(self.sb_click) + self.sb_click = 0 + self.double_click(widget, event, *args) + + if event.button == 2: # middle button event + pass +# print "middle button clicked" + + if event.button == 3: # right button event + pass +# print "right button clicked" + + def single_click(self, widget): +# Callback from the timeout in the single-click finding part of the +# button press call back. This needs to be modified to get all the +# arguments from the call. + self.sb_click = 0 + self.window.destroy() + return False + + def double_click(self, widget, event, *args): + self.toggle_decorated(widget) + + def toggle_decorated(self, widget): + top = widget.get_toplevel() + (x, y) = top.get_position() + + if top.get_decorated(): + top.set_decorated(0) + top.move(x, y) + else: + top.set_decorated(1) + top.move(x, y) + + def topify_window(self, window): + """Set the specified gtk window to stayontop in MS Windows.""" + + def windowEnumerationHandler(hwnd, resultList): + '''Callback for win32gui.EnumWindows() to generate list of window handles.''' + resultList.append((hwnd, win32gui.GetWindowText(hwnd))) + + unique_name = 'unique name for finding this window' + real_name = window.get_title() + window.set_title(unique_name) + tl_windows = [] + win32gui.EnumWindows(windowEnumerationHandler, tl_windows) + + for w in tl_windows: + if w[1] == unique_name: + win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE) +# notify_id = (w[0], +# 0, +# win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, +# win32con.WM_USER+20, +# 0, +# '') +# win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, notify_id) +# + window.set_title(real_name) + if __name__== "__main__": main_window = gtk.Window() main_window.connect("destroy", destroy) @@ -187,15 +441,6 @@ if __name__== "__main__": tables = Tables.discover(c) db = Database.Database(c, 'fpdb', 'holdem') - for attr in dir(Stats): - if attr.startswith('__'): continue - if attr == 'Configuration' or attr == 'Database': continue - if attr == 'GInitiallyUnowned': continue -# print Stats.attr.__doc__ - - print Stats.vpip.__doc__ - - for t in tables: win = Hud(t, 8, c, db) # t.get_details() diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py index 292fd58d..0af06c65 100644 --- a/pyfpdb/Mucked.py +++ b/pyfpdb/Mucked.py @@ -105,7 +105,8 @@ class MuckedList: self.mucked_cards.update(model.get_value(iter, 0)) def update(self, new_hand_id): - info_row = self.db_connection.get_hand_info(new_hand_id) +# info_row = self.db_connection.get_hand_info(new_hand_id) + info_row = ((new_hand_id, "xxxx", 0), ) iter = self.liststore.append(info_row[0]) sel = self.treeview.get_selection() sel.select_iter(iter) @@ -156,11 +157,15 @@ class MuckedCards: self.grid.attach(self.grid_contents[(c, r)], c, c+1, r, r+1, xpadding = 1, ypadding = 1) self.parent.add(self.grid) - + + def translate_cards(self, old_cards): + pass + def update(self, new_hand_id): cards = self.db_connection.get_cards(new_hand_id) self.clear() + cards = self.translate_cards(cards) for c in cards.keys(): self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) @@ -224,7 +229,7 @@ if __name__== "__main__": return(True) config = Configuration.Config() - db_connection = Database.Database(config, 'PTrackSv2', 'razz') + db_connection = Database.Database(config, 'fpdb', '') main_window = gtk.Window() main_window.set_keep_above(True) @@ -236,4 +241,3 @@ if __name__== "__main__": s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) gtk.main() - diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index ffec6c65..072d478e 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -60,7 +60,7 @@ class Sql: FROM game_players WHERE game_id = %s AND player_id = 3 """ - + self.query['get_cards'] = """ select seat_number, @@ -173,10 +173,6 @@ class Sql: self.query['get_stats_from_hand'] = """ SELECT HudCache.playerId AS player_id, - HudCache.gametypeId AS gametypeId, - activeSeats AS n_active, - position AS position, - HudCache.tourneyTypeId AS tourneyTypeId, sum(HDs) AS n, sum(street0VPI) AS vpip, sum(street0Aggr) AS pfr, @@ -241,12 +237,18 @@ class Sql: AND Hands.gametypeId = HudCache.gametypeId GROUP BY HudCache.PlayerId """ +# AND PlayerId LIKE %s +# HudCache.gametypeId AS gametypeId, +# activeSeats AS n_active, +# position AS position, +# HudCache.tourneyTypeId AS tourneyTypeId, self.query['get_players_from_hand'] = """ SELECT HandsPlayers.playerId, seatNo, name FROM HandsPlayers INNER JOIN Players ON (HandsPlayers.playerId = Players.id) WHERE handId = %s """ +# WHERE handId = %s AND Players.id LIKE %s self.query['get_table_name'] = """ select tableName, maxSeats, category @@ -255,6 +257,31 @@ class Sql: and Gametypes.id = Hands.gametypeId """ + self.query['get_cards'] = """ + select + seatNo AS seat_number, + name AS screen_name, + card1Value, card1Suit, + card2Value, card2Suit, + card3Value, card3Suit, + card4Value, card4Suit, + card5Value, card5Suit, + card6Value, card6Suit, + card7Value, card7Suit + from HandsPlayers, Players + where handID = %s and HandsPlayers.playerId = Players.id + order by seatNo + """ + +# self.query['get_hand_info'] = """ +# SELECT +# game_id, +# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, +# total_won-total_bet AS net +# FROM game_players +# WHERE game_id = %s AND player_id = 3 +# """ + if __name__== "__main__": # just print the default queries and exit s = Sql(game = 'razz', type = 'ptracks') diff --git a/pyfpdb/Stats.py b/pyfpdb/Stats.py index b52378cb..3531c017 100644 --- a/pyfpdb/Stats.py +++ b/pyfpdb/Stats.py @@ -43,6 +43,7 @@ # 6 For each stat you make add a line to the __main__ function to test it. # Standard Library modules +#import sys # pyGTK modules import pygtk @@ -55,10 +56,6 @@ import Database def do_tip(widget, tip): widget.set_tooltip_text(tip) -def list_stats(): - for key in dir(): - print key - def do_stat(stat_dict, player = 24, stat = 'vpip'): return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) # OK, for reference the tuple returned by the stat is: @@ -72,9 +69,7 @@ def do_stat(stat_dict, player = 24, stat = 'vpip'): ########################################### # functions that return individual stats def vpip(stat_dict, player): - """ - Voluntarily put $ in the pot - """ + """ Voluntarily put $ in the pot.""" stat = 0.0 try: stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n']) @@ -94,6 +89,7 @@ def vpip(stat_dict, player): ) def pfr(stat_dict, player): + """ Preflop (3rd street) raise.""" stat = 0.0 try: stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n']) @@ -114,6 +110,7 @@ def pfr(stat_dict, player): ) def wtsd(stat_dict, player): + """ Went to SD when saw flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f']) @@ -134,6 +131,7 @@ def wtsd(stat_dict, player): ) def wmsd(stat_dict, player): + """ Won $ at showdown.""" stat = 0.0 try: stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd']) @@ -141,7 +139,7 @@ def wmsd(stat_dict, player): '%3.1f' % (100*stat) + '%', 'w=%3.1f' % (100*stat) + '%', 'wmsd=%3.1f' % (100*stat) + '%', - '(%d/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), + '(%f5.0/%d)' % (stat_dict[player]['wmsd'], stat_dict[player]['sd']), '% won money at showdown' ) except: @@ -154,6 +152,7 @@ def wmsd(stat_dict, player): ) def saw_f(stat_dict, player): + """ Saw flop/4th.""" try: num = float(stat_dict[player]['saw_f']) den = float(stat_dict[player]['n']) @@ -178,6 +177,7 @@ def saw_f(stat_dict, player): ) def n(stat_dict, player): + """ Number of hands played.""" try: return (stat_dict[player]['n'], '%d' % (stat_dict[player]['n']), @@ -187,15 +187,16 @@ def n(stat_dict, player): 'number hands seen' ) except: - return (stat_dict[player][0], - '%d' % (stat_dict[player][0]), - 'n=%d' % (stat_dict[player][0]), - 'n=%d' % (stat_dict[player][0]), - '(%d)' % (stat_dict[player][0]), + return (0, + '%d' % (0), + 'n=%d' % (0), + 'n=%d' % (0), + '(%d)' % (0), 'number hands seen' ) def fold_f(stat_dict, player): + """ Folded flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['fold_2'])/fold(stat_dict[player]['saw_f']) @@ -216,6 +217,7 @@ def fold_f(stat_dict, player): ) def steal(stat_dict, player): + """ Steal %.""" stat = 0.0 try: stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp']) @@ -236,6 +238,7 @@ def steal(stat_dict, player): ) def f_SB_steal(stat_dict, player): + """ Folded SB to steal.""" stat = 0.0 try: stat = float(stat_dict[player]['SBnotDef'])/float(stat_dict[player]['SBstolen']) @@ -256,6 +259,7 @@ def f_SB_steal(stat_dict, player): ) def f_BB_steal(stat_dict, player): + """ Folded BB to steal.""" stat = 0.0 try: stat = float(stat_dict[player]['BBnotDef'])/float(stat_dict[player]['BBstolen']) @@ -276,6 +280,7 @@ def f_BB_steal(stat_dict, player): ) def three_B_0(stat_dict, player): + """ Three bet preflop/3rd.""" stat = 0.0 try: stat = float(stat_dict[player]['TB_0'])/float(stat_dict[player]['TB_opp_0']) @@ -296,6 +301,7 @@ def three_B_0(stat_dict, player): ) def WMsF(stat_dict, player): + """ Won $ when saw flop/4th.""" stat = 0.0 try: stat = float(stat_dict[player]['w_w_s_1'])/float(stat_dict[player]['saw_1']) @@ -316,6 +322,7 @@ def WMsF(stat_dict, player): ) def a_freq_1(stat_dict, player): + """ Flop/4th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_1'])/float(stat_dict[player]['saw_f']) @@ -336,6 +343,7 @@ def a_freq_1(stat_dict, player): ) def a_freq_2(stat_dict, player): + """ Turn/5th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_2'])/float(stat_dict[player]['saw_2']) @@ -356,6 +364,7 @@ def a_freq_2(stat_dict, player): ) def a_freq_3(stat_dict, player): + """ River/6th aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_3'])/float(stat_dict[player]['saw_3']) @@ -376,6 +385,7 @@ def a_freq_3(stat_dict, player): ) def a_freq_4(stat_dict, player): + """ 7th street aggression frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['aggr_4'])/float(stat_dict[player]['saw_4']) @@ -389,13 +399,14 @@ def a_freq_4(stat_dict, player): except: return (stat, '%3.1f' % (0) + '%', - 'a1=%3.1f' % (0) + '%', - 'a_fq_1=%3.1f' % (0) + '%', + 'a4=%3.1f' % (0) + '%', + 'a_fq_4=%3.1f' % (0) + '%', '(%d/%d)' % (0, 0), 'Aggression Freq flop/4th' ) def cb_1(stat_dict, player): + """ Flop continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_1'])/float(stat_dict[player]['CB_opp_1']) @@ -416,6 +427,7 @@ def cb_1(stat_dict, player): ) def cb_2(stat_dict, player): + """ Turn continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_2'])/float(stat_dict[player]['CB_opp_2']) @@ -436,6 +448,7 @@ def cb_2(stat_dict, player): ) def cb_3(stat_dict, player): + """ River continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_3'])/float(stat_dict[player]['CB_opp_3']) @@ -456,6 +469,7 @@ def cb_3(stat_dict, player): ) def cb_4(stat_dict, player): + """ 7th street continuation bet.""" stat = 0.0 try: stat = float(stat_dict[player]['CB_4'])/float(stat_dict[player]['CB_opp_4']) @@ -476,6 +490,7 @@ def cb_4(stat_dict, player): ) def ffreq_1(stat_dict, player): + """ Flop/4th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_1'])/float(stat_dict[player]['was_raised_1']) @@ -496,6 +511,7 @@ def ffreq_1(stat_dict, player): ) def ffreq_2(stat_dict, player): + """ Turn/5th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_2'])/float(stat_dict[player]['was_raised_2']) @@ -516,6 +532,7 @@ def ffreq_2(stat_dict, player): ) def ffreq_3(stat_dict, player): + """ River/6th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_3'])/float(stat_dict[player]['was_raised_3']) @@ -536,6 +553,7 @@ def ffreq_3(stat_dict, player): ) def ffreq_4(stat_dict, player): + """ 7th fold frequency.""" stat = 0.0 try: stat = float(stat_dict[player]['f_freq_4'])/float(stat_dict[player]['was_raised_4']) @@ -559,7 +577,7 @@ if __name__== "__main__": c = Configuration.Config() db_connection = Database.Database(c, 'fpdb', 'holdem') h = db_connection.get_last_hand() - stat_dict = db_connection.get_stats_from_hand(h, 0) + stat_dict = db_connection.get_stats_from_hand(h) for player in stat_dict.keys(): print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip') @@ -587,13 +605,14 @@ if __name__== "__main__": print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') -# print "\n\nLegal stats:" -# for attr in dir(): -# if attr.startswith('__'): continue -# if attr == 'Configuration' or attr == 'Database': continue -# if attr == 'GInitiallyUnowned': continue -# print attr.__doc__ -# -# print vpip.__doc__ + print "\n\nLegal stats:" + for attr in dir(): + if attr.startswith('__'): continue + if attr in ("Configuration", "Database", "GInitiallyUnowned", "gtk", "pygtk", + "player", "c", "db_connection", "do_stat", "do_tip", "stat_dict", + "h"): continue + print attr, eval("%s.__doc__" % (attr)) +# print " " % (attr) + db_connection.close diff --git a/pyfpdb/Tables.py b/pyfpdb/Tables.py index 6c53a590..d7d9533d 100644 --- a/pyfpdb/Tables.py +++ b/pyfpdb/Tables.py @@ -26,8 +26,14 @@ of Table_Window objects representing the windows found. # Standard Library modules import os +import sys import re +# Win32 modules + +if os.name == 'nt': + import win32gui + # FreePokerTools modules import Configuration @@ -49,8 +55,24 @@ class Table_Window: table.tournament = 0 def discover(c): + if os.name == 'posix': + tables = discover_posix(c) + return tables + elif os.name == 'nt': + tables = discover_nt(c) + return tables + elif ox.name == 'mac': + tables = discover_mac(c) + return tables + else: tables = {} + + return(tables) + +def discover_posix(c): + """ Poker client table window finder for posix/Linux = XWindows.""" tables = {} for listing in os.popen('xwininfo -root -tree').readlines(): +# xwininfo -root -tree -id 0xnnnnn gets the info on a single window if re.search('Lobby', listing): continue if re.search('Instant Hand History', listing): continue if not re.search('Logged In as ', listing): continue @@ -75,6 +97,68 @@ def discover(c): eval("%s(tw)" % c.supported_sites[s].decoder) tables[tw.name] = tw return tables +# +# The discover_xx functions query the system and report on the poker clients +# currently displayed on the screen. The discover_posix should give you +# some idea how to support other systems. +# +# discover_xx() returns a dict of TableWindow objects--one TableWindow +# object for each poker client table on the screen. +# +# Each TableWindow object must have the following attributes correctly populated: +# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site +# name specified in the config file. +# tw.number = This is the system id number for the client table window in the +# format that the system presents it. +# tw.title = The full title from the window title bar. +# tw.width, tw.height = The width and height of the window in pixels. This is +# the internal width and height, not including the title bar and +# window borders. +# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative +# to the top left of the display screen. This also does not include the +# title bar and window borders. To put it another way, this is the +# screen location of (0, 0) in the working window. + +def win_enum_handler(hwnd, titles): + titles[hwnd] = win32gui.GetWindowText(hwnd) + +def child_enum_handler(hwnd, children): + print hwnd, win32.GetWindowRect(hwnd) + +def discover_nt(c): + """ Poker client table window finder for Windows.""" +# +# I cannot figure out how to get the inside dimensions of the poker table +# windows. So I just assume all borders are 3 thick and all title bars +# are 29 high. No doubt this will be off when used with certain themes. +# + b_width = 3 + tb_height = 29 + titles = {} + tables = {} + win32gui.EnumWindows(win_enum_handler, titles) + for hwnd in titles.keys(): + if re.search('Logged In as', titles[hwnd]) and not re.search('Lobby', titles[hwnd]): + tw = Table_Window() +# tw.site = c.supported_sites[s].site_name + tw.number = hwnd + (x, y, width, height) = win32gui.GetWindowRect(hwnd) + tw.title = titles[hwnd] + tw.width = int( width ) - 2*b_width + tw.height = int( height ) - b_width - tb_height + tw.x = int( x ) + b_width + tw.y = int( y ) + tb_height + eval("%s(tw)" % "pokerstars_decode_table") + tw.site = "PokerStars" + + + tables[tw.name] = tw + return tables + +def discover_mac(c): + """ Poker client table window finder for Macintosh.""" + tables = {} + return tables def pokerstars_decode_table(tw): # extract the table name OR the tournament number and table name from the title @@ -95,18 +179,6 @@ def pokerstars_decode_table(tw): mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title) -#Traceback (most recent call last): -# File "/home/fatray/razz-poker-productio/HUD_main.py", line 41, in process_new_hand -# table_windows = Tables.discover(config) -# File "/home/fatray/razz-poker-productio/Tables.py", line 58, in discover -# eval("%s(tw)" % c.supported_sites[s].decoder) -# File "", line 1, in -# File "/home/fatray/razz-poker-productio/Tables.py", line 80, in pokerstars_decode_table -# tw.game = mo.group(1).lower() -#AttributeError: 'NoneType' object has no attribute 'group' -# -#This problem happens with observed windows!! - tw.game = mo.group(1).lower() tw.game = re.sub('\'', '', tw.game) tw.game = re.sub('h/l', 'hi/lo', tw.game) @@ -132,4 +204,8 @@ if __name__=="__main__": tables = discover(c) for t in tables.keys(): - print tables[t] \ No newline at end of file + print "t = ", t + print tables[t] + + print "press enter to continue" + sys.stdin.readline() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index def44534..878ca119 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -407,7 +407,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) - self.window.set_title("Free Poker DB - version: alpha2+, p78") + self.window.set_title("Free Poker DB - version: alpha3, p80") self.window.set_border_width(1) self.window.set_size_request(1020,400) self.window.set_resizable(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 1b9f8778..4ff5f621 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -25,6 +25,7 @@ class fpdb_db: self.cursor=None self.MYSQL_INNODB=2 self.PGSQL=3 + self.SQLITE=4 #end def __init__ def connect(self, backend, host, database, user, password): @@ -39,8 +40,10 @@ class fpdb_db: import MySQLdb self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) elif backend==self.PGSQL: - import pgdb - self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) +# import pgdb +# self.db = pgdb.connect(dsn=host+":"+database, user='postgres', password=password) + import psycopg2 + self.db = psycopg2.connect(host = host, user = user, password = password, database = database) else: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) self.cursor=self.db.cursor() @@ -153,6 +156,24 @@ class fpdb_db: def recreate_tables(self): """(Re-)creates the tables of the current DB""" + + if self.backend == 3: +# postgresql + print "recreating tables in postgres db" + schema_file = open('schema.postgres.sql', 'r') + schema = schema_file.read() + schema_file.close() + curse = self.db.cursor() +# curse.executemany(schema, [1, 2]) + for sql in schema.split(';'): + sql = sql.rstrip() + if sql == '': + continue + curse.execute(sql) + self.db.commit() + curse.close() + return + self.drop_tables() self.create_table("""Settings ( diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py old mode 100755 new mode 100644 index f53f6ed1..149b4c7b --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -19,6 +19,7 @@ import sys import MySQLdb +import psycopg2 #import pgdb import math import os @@ -37,7 +38,7 @@ def import_file(server, database, user, password, inputFile): self.settings={'imp-callFpdbHud':False} import_file_dict(self, settings) -def import_file_dict(options, settings, callHud=True): +def import_file_dict(options, settings, callHud=False): last_read_hand=0 if (options.inputFile=="stdin"): inputFile=sys.stdin @@ -45,8 +46,16 @@ def import_file_dict(options, settings, callHud=True): inputFile=open(options.inputFile, "rU") #connect to DB - db = MySQLdb.connect(host = options.server, user = options.user, + if options.settings['db-backend'] == 2: + db = MySQLdb.connect(host = options.server, user = options.user, passwd = options.password, db = options.database) + elif options.settings['db-backend'] == 3: + db = psycopg2.connect(host = options.server, user = options.user, + password = options.password, database = options.database) + elif options.settings['db-backend'] == 4: + pass + else: + pass cursor = db.cursor() if (not options.quiet): @@ -104,11 +113,13 @@ def import_file_dict(options, settings, callHud=True): db.commit() stored+=1 - if settings['imp-callFpdbHud'] and callHud and os.sep=='/': + db.commit() +# if settings['imp-callFpdbHud'] and callHud and os.sep=='/': + if settings['imp-callFpdbHud'] and callHud: #print "call to HUD here. handsId:",handsId #pipe the Hands.id out to the HUD - options.pipe_to_hud.stdin.write("%s\n" % (handsId)) - db.commit() +# options.pipe_to_hud.write("%s" % (handsId) + os.linesep) + options.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) except fpdb_simple.DuplicateError: duplicates+=1 except (ValueError), fe: diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 1c854008..dcfac57b 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -18,6 +18,7 @@ #This file contains simple functions for fpdb import datetime +import re PS=1 FTP=2 @@ -781,8 +782,10 @@ def parseHandStartTime(topline, site): #print "parsehandStartTime, tmp:", tmp pos = tmp.find("-")+2 tmp = tmp[pos:] - #print "year:", tmp[0:4], "month", tmp[5:7], "day", tmp[8:10], "hour", tmp[13:15], "minute", tmp[16:18], "second", tmp[19:21] - result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) + if re.search('\(ET\)', tmp): # the old datetime format at ps + result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[13:15]), int(tmp[16:18]), int(tmp[19:21])) + else: # new format + result = datetime.datetime(int(tmp[0:4]), int(tmp[5:7]), int(tmp[8:10]), int(tmp[11:13]), int(tmp[14:16]), int(tmp[17:19])) else: raise FpdbError("invalid site in parseHandStartTime") @@ -1951,7 +1954,7 @@ def storeHudCache(cursor, base, category, gametypeId, playerIds, hudImportData): row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50], row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60], - row[1], row[2], row[3], row[4], row[5])) + row[1], row[2], row[3], str(row[4]), row[5])) # else: # print "todo: implement storeHudCache for stud base" #end def storeHudCache diff --git a/pyfpdb/schema.postgres.sql b/pyfpdb/schema.postgres.sql new file mode 100644 index 00000000..74653ba6 --- /dev/null +++ b/pyfpdb/schema.postgres.sql @@ -0,0 +1,217 @@ + +DROP TABLE IF EXISTS Settings CASCADE; +CREATE TABLE Settings (version SMALLINT); + +DROP TABLE IF EXISTS Sites CASCADE; +CREATE TABLE Sites ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name varchar(32), + currency char(3)); + +DROP TABLE IF EXISTS Gametypes CASCADE; +CREATE TABLE Gametypes ( + id SERIAL UNIQUE, PRIMARY KEY (id), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + type char(4), + base char(4), + category varchar(9), + limitType char(2), + hiLo char(1), + smallBlind int, + bigBlind int, + smallBet int, + bigBet int); + +DROP TABLE IF EXISTS Players CASCADE; +CREATE TABLE Players ( + id SERIAL UNIQUE, PRIMARY KEY (id), + name VARCHAR(32), + siteId INTEGER, FOREIGN KEY (siteId) REFERENCES Sites(id), + comment text, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS Autorates CASCADE; +CREATE TABLE Autorates ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + description varchar(50), + shortDesc char(8), + ratingTime timestamp without time zone, + handCount int); + +DROP TABLE IF EXISTS Hands CASCADE; +CREATE TABLE Hands ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tableName VARCHAR(20), + siteHandNo BIGINT, + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + handStart timestamp without time zone, + importTime timestamp without time zone, + seats SMALLINT, + maxSeats SMALLINT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS BoardCards CASCADE; +CREATE TABLE BoardCards ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1)); + +DROP TABLE IF EXISTS TourneyTypes CASCADE; +CREATE TABLE TourneyTypes ( + id SERIAL, PRIMARY KEY (id), + siteId INT, FOREIGN KEY (siteId) REFERENCES Sites(id), + buyin INT, + fee INT, + knockout INT, + rebuyOrAddon BOOLEAN); + +DROP TABLE IF EXISTS Tourneys CASCADE; +CREATE TABLE Tourneys ( + id SERIAL UNIQUE, PRIMARY KEY (id), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + siteTourneyNo BIGINT, + entries INT, + prizepool INT, + startTime timestamp without time zone, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS TourneysPlayers CASCADE; +CREATE TABLE TourneysPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + tourneyId INT, FOREIGN KEY (tourneyId) REFERENCES Tourneys(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + payinAmount INT, + rank INT, + winnings INT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS HandsPlayers CASCADE; +CREATE TABLE HandsPlayers ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + startCash INT, + position CHAR(1), + seatNo SMALLINT, + ante INT, + + card1Value smallint, + card1Suit char(1), + card2Value smallint, + card2Suit char(1), + card3Value smallint, + card3Suit char(1), + card4Value smallint, + card4Suit char(1), + card5Value smallint, + card5Suit char(1), + card6Value smallint, + card6Suit char(1), + card7Value smallint, + card7Suit char(1), + + winnings int, + rake int, + comment text, + commentTs timestamp without time zone, + tourneysPlayersId BIGINT, FOREIGN KEY (tourneysPlayersId) REFERENCES TourneysPlayers(id)); + +DROP TABLE IF EXISTS HandsActions CASCADE; +CREATE TABLE HandsActions ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + handPlayerId BIGINT, FOREIGN KEY (handPlayerId) REFERENCES HandsPlayers(id), + street SMALLINT, + actionNo SMALLINT, + action CHAR(5), + amount INT, + comment TEXT, + commentTs timestamp without time zone); + +DROP TABLE IF EXISTS HudCache CASCADE; +CREATE TABLE HudCache ( + id BIGSERIAL UNIQUE, PRIMARY KEY (id), + gametypeId INT, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), + playerId INT, FOREIGN KEY (playerId) REFERENCES Players(id), + activeSeats SMALLINT, + position CHAR(1), + tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id), + + HDs INT, + street0VPI INT, + street0Aggr INT, + street0_3B4BChance INT, + street0_3B4BDone INT, + street1Seen INT, + street2Seen INT, + street3Seen INT, + street4Seen INT, + sawShowdown INT, + street1Aggr INT, + street2Aggr INT, + street3Aggr INT, + street4Aggr INT, + otherRaisedStreet1 INT, + otherRaisedStreet2 INT, + otherRaisedStreet3 INT, + otherRaisedStreet4 INT, + foldToOtherRaisedStreet1 INT, + foldToOtherRaisedStreet2 INT, + foldToOtherRaisedStreet3 INT, + foldToOtherRaisedStreet4 INT, + wonWhenSeenStreet1 FLOAT, + wonAtSD FLOAT, + + stealAttemptChance INT, + stealAttempted INT, + foldBbToStealChance INT, + foldedBbToSteal INT, + foldSbToStealChance INT, + foldedSbToSteal INT, + + street1CBChance INT, + street1CBDone INT, + street2CBChance INT, + street2CBDone INT, + street3CBChance INT, + street3CBDone INT, + street4CBChance INT, + street4CBDone INT, + + foldToStreet1CBChance INT, + foldToStreet1CBDone INT, + foldToStreet2CBChance INT, + foldToStreet2CBDone INT, + foldToStreet3CBChance INT, + foldToStreet3CBDone INT, + foldToStreet4CBChance INT, + foldToStreet4CBDone INT, + + totalProfit INT, + + street1CheckCallRaiseChance INT, + street1CheckCallRaiseDone INT, + street2CheckCallRaiseChance INT, + street2CheckCallRaiseDone INT, + street3CheckCallRaiseChance INT, + street3CheckCallRaiseDone INT, + street4CheckCallRaiseChance INT, + street4CheckCallRaiseDone INT); + +INSERT INTO Settings VALUES (76); +INSERT INTO Sites ("name", currency) VALUES ('Full Tilt Poker', 'USD'); +INSERT INTO Sites ("name", currency) VALUES ('PokerStars', 'USD'); +INSERT INTO TourneyTypes (buyin, fee, knockout, rebuyOrAddon) VALUES (0, 0, 0, FALSE);