From 0acaa8cb6df0ec67093100647dc5464150b73d47 Mon Sep 17 00:00:00 2001 From: gimick Date: Tue, 28 Dec 2010 03:20:49 +0000 Subject: [PATCH 1/8] GUI: RingPlayerStats correct the description of w$sd --- pyfpdb/GuiRingPlayerStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiRingPlayerStats.py b/pyfpdb/GuiRingPlayerStats.py index 86ba1aad..c19f0f1d 100644 --- a/pyfpdb/GuiRingPlayerStats.py +++ b/pyfpdb/GuiRingPlayerStats.py @@ -58,7 +58,7 @@ onlinehelp = {'Game':_('Type of Game'), 'Saw_F':_('% Saw Flop vs hands dealt'), 'SawSD':_('Saw Show Down / River'), 'WtSDwsF':_('Went To Show Down When Saw Flop'), - 'W$SD':_('Amount Won when Show Down seen'), + 'W$SD':_('% Times Won when Show Down seen'), 'FlAFq':_('Flop Aggression\n% Bet or Raise after seeing Flop'), 'TuAFq':_('Turn Aggression\n% Bet or Raise after seeing Turn'), 'RvAFq':_('River Aggression\n% Bet or Raise after seeing River'), From 95b496e9f477fff9e9310cdc808f8d05348e1ff8 Mon Sep 17 00:00:00 2001 From: gimick Date: Tue, 28 Dec 2010 13:35:24 +0000 Subject: [PATCH 2/8] GUI: another w$sd description change --- pyfpdb/GuiRingPlayerStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiRingPlayerStats.py b/pyfpdb/GuiRingPlayerStats.py index c19f0f1d..f061322e 100644 --- a/pyfpdb/GuiRingPlayerStats.py +++ b/pyfpdb/GuiRingPlayerStats.py @@ -58,7 +58,7 @@ onlinehelp = {'Game':_('Type of Game'), 'Saw_F':_('% Saw Flop vs hands dealt'), 'SawSD':_('Saw Show Down / River'), 'WtSDwsF':_('Went To Show Down When Saw Flop'), - 'W$SD':_('% Times Won when Show Down seen'), + 'W$SD':_('% Won some money at showdown'), 'FlAFq':_('Flop Aggression\n% Bet or Raise after seeing Flop'), 'TuAFq':_('Turn Aggression\n% Bet or Raise after seeing Turn'), 'RvAFq':_('River Aggression\n% Bet or Raise after seeing River'), From 744e5292eff4095752d65d920c1518269284d6ff Mon Sep 17 00:00:00 2001 From: gimick Date: Tue, 28 Dec 2010 18:29:23 +0000 Subject: [PATCH 3/8] py2exe: update walkthrough (suppress popup error message on exit) --- .../windows/py2exeWalkthroughPython26.txt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packaging/windows/py2exeWalkthroughPython26.txt b/packaging/windows/py2exeWalkthroughPython26.txt index 7ad865c0..2762699e 100644 --- a/packaging/windows/py2exeWalkthroughPython26.txt +++ b/packaging/windows/py2exeWalkthroughPython26.txt @@ -33,6 +33,7 @@ psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2 (py)pokereval v138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pypoker-eval-win32/pokereval-138.win32.exe/download (Note: There are no official windows builds, this installer is built from source. A walkthrough is in the same directory as this walkthrough. + 1.2/ MySQL Install the following file: @@ -67,6 +68,26 @@ with this line: 1.3.4/ Save and exit +1.4/ Patch py2exe to stop popup runtime error message + +see http://www.py2exe.org/index.cgi/StderrLog for technical info. + +1.4.1/ + +dos> write C:\Python26\Lib\site-packages\py2exe\boot_common.py + +replace: + atexit.register(alert, 0, + "See the logfile '%s' for details" % fname, + "Errors occurred") +with: + #atexit.register(alert, 0, + # "See the logfile '%s' for details" % fname, + # "Errors occurred") + +1.4.2/ save and exit + + Step 2 Setup GTK ----------------- From 36e2ef3f35613ec19197bb99fecef0a11cf94ffc Mon Sep 17 00:00:00 2001 From: gimick Date: Thu, 30 Dec 2010 23:49:39 +0000 Subject: [PATCH 4/8] rushnotes: initial commit (for testing and eval only) --- pyfpdb/RushNotesAux.py | 216 +++++++++++++++++++++++++++++++++++++ pyfpdb/RushNotesMerge.py | 122 +++++++++++++++++++++ pyfpdb/RushNotesReadMe.txt | 166 ++++++++++++++++++++++++++++ 3 files changed, 504 insertions(+) create mode 100644 pyfpdb/RushNotesAux.py create mode 100755 pyfpdb/RushNotesMerge.py create mode 100644 pyfpdb/RushNotesReadMe.txt diff --git a/pyfpdb/RushNotesAux.py b/pyfpdb/RushNotesAux.py new file mode 100644 index 00000000..bb1ff506 --- /dev/null +++ b/pyfpdb/RushNotesAux.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""RushNotesAux.py + + EXPERIMENTAL - USE WITH CARE + +Auxilliary process to push HUD data into the FullTilt player notes XML +This will allow a rudimentary "HUD" in rush games + +The existing notes file will be altered by this function +""" +# Copyright 2010, "Gimick" of the FPDB project fpdb.sourceforge.net +# +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in agpl-3.0.txt. + +######################################################################## + +##########for each hand processed, attempts to update hero.xml player notes for FullTilt +##########based upon the AW howto notes written by Ray E. Barker (nutomatic) at fpdb.sourceforge.net + +#to do +### think about seeding +### multiple huds firing at the same xml +### same player / two levels / only one xml +### http://www.faqs.org/docs/diveintopython/kgp_search.html + +#debugmode will write logfiles for the __init__ and update_data methods +debugmode = False + +# Standard Library modules +import os +from xml.dom import minidom +from datetime import datetime +from time import * + +# FreePokerDatabase modules +from Mucked import Aux_Window +from Mucked import Seat_Window +from Mucked import Aux_Seats +import Stats + +class RushNotes(Aux_Window): + + def __init__(self, hud, config, params): + + self.hud = hud + self.config = config + + # + # following line makes all the site params magically available (thanks Ray!) + # + site_params_dict = self.hud.config.get_site_parameters(self.hud.site) + + heroname = site_params_dict['screen_name'] + sitename = site_params_dict['site_name'] + notepath = site_params_dict['site_path'] # this is a temporary hijack of site-path + notepath = r"/home/steve/.wine/drive_c/Program Files/Full Tilt Poker/" + self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname) + self.notefile = notepath + "/" + heroname + ".xml" + + # + # read in existing notefile and backup with date/time in name + # todo change to not use dom + # + now = datetime.now() + notefilebackup = self.notefile + ".backup." + now.strftime("%Y%m%d%H%M%S") + xmlnotefile = minidom.parse(self.notefile) + outputfile = open(notefilebackup, 'w') + xmlnotefile.writexml(outputfile) + outputfile.close() + xmlnotefile.unlink + + # Create a fresh queue file with skeleton XML + # + self.queuefile = self.notefile + ".queue" + queuedom = minidom.Document() + # Create the minidom document + +# Create the base element + pld=queuedom.createElement("PLAYERDATA") + queuedom.appendChild(pld) + + nts=queuedom.createElement("NOTES") + pld.appendChild(nts) + + nte = queuedom.createElement("NOTE") + nte = queuedom.createTextNode("\n") + nts.insertBefore(nte,None) + + outputfile = open(self.queuefile, 'w') + queuedom.writexml(outputfile) + outputfile.close() + queuedom.unlink + + if (debugmode): + #initialise logfiles + debugfile=open("~Rushdebug.init", "w") + debugfile.write("conf="+str(config)+"\n") + debugfile.write("spdi="+str(site_params_dict)+"\n") + debugfile.write("para="+str(params)+"\n") + debugfile.write("hero="+heroname+" "+str(self.heroid)+"\n") + debugfile.write("back="+notefilebackup+"\n") + debugfile.write("queu="+self.queuefile+"\n") + debugfile.close() + + open("~Rushdebug.data", "w").close() + + + def update_data(self, new_hand_id, db_connection): + #this method called once for every hand processed + # self.hud.stat_dict contains the stats information for this hand + + + + if (debugmode): + debugfile=open("~Rushdebug.data", "a") + debugfile.write(new_hand_id+"\n") + now = datetime.now() + debugfile.write(now.strftime("%Y%m%d%H%M%S")+ " update_data begins"+ "\n") + debugfile.write("hero="+str(self.heroid)+"\n") + #debugfile.write(str(self.hud.stat_dict)+"\n") + debugfile.write(self.hud.table.name+"\n") + debugfile.write(str(self.hud.stat_dict.keys())+"\n") + + if self.hud.table.name not in {"Mach 10", "Lightning", "Celerity", "Flash", "Zoom"} + return + # + # Grab a list of player id's + # + handplayers = self.hud.stat_dict.keys() + + # + # build a dictionary of stats text for each player in the hand (excluding the hero) + # xmlqueuedict contains {playername : stats text} + # + xmlqueuedict = {} + for playerid in handplayers: + # ignore hero, no notes available for hero at Full Tilt + if playerid == self.heroid: continue + + playername=unicode(str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'playername')[1])) + # Use index[3] which is a short description + n=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'n')[3] + " ") + vpip=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'vpip')[3] + " ") + pfr=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'pfr')[3] + " ") + three_B=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'three_B')[3] + " ") + cbet=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'cbet')[3] + " ") + steal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'steal')[3] + " ") + ffreq1=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'ffreq1')[3] + " ") + agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ") + BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3] + " ") + + xmlqueuedict[playername] = "~fpdb~" + n + vpip + pfr + three_B + cbet + steal + ffreq1 + agg_freq + BBper100 + "~ends~" + + if (debugmode): + now = datetime.now() + debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n") + debugfile.write(str(xmlqueuedict)+"\n") + + # + # delaying processing of xml until now. Grab current queuefile contents and + # read each existing NOTE element in turn, if matched to a player in xmlqueuedict + # update their text in the xml and delete the dictionary item + # + xmlnotefile = minidom.parse(self.queuefile) + notelist = xmlnotefile.getElementsByTagName('NOTE') + + for noteentry in notelist: #for each note in turn + noteplayer = noteentry.getAttribute("PlayerId") #extract the playername from xml + if noteplayer in xmlqueuedict: # does that player exist in the queue? + noteentry.setAttribute("Text",xmlqueuedict[noteplayer]) + del xmlqueuedict[noteplayer] #remove from list, does not need to be added later on + + # + #create entries for new players (those remaining in the dictionary) + # + if len(xmlqueuedict) > 0: + playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list) + notesnode=playerdata.childNodes[0] #Find NOTES node + + for newplayer in xmlqueuedict: + newentry = xmlnotefile.createElement("NOTE") + newentry.setAttribute("PlayerId", newplayer) + newentry.setAttribute("Text", xmlqueuedict[newplayer]) + notesnode.insertBefore(newentry,None) + newentry = xmlnotefile.createTextNode("\n") + notesnode.insertBefore(newentry,None) + + if (debugmode): + now = datetime.now() + debugfile.write(now.strftime("%Y%m%d%H%M%S")+" xml pre-processing complete"+ "\n") + + # + # OverWrite existing xml file with updated DOM and cleanup + # + updatednotes = open(self.queuefile, 'w') + xmlnotefile.writexml(updatednotes) + updatednotes.close() + + xmlnotefile.unlink + + if (debugmode): + now = datetime.now() + debugfile.write(now.strftime("%Y%m%d%H%M%S")+" dom written, process finished"+ "\n") + debugfile.close() diff --git a/pyfpdb/RushNotesMerge.py b/pyfpdb/RushNotesMerge.py new file mode 100755 index 00000000..ec8896d1 --- /dev/null +++ b/pyfpdb/RushNotesMerge.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""RushNotesMerge.py + + EXPERIMENTAL - USE WITH CARE + +Merge .queue file with hero's note to generate fresh .merge file + +normal usage +$> ./pyfpdb/RushNotesMerge.py "/home/steve/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml" + +The generated file can then replace heroname.xml (if all is well). + + +""" +# Copyright 2010, "Gimick" of the FPDB project fpdb.sourceforge.net +# +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in agpl-3.0.txt. + +######################################################################## + + +# Standard Library modules +import os +import sys +from xml.dom import minidom + +statqueue=0 +statupdated=0 +statadded=0 + +def cleannote(textin): + if textin.find("~fpdb~") == -1: return textin + if textin.find("~ends~") == -1: return textin + if textin.find("~fpdb~") > textin.find("~ends~"): return textin + return textin[0:textin.find("~fpdb~")] + textin[textin.find("~ends~")+6:] +# get out now if parameter not passed +try: + sys.argv[1] <> "" +except: + print "A parameter is required, quitting now" + quit() + +if not os.path.isfile(sys.argv[1]): + print "Hero notes file not found, quitting" + quit() + +if not os.path.isfile((sys.argv[1]+".queue")): + print "Nothing queued, quitting" + quit() + +print "reading from: ", sys.argv[1] +print "merging with: ", sys.argv[1]+".queue" + +#read queue and turn into a dict +queuedict = {} +xmlqueue = minidom.parse(sys.argv[1]+".queue") +notelist = xmlqueue.getElementsByTagName('NOTE') + +for noteentry in notelist: + noteplayer = noteentry.getAttribute("PlayerId") + notetext = noteentry.getAttribute("Text") + queuedict[noteplayer] = notetext + statqueue = statqueue + 1 + +#read existing player note file + +xmlnotefile = minidom.parse(sys.argv[1]) +notelist = xmlnotefile.getElementsByTagName('NOTE') + +# +#for existing players, empty out existing fpdbtext and refill +# +for noteentry in notelist: + noteplayer = noteentry.getAttribute("PlayerId") + if noteplayer in queuedict: + existingnote = noteentry.getAttribute("Text") + newnote=cleannote(existingnote) + newnote = newnote + queuedict[noteplayer] + noteentry.setAttribute("Text",newnote) + statupdated = statupdated + 1 + del queuedict[noteplayer] + +# +#create entries for new players (those remaining in the dictionary) +# +if len(queuedict) > 0: + playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list) + notesnode=playerdata.childNodes[1] #Find NOTES node + +for newplayer in queuedict: + newentry = xmlnotefile.createElement("NOTE") + newentry.setAttribute("PlayerId", newplayer) + newentry.setAttribute("Text", queuedict[newplayer]) + notesnode.insertBefore(newentry,None) + newentry = xmlnotefile.createTextNode("\n") + notesnode.insertBefore(newentry,None) + statadded=statadded+1 + +#print xmlnotefile.toprettyxml() + +mergednotes = open(sys.argv[1]+".merged", 'w') +xmlnotefile.writexml(mergednotes) +mergednotes.close() + +xmlnotefile.unlink + +print "new file has been written to: ", sys.argv[1]+".merged" +print "number in queue: ", statqueue +print "existing players updated: ", statupdated +print "new players added: ", statadded diff --git a/pyfpdb/RushNotesReadMe.txt b/pyfpdb/RushNotesReadMe.txt new file mode 100644 index 00000000..595b5adb --- /dev/null +++ b/pyfpdb/RushNotesReadMe.txt @@ -0,0 +1,166 @@ +aux to write fpdb data to player notes on Full Tilt +--------------------------------------------------- + +by Gimick 30th Dec 2010 + +RushNotesAux - auxillary processed attached to the full tilt hud + builds up fpdb notes "queue" for each villain met while the autoimport is running + uses HUD aggregation stats to do this + +RushNotesMerge - stand alone process to merge the existing ftp notes, together with queue + produced by Aux. + the output file can then be renamed to become the new ftp notes file + +Important info: +The Merge process can only be run when ftp client is shutdown - otherwise ftp overwrites the xml on exit. + +Restarting the autoimport will empty the notes"queue" so avoid restarting autoimport until the previous +notes "queue" has been merged. + +Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed, you have been warned +Existing colour codings should be preserved, this process should not change colourcodings. + +Copies of the live ftp notes file are preserved everytime RushNotesAux is started, just in case. + +The AW is hard-coded with just the table names of Micro Rush Poker, and should ignore all other hands. + +Getting started: +--------------- + +1. Set the Hero aggregation to alltime. hero_stat_range="A" + This overcomes a sqlite "bug" which has nothing to do with auxillary windows - not doing this + will slow processing down to about 1 hand per minute. + +2. Set the site_path to be the folder containing the FTP notes xml file +(on wine this is normally site_path="/home/blah/.wine/Program Files/Full Tilt Poker/") + + +Wire-up the aux process: +----------------------- + + + + +or whatever works for you. + +Start Autoimport, and rearrange the on-screen stats out of the way + (killing the HUD kills the AW updates) + +Play some poker + +Stop the autoimport +Exit the Full tilt poker client (ensure it has fully stopped with ps -A) + +execute the following: + +./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myname.xml" + +A revised notes file (blah.merge) should automagically appear in the full tilt root directory. +If you are happy with it, replace the existing (myname.xml file) + + +Summary +------ + +This is very rough and ready, but it does what I set-out to achieve. + +All feedback welcome, and if this is useful as a basis for general notes processing, then thats great. + +As I find bugs and make improvements, I will push to the git branch. + + +Much more information below: +---------------------------- + +Background +---------- + +When playing rush poker, some sort of rudimentary HUD would answer simple questions +like "is this allin overbet being made by a nit, or a maniac". Although some +notes may have been made previously, some statistics would help to backup the decision. + +Currently fpdb cannot support rush because the HUD is always 1 hand or more +behind the current action. + +The only way to do this at the moment is to switch to GuiPlayerStats and make a quick +enquiry by player name. However, this effectively times you out of all other +action if multitabling. + +Full Tilt situation +------------------- + +Full Tilt notes are stored in xml format ("hero.xml"). Previously these could +be updated while the game was in progress, however, FullTilt now cache the +notes and write them out when the application exits. This makes it impossible +to use the notes as a real-time HUD, and therefore real-time huds are now +forced to screen-scrape or poke around in the client memory. + +Accepting this a limitation, this implementation updates the notes only once +the FullTilt client has been closed. Obviously, the villain HUD stats are only +as at the end of the last session, however, it is hoped this is significantly +better than having nothing at all. As the hero's hand history increases, the +notes should progressively mature in accuracy. + +Preamble +-------- + +Note that this implementation was written purely to be "good enough" to work +for the author, and is not intended as package or production quality. It +is contributed as a starting point for others, or for experimental use. + +Thanks to Ray Barker who gave a great deal of help throughout. + + +The implementation +------------------- + +RushNotesAux is an fpdb auxilliary process, and is called for every hand +processed by autoimport. Each villain has a note prepared based on the current +fpdb data, and this note (in XML format) is stored in a queue file. + +Auxilliary windows were chosen because +a) the author has limited fpdb and programming skill +b) the auxillary windows handler is well documented and supported +c) any code created has access to the full range of stats with little or no extra work +d) runs within the HUD, so the aggregation parameters are already available + + +Limitations +----------- + +The notes are only regenerated if a hand is played against the villain. The +process does not "bulk load" notes based upon all the player stats in FPDB. + +It is hoped that due to the relatively large hand volume and relatively small + player pools, this limitation will be largely overcome after a few sessions +although there will obviously be a number of players with no fpdb note. + +The aggregation parameters used for the notes are based upon the HUD parameters. + +Stopping and starting the HUD will erase the previously created notes holding file. + +The HUD must run, so the individual player popups need to be manually relocated. + +Although hard-coded for micro RUSH tablenames, the auxilliary window will +probably happily update notes of all cash and tournament players. + +Process overview +---------------- + +1/ The HUD process is started. +1.1/ when the first hand is received, h fresh holding file is created, and +a copy of the current live xml note file is created as a security backup. +2/ For every hand played, the auxillary window is called +3/ Based upon the players in the hand, fpdb will be interrogated +and key stats are formatted in xml-style and written out to a holding file. +4/ At the end of the session, the HUD is stopped and the poker client closed + +5/ The user can then review the contents of the holding file. +6/ A process is begun to "merge" the holding file into the existing player notes +7/ A new "merged" file is created. The process attempts to preserve any +existing notes, but this cannot be guaranteed. +8/ The user can review this merged file, and if they are happy, +they replace the existing note file. +9/ Note that this process never updates the live notes file in situ, but +there is a risk that something goes wrong, and that existing notes could be destroyed. + From afaaee1fcf69c82982f97b8aad41549f8aa0d729 Mon Sep 17 00:00:00 2001 From: gimick Date: Fri, 31 Dec 2010 00:19:52 +0000 Subject: [PATCH 5/8] rushnotes: fix typo, switch off debug mode --- pyfpdb/RushNotesAux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/RushNotesAux.py b/pyfpdb/RushNotesAux.py index bb1ff506..1a87fd78 100644 --- a/pyfpdb/RushNotesAux.py +++ b/pyfpdb/RushNotesAux.py @@ -133,7 +133,7 @@ class RushNotes(Aux_Window): debugfile.write(self.hud.table.name+"\n") debugfile.write(str(self.hud.stat_dict.keys())+"\n") - if self.hud.table.name not in {"Mach 10", "Lightning", "Celerity", "Flash", "Zoom"} + if self.hud.table.name not in ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom"): return # # Grab a list of player id's From ba3883d0b75ef4aeb8bf1b92f94ee14a55c25b4f Mon Sep 17 00:00:00 2001 From: gimick Date: Sat, 1 Jan 2011 18:53:53 +0000 Subject: [PATCH 6/8] rushnotes: tweaks + overcome "lf" bug in minidom + place HUD on multiline --- pyfpdb/RushNotesAux.py | 66 +++++++++++++++++++++++++++++++++++++--- pyfpdb/RushNotesMerge.py | 42 +++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/pyfpdb/RushNotesAux.py b/pyfpdb/RushNotesAux.py index 1a87fd78..28e3aaaa 100644 --- a/pyfpdb/RushNotesAux.py +++ b/pyfpdb/RushNotesAux.py @@ -36,10 +36,11 @@ The existing notes file will be altered by this function ### http://www.faqs.org/docs/diveintopython/kgp_search.html #debugmode will write logfiles for the __init__ and update_data methods -debugmode = False +debugmode = True # Standard Library modules import os +import sys from xml.dom import minidom from datetime import datetime from time import * @@ -50,6 +51,48 @@ from Mucked import Seat_Window from Mucked import Aux_Seats import Stats +# +# overload minidom methods to fix bug where \n is parsed as " ". +# described here: http://bugs.python.org/issue7139 +# + +def _write_data(writer, data, isAttrib=False): + "Writes datachars to writer." + if isAttrib: + data = data.replace("\r", " ").replace("\n", " ") + data = data.replace("\t", " ") + writer.write(data) +minidom._write_data = _write_data + +def writexml(self, writer, indent="", addindent="", newl=""): + # indent = current indentation + # addindent = indentation to add to higher levels + # newl = newline string + writer.write(indent+"<" + self.tagName) + + attrs = self._get_attributes() + a_names = attrs.keys() + a_names.sort() + + for a_name in a_names: + writer.write(" %s=\"" % a_name) + _write_data(writer, attrs[a_name].value, isAttrib=True) + writer.write("\"") + if self.childNodes: + writer.write(">%s"%(newl)) + for node in self.childNodes: + node.writexml(writer,indent+addindent,addindent,newl) + writer.write("%s%s" % (indent,self.tagName,newl)) + else: + writer.write("/>%s"%(newl)) +# For an introduction to overriding instance methods, see +# http://irrepupavel.com/documents/python/instancemethod/ +instancemethod = type(minidom.Element.writexml) +minidom.Element.writexml = instancemethod( + writexml, None, minidom.Element) + + + class RushNotes(Aux_Window): def __init__(self, hud, config, params): @@ -65,9 +108,14 @@ class RushNotes(Aux_Window): heroname = site_params_dict['screen_name'] sitename = site_params_dict['site_name'] notepath = site_params_dict['site_path'] # this is a temporary hijack of site-path - notepath = r"/home/steve/.wine/drive_c/Program Files/Full Tilt Poker/" self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname) self.notefile = notepath + "/" + heroname + ".xml" + + if not os.path.isfile(self.notefile): + self.active = False + return + else: + self.active = True # # read in existing notefile and backup with date/time in name @@ -121,7 +169,8 @@ class RushNotes(Aux_Window): #this method called once for every hand processed # self.hud.stat_dict contains the stats information for this hand - + if not self.active: + return if (debugmode): debugfile=open("~Rushdebug.data", "a") @@ -156,13 +205,15 @@ class RushNotes(Aux_Window): pfr=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'pfr')[3] + " ") three_B=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'three_B')[3] + " ") cbet=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'cbet')[3] + " ") + fbbsteal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'f_BB_steal')[3] + " ") + steal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'steal')[3] + " ") ffreq1=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'ffreq1')[3] + " ") agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ") BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3] + " ") - xmlqueuedict[playername] = "~fpdb~" + n + vpip + pfr + three_B + cbet + steal + ffreq1 + agg_freq + BBper100 + "~ends~" - + xmlqueuedict[playername] = "~fpdb~" + n + vpip + pfr + three_B + fbbsteal + "\n" + steal + cbet + ffreq1 + "\n" + agg_freq + BBper100 + "~ends~" + if (debugmode): now = datetime.now() debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n") @@ -214,3 +265,8 @@ class RushNotes(Aux_Window): now = datetime.now() debugfile.write(now.strftime("%Y%m%d%H%M%S")+" dom written, process finished"+ "\n") debugfile.close() + + + + + diff --git a/pyfpdb/RushNotesMerge.py b/pyfpdb/RushNotesMerge.py index ec8896d1..8d46c3fa 100755 --- a/pyfpdb/RushNotesMerge.py +++ b/pyfpdb/RushNotesMerge.py @@ -36,6 +36,48 @@ import os import sys from xml.dom import minidom +# +# overload minidom methods to fix bug where \n is parsed as " ". +# described here: http://bugs.python.org/issue7139 +# + +def _write_data(writer, data, isAttrib=False): + "Writes datachars to writer." + if isAttrib: + data = data.replace("\r", " ").replace("\n", " ") + data = data.replace("\t", " ") + writer.write(data) +minidom._write_data = _write_data + +def writexml(self, writer, indent="", addindent="", newl=""): + # indent = current indentation + # addindent = indentation to add to higher levels + # newl = newline string + writer.write(indent+"<" + self.tagName) + + attrs = self._get_attributes() + a_names = attrs.keys() + a_names.sort() + + for a_name in a_names: + writer.write(" %s=\"" % a_name) + _write_data(writer, attrs[a_name].value, isAttrib=True) + writer.write("\"") + if self.childNodes: + writer.write(">%s"%(newl)) + for node in self.childNodes: + node.writexml(writer,indent+addindent,addindent,newl) + writer.write("%s%s" % (indent,self.tagName,newl)) + else: + writer.write("/>%s"%(newl)) +# For an introduction to overriding instance methods, see +# http://irrepupavel.com/documents/python/instancemethod/ +instancemethod = type(minidom.Element.writexml) +minidom.Element.writexml = instancemethod( + writexml, None, minidom.Element) + + + statqueue=0 statupdated=0 statadded=0 From 96275f9f1e0685019ef77e8fdfffde72c7bfd478 Mon Sep 17 00:00:00 2001 From: gimick Date: Wed, 5 Jan 2011 22:35:42 +0000 Subject: [PATCH 7/8] rushnotes: more tweaks - add LF's and brackets for negative BB100 --- pyfpdb/RushNotesAux.py | 11 ++++++++--- pyfpdb/RushNotesMerge.py | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/pyfpdb/RushNotesAux.py b/pyfpdb/RushNotesAux.py index 28e3aaaa..3a445a0f 100644 --- a/pyfpdb/RushNotesAux.py +++ b/pyfpdb/RushNotesAux.py @@ -36,7 +36,7 @@ The existing notes file will be altered by this function ### http://www.faqs.org/docs/diveintopython/kgp_search.html #debugmode will write logfiles for the __init__ and update_data methods -debugmode = True +debugmode = False # Standard Library modules import os @@ -210,9 +210,14 @@ class RushNotes(Aux_Window): steal=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'steal')[3] + " ") ffreq1=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'ffreq1')[3] + " ") agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ") - BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3] + " ") + BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3]) + if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")" - xmlqueuedict[playername] = "~fpdb~" + n + vpip + pfr + three_B + fbbsteal + "\n" + steal + cbet + ffreq1 + "\n" + agg_freq + BBper100 + "~ends~" + xmlqueuedict[playername] = ("~fpdb~" + "\n" + + n + vpip + pfr + three_B + fbbsteal + "\n" + + steal + cbet + ffreq1 + "\n" + + agg_freq + BBper100 + "\n" + + "~ends~") if (debugmode): now = datetime.now() diff --git a/pyfpdb/RushNotesMerge.py b/pyfpdb/RushNotesMerge.py index 8d46c3fa..f22b67ab 100755 --- a/pyfpdb/RushNotesMerge.py +++ b/pyfpdb/RushNotesMerge.py @@ -102,8 +102,15 @@ if not os.path.isfile((sys.argv[1]+".queue")): print "Nothing queued, quitting" quit() -print "reading from: ", sys.argv[1] -print "merging with: ", sys.argv[1]+".queue" +print "***************************************************************" +print "IMPORTANT: *** Before running this merge: ***" +print "Closedown the FullTiltClient and wait for it to completely stop" +print "If FullTiltClient was running, run the merge again once it" +print "has stopped completely" +print "***************************************************************" +print +print "read from: ", sys.argv[1] +print "merge with: ", sys.argv[1]+".queue" #read queue and turn into a dict queuedict = {} @@ -158,7 +165,13 @@ mergednotes.close() xmlnotefile.unlink -print "new file has been written to: ", sys.argv[1]+".merged" +print "Merged file has been written to: ", sys.argv[1]+".merged" +print "" print "number in queue: ", statqueue print "existing players updated: ", statupdated print "new players added: ", statadded +print "\n" +print "Use a viewer to check the contents of the merge file." +print "If you are happy, carry out the following steps:" +print "1 Rename or delete the existing notes file (normally .xml" +print "2 Rename the .merged file to become the new notes file" From ae7f983591008d339844dcea1e926eb23bd51eed Mon Sep 17 00:00:00 2001 From: gimick Date: Sat, 8 Jan 2011 23:28:41 +0000 Subject: [PATCH 8/8] rushnotes: major update, show villain ranges in HUD + other tweaks --- pyfpdb/RushNotesAux.py | 128 ++++++++++++++++++++++++++----------- pyfpdb/RushNotesMerge.py | 12 +++- pyfpdb/RushNotesReadMe.txt | 60 ++++++++++++----- 3 files changed, 145 insertions(+), 55 deletions(-) diff --git a/pyfpdb/RushNotesAux.py b/pyfpdb/RushNotesAux.py index 3a445a0f..312de6d5 100644 --- a/pyfpdb/RushNotesAux.py +++ b/pyfpdb/RushNotesAux.py @@ -26,16 +26,14 @@ The existing notes file will be altered by this function ######################################################################## -##########for each hand processed, attempts to update hero.xml player notes for FullTilt +##########for each hand processed, attempts to create update for player notes in FullTilt ##########based upon the AW howto notes written by Ray E. Barker (nutomatic) at fpdb.sourceforge.net +##########Huge thanks to Ray for his guidance and encouragement to create this !! -#to do -### think about seeding -### multiple huds firing at the same xml -### same player / two levels / only one xml -### http://www.faqs.org/docs/diveintopython/kgp_search.html - +# #debugmode will write logfiles for the __init__ and update_data methods +# writes into ./pyfpdb/~Rushdebug.* +# debugmode = False # Standard Library modules @@ -50,9 +48,10 @@ from Mucked import Aux_Window from Mucked import Seat_Window from Mucked import Aux_Seats import Stats +import Card # -# overload minidom methods to fix bug where \n is parsed as " ". +# overload minidom methods to fix bug where \n is parsed as " ". # described here: http://bugs.python.org/issue7139 # @@ -110,7 +109,8 @@ class RushNotes(Aux_Window): notepath = site_params_dict['site_path'] # this is a temporary hijack of site-path self.heroid = self.hud.db_connection.get_player_id(self.config, sitename, heroname) self.notefile = notepath + "/" + heroname + ".xml" - + self.rushtables = ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom") + if not os.path.isfile(self.notefile): self.active = False return @@ -129,28 +129,27 @@ class RushNotes(Aux_Window): outputfile.close() xmlnotefile.unlink + # # Create a fresh queue file with skeleton XML - # + # self.queuefile = self.notefile + ".queue" - queuedom = minidom.Document() - # Create the minidom document + queuedom = minidom.Document() -# Create the base element pld=queuedom.createElement("PLAYERDATA") queuedom.appendChild(pld) nts=queuedom.createElement("NOTES") pld.appendChild(nts) - + nte = queuedom.createElement("NOTE") nte = queuedom.createTextNode("\n") - nts.insertBefore(nte,None) - + nts.insertBefore(nte,None) + outputfile = open(self.queuefile, 'w') queuedom.writexml(outputfile) outputfile.close() queuedom.unlink - + if (debugmode): #initialise logfiles debugfile=open("~Rushdebug.init", "w") @@ -159,19 +158,19 @@ class RushNotes(Aux_Window): debugfile.write("para="+str(params)+"\n") debugfile.write("hero="+heroname+" "+str(self.heroid)+"\n") debugfile.write("back="+notefilebackup+"\n") - debugfile.write("queu="+self.queuefile+"\n") + debugfile.write("queu="+self.queuefile+"\n") debugfile.close() - + open("~Rushdebug.data", "w").close() - + def update_data(self, new_hand_id, db_connection): #this method called once for every hand processed # self.hud.stat_dict contains the stats information for this hand - + if not self.active: return - + if (debugmode): debugfile=open("~Rushdebug.data", "a") debugfile.write(new_hand_id+"\n") @@ -179,10 +178,11 @@ class RushNotes(Aux_Window): debugfile.write(now.strftime("%Y%m%d%H%M%S")+ " update_data begins"+ "\n") debugfile.write("hero="+str(self.heroid)+"\n") #debugfile.write(str(self.hud.stat_dict)+"\n") - debugfile.write(self.hud.table.name+"\n") - debugfile.write(str(self.hud.stat_dict.keys())+"\n") + debugfile.write("table="+self.hud.table.name+"\n") + debugfile.write("players="+str(self.hud.stat_dict.keys())+"\n") + debugfile.write("db="+str(db_connection)+"\n") - if self.hud.table.name not in ("Mach 10", "Lightning", "Celerity", "Flash", "Zoom"): + if self.hud.table.name not in self.rushtables: return # # Grab a list of player id's @@ -212,18 +212,74 @@ class RushNotes(Aux_Window): agg_freq=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'agg_freq')[3] + " ") BBper100=str(Stats.do_stat(self.hud.stat_dict, player = playerid, stat = 'BBper100')[3]) if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")" - - xmlqueuedict[playername] = ("~fpdb~" + "\n" + - n + vpip + pfr + three_B + fbbsteal + "\n" + - steal + cbet + ffreq1 + "\n" + - agg_freq + BBper100 + "\n" + + + + # + # grab villain known starting hands + # only those where they VPIP'd, so limp in the BB will not be shown + # sort by hand strength. Output will show position too, + # so KK.1 is KK from late posn etc. + # ignore non-rush hands (check against known rushtablenames) + # cards decoding is hard-coded for holdem, so that's tuff atm + # three categories of known hands are shown: + # agression preflop hands + # non-aggression preflop hands + # bigblind called to defend hands + # + # This isn't perfect, but it isn't too bad a starting point + # + + PFcall="PFcall" + PFaggr="PFaggr" + PFdefend="PFdefend" + + c = db_connection.get_cursor() + c.execute(("SELECT handId, position, startCards, street0Aggr, tableName " + + "FROM hands, handsPlayers " + + "WHERE handsplayers.handId = hands.id " + + "AND street0VPI = 1 " + + "AND startCards > 0 " + + "AND playerId = %d " + + "ORDER BY startCards DESC " + + ";") + % int(playerid)) + + for (qid, qposition, qstartcards, qstreet0Aggr, qtablename) in c.fetchall(): + if (debugmode): + debugfile.write("pid, hid, pos, cards, aggr, table player"+ + str(playerid)+"/"+str(qid)+"/"+str(qposition)+"/"+ + str(qstartcards)+"/"+str(qstreet0Aggr)+"/"+ + str(qtablename)+"/"+str(playername)+ + "\n") + + humancards = Card.decodeStartHandValue("holdem", qstartcards) + + if qtablename not in self.rushtables: + pass + elif qposition == "B" and qstreet0Aggr == False: + PFdefend=PFdefend+"/"+humancards + elif qstreet0Aggr == True: + PFaggr=PFaggr+"/"+humancards+"."+qposition + else: + PFcall=PFcall+"/"+humancards+"."+qposition + c.close + + # + # build up final text package (top/tail with ~fpdb~ ~ends~ + # for later search/replace by Merge module + # + xmlqueuedict[playername] = ("~fpdb~" + "\n" + + n + vpip + pfr + three_B + fbbsteal + "\n" + + steal + cbet + ffreq1 + "\n" + + agg_freq + BBper100 + "\n" + + PFcall+"\n"+PFaggr+"\n"+PFdefend +"\n" "~ends~") if (debugmode): now = datetime.now() debugfile.write(now.strftime("%Y%m%d%H%M%S")+" villain data has been processed" + "\n") debugfile.write(str(xmlqueuedict)+"\n") - + # # delaying processing of xml until now. Grab current queuefile contents and # read each existing NOTE element in turn, if matched to a player in xmlqueuedict @@ -244,26 +300,26 @@ class RushNotes(Aux_Window): if len(xmlqueuedict) > 0: playerdata=xmlnotefile.lastChild #move to the PLAYERDATA node (assume last one in the list) notesnode=playerdata.childNodes[0] #Find NOTES node - + for newplayer in xmlqueuedict: newentry = xmlnotefile.createElement("NOTE") newentry.setAttribute("PlayerId", newplayer) - newentry.setAttribute("Text", xmlqueuedict[newplayer]) + newentry.setAttribute("Text", xmlqueuedict[newplayer]) notesnode.insertBefore(newentry,None) newentry = xmlnotefile.createTextNode("\n") notesnode.insertBefore(newentry,None) - + if (debugmode): now = datetime.now() debugfile.write(now.strftime("%Y%m%d%H%M%S")+" xml pre-processing complete"+ "\n") - + # # OverWrite existing xml file with updated DOM and cleanup # updatednotes = open(self.queuefile, 'w') xmlnotefile.writexml(updatednotes) updatednotes.close() - + xmlnotefile.unlink if (debugmode): diff --git a/pyfpdb/RushNotesMerge.py b/pyfpdb/RushNotesMerge.py index f22b67ab..7bfec3df 100755 --- a/pyfpdb/RushNotesMerge.py +++ b/pyfpdb/RushNotesMerge.py @@ -7,7 +7,7 @@ Merge .queue file with hero's note to generate fresh .merge file normal usage -$> ./pyfpdb/RushNotesMerge.py "/home/steve/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml" +$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/heroname.xml" The generated file can then replace heroname.xml (if all is well). @@ -37,7 +37,7 @@ import sys from xml.dom import minidom # -# overload minidom methods to fix bug where \n is parsed as " ". +# overload minidom methods to fix bug where \n is parsed as " ". # described here: http://bugs.python.org/issue7139 # @@ -92,14 +92,20 @@ try: sys.argv[1] <> "" except: print "A parameter is required, quitting now" + print "normal usage is something like:" + print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"' quit() if not os.path.isfile(sys.argv[1]): print "Hero notes file not found, quitting" + print "normal usage is something like:" + print '$> ./pyfpdb/RushNotesMerge.py "/home/foo/.wine/drive_c/Program Files/Full Tilt Poker/myhero.xml"' quit() if not os.path.isfile((sys.argv[1]+".queue")): - print "Nothing queued, quitting" + print "Nothing found to merge, quitting" + print "Did the HUD not get started during the last session?" + print "Has the HUD been stopped and started without merging?" quit() print "***************************************************************" diff --git a/pyfpdb/RushNotesReadMe.txt b/pyfpdb/RushNotesReadMe.txt index 595b5adb..43475536 100644 --- a/pyfpdb/RushNotesReadMe.txt +++ b/pyfpdb/RushNotesReadMe.txt @@ -12,24 +12,40 @@ RushNotesMerge - stand alone process to merge the existing ftp notes, together w the output file can then be renamed to become the new ftp notes file Important info: -The Merge process can only be run when ftp client is shutdown - otherwise ftp overwrites the xml on exit. +The Merge process can only be run when ftp client is shutdown + - otherwise ftp overwrites the xml on exit. -Restarting the autoimport will empty the notes"queue" so avoid restarting autoimport until the previous -notes "queue" has been merged. +Restarting the autoimport will empty the notes"queue" so avoid restarting + autoimport until the previous notes "queue" has been merged. You will + lose all the queued notes, but these will be regenerated the next time + the villian is at your table, so it isn't the end of the world. -Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed, you have been warned -Existing colour codings should be preserved, this process should not change colourcodings. +Existing ftp notes _SHOULD_ be preserved, but this isn't guaranteed, + you have been warned! + +Existing colour codings should be preserved, + this process does not change or set colourcodings. -Copies of the live ftp notes file are preserved everytime RushNotesAux is started, just in case. +Copies of the live ftp notes file should be preserved everytime + RushNotesAux (i.e. the HUD is started) -The AW is hard-coded with just the table names of Micro Rush Poker, and should ignore all other hands. +The AW is hard-coded with just the table names of Micro Rush Poker, + and should ignore all other hands. +What might not work? +-------------------- + +This isn't tested with Windows, and probably won't work, feedback welcome. +Hasn't been tested for co-existance with other sites, feedback welcome. +Whenever FTP change their notes file format, this will all break rather spectacularly, + you have been warned! + Getting started: --------------- 1. Set the Hero aggregation to alltime. hero_stat_range="A" - This overcomes a sqlite "bug" which has nothing to do with auxillary windows - not doing this - will slow processing down to about 1 hand per minute. + This overcomes a sqlite "bug" which has nothing to do with auxillary windows + not doing this will slow processing down to about 1 hand per minute. 2. Set the site_path to be the folder containing the FTP notes xml file (on wine this is normally site_path="/home/blah/.wine/Program Files/Full Tilt Poker/") @@ -43,12 +59,17 @@ Wire-up the aux process: or whatever works for you. -Start Autoimport, and rearrange the on-screen stats out of the way - (killing the HUD kills the AW updates) Play some poker +--------------- + +Start Autoimport, and rearrange the on-screen stats out of the way + (the full HUD must run, killing the HUD kills the AW updates) + +Play whatever you want Stop the autoimport + Exit the Full tilt poker client (ensure it has fully stopped with ps -A) execute the following: @@ -59,14 +80,20 @@ A revised notes file (blah.merge) should automagically appear in the full tilt r If you are happy with it, replace the existing (myname.xml file) +Since the updates aren't real time, it would be ok to play the rush + session with fpdb inactive, but before quitting any of the tables, + start the HUD and wait for it to catch-up processing all the hands played. + + Summary ------- +------- This is very rough and ready, but it does what I set-out to achieve. -All feedback welcome, and if this is useful as a basis for general notes processing, then thats great. +All feedback welcome, and if this is useful as a basis for general notes + processing in future, then thats great. -As I find bugs and make improvements, I will push to the git branch. +As I find bugs and make improvements, I will push to git. Much more information below: @@ -136,12 +163,13 @@ It is hoped that due to the relatively large hand volume and relatively small although there will obviously be a number of players with no fpdb note. The aggregation parameters used for the notes are based upon the HUD parameters. + (with the exception of the hand-ranges, which uses its' own criteria (see source) -Stopping and starting the HUD will erase the previously created notes holding file. +Stopping and starting the HUD will erase the previously created notes queue file. The HUD must run, so the individual player popups need to be manually relocated. -Although hard-coded for micro RUSH tablenames, the auxilliary window will +Although hard-coded for micro RUSH tablenames, the auxilliary window could probably happily update notes of all cash and tournament players. Process overview