Merge branch 'master' of git://git.assembla.com/fpdb-gimick

This commit is contained in:
Worros 2011-01-11 11:44:35 +08:00
commit 0f40afe7a7
5 changed files with 732 additions and 1 deletions

View File

@ -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
-----------------

View File

@ -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':_('% 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'),

333
pyfpdb/RushNotesAux.py Normal file
View File

@ -0,0 +1,333 @@
#!/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 <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in agpl-3.0.txt.
########################################################################
##########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 !!
#
#debugmode will write logfiles for the __init__ and update_data methods
# writes into ./pyfpdb/~Rushdebug.*
#
debugmode = False
# Standard Library modules
import os
import sys
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
import Card
#
# 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", "&#xD;").replace("\n", "&#xA;")
data = data.replace("\t", "&#x9;")
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>%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):
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
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
else:
self.active = True
#
# 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()
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 not self.active:
return
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("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 self.rushtables:
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] + " ")
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])
if BBper100[6] == "-": BBper100=BBper100[0:6] + "(" + BBper100[7:] + ")"
#
# 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
# 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()

183
pyfpdb/RushNotesMerge.py Executable file
View File

@ -0,0 +1,183 @@
#!/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/foo/.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 <http://www.gnu.org/licenses/>.
#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
#
# 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", "&#xD;").replace("\n", "&#xA;")
data = data.replace("\t", "&#x9;")
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>%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
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"
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 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 "***************************************************************"
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 = {}
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 "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 <heroname>.xml"
print "2 Rename the .merged file to become the new notes file"

194
pyfpdb/RushNotesReadMe.txt Normal file
View File

@ -0,0 +1,194 @@
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. 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 does not change or set colourcodings.
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.
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.
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:
-----------------------
<aw class="RushNotes" module="RushNotesAux" name="Rush1"> </aw>
<game aux="Rush1" cols="3" db="fpdb" game_name="holdem" rows="3">
or whatever works for you.
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:
./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)
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 in future, then thats great.
As I find bugs and make improvements, I will push to git.
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.
(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 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 could
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.