Hand grabbers¶
NoteCaddy creates data by reading the hands in your database. There are two methods with which this can be done:
Turbo hand grabber creates a cursor (
more information) to your database which gives it the ability to quickly cycle through hands. This mode puts significant strain on weak computers. It is turned off by default
Standard hand grabber repeatedly executes queries grabbing 1000 hands at a time. This is much slower than the turbo hand grabber. However, it works well on weak computers.
You configure which hand grabber to use by going to file->settings->database settings
Creating notes
In each hand there are 2-10 players. NoteCaddy will look at each player unless a white list or black list is defined in file->filters. For each player, NoteCaddy will see if any active note definition is valid. If it is, NoteCaddy will create a note for that player. All of the notes are stored in memory temporarily. You can see the progress of this on the
creating notes panel in NoteCaddy
This screen lists how many hands are in your database and how many of them NoteCaddy has processed. As you can see in the screenshot, the total hands is listed as an estimate. For large databases, it can take several minutes to count the exact amount of hands. This is time that is wasted since that number is of no practical consequence. The estimate that is used takes only a fraction of a second to obtain. This number will be less accurate if you have purged hands from your database or have encountered a large amount of import errors.
If you are using the HM-App version of NoteCaddy (the one embedded in HM2) then this process will start automatically. Please see
Getting Started with NoteCaddy for recommended settings. You can configure NoteCaddy to not automatically create notes by going to file->settings->database settings and unchecking "automatically start taking notes when HM2 opens"
NoteCaddy will create notes only for the database you have selected in HM2. There is no way to configure a certain database to be excluded.
When you play new hands, notes are automatically created for them assuming two things are true:
- You did not configure NoteCaddy to not automatically take notes (see the screenshot above)
- Your entire database has been processed already
Saving notes to the database
The data cannot remain in memory as that would be lost when you close HM2. Therefore it is saved to the database periodically. When this happens, you will see a message that says "Saving notes to the database" on the
creating notes panel. By default this is every 2000 hands. You can configure NoteCaddy to wait longer by going to file->settings->database settings and entering a larger number
In the above image you can see that it is set to 5000. This can lead to a performance increase. The reason is that there is less time spent writing to the database. However, if HM2 closes in the middle of those 5000 hands, then no data will be saved and the next time you open HM2 they will need to be processed again. Therefore you should avoid making this value too large
Vacuuming your database
The database that HM2 uses is PostGres. This database temporarily occupies excessive disk space when data is being created and updated. You can immediately reclaim this space by performing the vacuum function. You can perform this directly from NoteCaddy by clicking tools->Vacuum Database. Furthermore, a
Pending Task will be created for you if you process more than 100,000 hands. This is as a reminder that you may want to perform a database vacuum.
Data persistence format
NoteCaddy stores all player data in the database. One row is created for each player and the full data is stored as a single piece of text. This is done to avoid issues with database schema changes. The name of the table is notecaddy_data. If you are a developer you can easily read the data from there.
An example query would be:
select * from notecaddy_dat where player_id = 7
or
select ncd.*
from notecaddy_data ncd
inner join players p on p.player_id = ncd.player_id
where p.playername = 'SretiCentV'}}
Below is a source code sample of this data is parsed
{{
String rawNote = dt.Rows[0]["data"].ToString();
if (rawNote.Trim() == "")
return null;
String[] noteTokens = rawNote.Split('\n');
Int32 index = 0;
foreach (String noteToken in noteTokens)
{
NoteDetails nd = new NoteDetails();
nd.Player = player;
String[] fieldTokens = noteToken.Split('\t');
Int16 dbID = Int16.Parse(fieldTokens[0]);
if (!GlobalController.DbNoteDefinitions.ContainsKey(dbID))
{
ReadDescription definition = GlobalController.NoteDefinitions.FirstOrDefault(
d => d.DatabaseID == dbID);
if (definition == null)
continue;
try
{
GlobalController.DbNoteDefinitions.TryAdd(dbID, definition);
}
catch { }
}
nd.Definition = GlobalController.DbNoteDefinitions[dbID];
nd.DatabaseIndex = dbID;
//this prevents the note from getting deleted using the task
//if (!nd.Definition.IsActive)
// continue;
#if DEBUG
if (nd.Definition == null)
nd.Definition = new ReadDescription();
#endif
nd.ShowPercentage = true;
nd.ShowPrefix = true;
nd.ReadID = nd.Definition.ID.ToString();
nd.Demographics = Int16.Parse(fieldTokens[7]);
nd.Prefix = nd.Definition.Category;
//List
instanceHits = new List();
#region instances
String rawGuilty = fieldTokens[1];
if (!String.IsNullOrEmpty(rawGuilty))
{
String[] guiltyTokens = rawGuilty.Split(',');
foreach (String guiltyToken in guiltyTokens)
instances.Add(Int32.Parse(guiltyToken));
//Int32 skip = 0;
if (!nd.Definition.IsSaveHistory)
{
nd.Instances = instances[0];
nd.Opportunities = instances[1];
}
else
{
//if (useLast != 0 && useLast < instances.Count)
// skip = instances.Count - useLast;
foreach (Int32 i in instances)//.Skip(skip))
{
nd.GuiltyHands.Add(i);
nd.Opportunities++;
nd.InstanceIndex++;
if (i > 0)
{
nd.Instances++;
//instanceHits.Add(i);
}
}
}
}
#endregion
#region range variable
String rawRangeVar = fieldTokens[5];
if (!String.IsNullOrEmpty(rawRangeVar))
{
Int32[] rangeVarResults = rawRangeVar.ToInt32List();
foreach (Int32 i in rangeVarResults)//.Skip(skip))
{
//if (i == 0)
// continue;
nd.RangeVariableResults.Add(i);
}
nd.RangeVarIndex = rangeVarResults.Length;
}
#endregion
#region spark
Int32[] sparkPoints = null;
String rawSpark = fieldTokens[2];
if (!String.IsNullOrEmpty(rawSpark))
{
sparkPoints = rawSpark.ToInt32List();
//Int32 skip = 0;
//if (useLast > 0)
// skip = sparkPoints.Length - nd.Instances;
//if (skip < 0)
// skip = 0;
//Int32 index = -1;
foreach (Int32 i in sparkPoints)//.Skip(skip))
{
nd.SparkSequence.Add((Int16)i);
//index++;
//if (i == 0)
// continue;
//if (!nd.SparkRange.ContainsKey(i))
// nd.SparkRange.Add(i, 0);
//nd.SparkRange[i]++;
}
nd.SparkIndex = sparkPoints.Length;
}
#endregion
#region scatter
String rawScatter = fieldTokens[3];
if (!String.IsNullOrEmpty(rawScatter))
{
Int32[] betSizes = rawScatter.ToInt32List();
//Int32 skip = 0;
//if (useLast > 0)
// skip = betSizes.Length - nd.Instances;
//if (skip < 0)
// skip = 0;
//Int32 index = -1;
foreach (Int32 i in betSizes)//.Skip(skip))
{
nd.ScatterSequence.Add((Int16)i);
//index++;
if (i <= 0)
continue;
//ScatterPoint sp = new ScatterPoint();
//if (nd.Definition.IsSaveHistory)
// sp.HandID = instances[index];
//if ((sp.HandID > 0 || !nd.Definition.IsSaveHistory) &&
// nd.SparkSequence.Count > index)
//{
// sp.PotRatio = (Decimal)i / 100;
// sp.Strength = nd.SparkSequence[index];
// nd.ScatterPoints.Add(sp);
//}
}
nd.ScatterIndex = betSizes.Length;
}
#endregion
#region timing points
String rawTiming = fieldTokens[4];
if (!String.IsNullOrEmpty(rawTiming))
{
Int32[] timings = rawTiming.ToInt32List();
//Int32 skip = 0;
//if (useLast > 0)
// skip = timings.Length - nd.Instances;
//if (skip < 0)
// skip = 0;
//Int32 index = -1;
foreach (Int32 i in timings)//.Skip(skip))
{
nd.TimingSequence.Add((Int16)i);
//index++;
//if (i <= 0)
// continue;
//TimingPoint tp = new TimingPoint();
//tp.Action = TableActions.Bet;
//if (nd.Definition.IsSaveHistory && instances.Count > index)
// tp.HandID = instances[index];
//tp.TimingDelay = (Int16)i;
//if (nd.SparkSequence.Count > index)
// tp.Strength = nd.SparkSequence[index];
//tp.Street = nd.Definition.AutoDetectStreet();
//if (tp.TimingDelay > 0)
// nd.TimingPoints.Add(tp);
}
nd.TimingIndex = timings.Length;
}
#endregion
#region other variables
String rawOther = fieldTokens[6];
if (!String.IsNullOrEmpty(rawOther))
{
Int32[] othervarresults = rawOther.ToInt32List();
foreach (Int32 i in othervarresults)
{
nd.VariableResults.Add(i);
}
}
#endregion
#region player partition
String rawPlayer = fieldTokens[8];
if (!String.IsNullOrEmpty(rawPlayer))
{
Int16 id = Int16.Parse(rawPlayer);
nd.PlayerPartitionID = id;
PartitionRange guiltyPartition = nd.Definition.PlayerPartitionRanges
.FirstOrDefault(p => p.ID == id);
if (guiltyPartition == null)
guiltyPartition = GlobalController.GlobalPartitions
.FirstOrDefault(p => p.ID == id);
if (guiltyPartition != null)
{
if (guiltyPartition.DisplayMinimum > 0)
nd.MinimumPlayerRange = guiltyPartition.DisplayMinimum;
else
nd.MinimumPlayerRange = guiltyPartition.Minimum;
if (guiltyPartition.DisplayMaximum > 0)
nd.MaximumPlayerRange = guiltyPartition.DisplayMaximum;
else
nd.MaximumPlayerRange = guiltyPartition.Maximum;
}
else
{
switch (nd.GameSize)
{
case GameSizes.HeadsUp:
nd.MinimumPlayerRange = 2;
nd.MaximumPlayerRange = 2;
break;
case GameSizes.Medium:
nd.MinimumPlayerRange = 3;
nd.MaximumPlayerRange = 6;
break;
case GameSizes.Full:
nd.MinimumPlayerRange = 7;
nd.MaximumPlayerRange = 10;
break;
}
}
}
else
{
switch (nd.GameSize)
{
case GameSizes.HeadsUp:
nd.MinimumPlayerRange = 2;
nd.MaximumPlayerRange = 2;
break;
case GameSizes.Medium:
nd.MinimumPlayerRange = 3;
nd.MaximumPlayerRange = 6;
break;
case GameSizes.Full:
nd.MinimumPlayerRange = 7;
nd.MaximumPlayerRange = 10;
break;
}
}
#endregion
#region stack partition
String rawStack = fieldTokens[9];
if (!String.IsNullOrEmpty(rawStack))
{
Int16 id = Int16.Parse(rawStack);
nd.StackSizePartitionID = id;
PartitionRange guiltyPartition = nd.Definition.AverageStackPartitionRanges
.FirstOrDefault(p => p.ID == id);
if (guiltyPartition != null)
{
nd.MinimumAverageStackRange = guiltyPartition.Minimum;
nd.MaximumAverageStackRange = guiltyPartition.Maximum;
}
}
#endregion
nd.NetBigBlinds = Decimal.Parse(fieldTokens[10]);
For additional performance tips, please see NoteCaddy speed improvement