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);