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. +