poker-bitvaljs 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bitval-worker.js +4 -3
- package/bitval.js +179 -123
- package/package.json +1 -1
package/bitval-worker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Web Worker for parallel matchup evaluation
|
|
2
2
|
// Loads bitval.js to access BitVal class and dependencies
|
|
3
|
-
importScripts('bitval.js');
|
|
3
|
+
importScripts('./bitval.js');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Deserializes setup data from main thread (converts string BigInts back to BigInt)
|
|
@@ -39,6 +39,7 @@ function deserializeMatchup(matchupData) {
|
|
|
39
39
|
async function evaluateMatchupInWorker(heroMask, villainMask, setup, evalCache, cacheInfo, bitval) {
|
|
40
40
|
let win = 0, tie = 0, lose = 0;
|
|
41
41
|
const deadMask = heroMask | villainMask | setup.deadCardsMask;
|
|
42
|
+
const matchupDeadMask = heroMask | villainMask; // Only matchup-level dead cards (excludes setup.deadCardsMask)
|
|
42
43
|
|
|
43
44
|
// Initialize random number generator for Monte Carlo (not needed for exhaustive)
|
|
44
45
|
if (!setup.isExhaustive) {
|
|
@@ -71,8 +72,8 @@ async function evaluateMatchupInWorker(heroMask, villainMask, setup, evalCache,
|
|
|
71
72
|
// Evaluate both hands
|
|
72
73
|
let hEval, hKick, vEval, vKick;
|
|
73
74
|
if (cacheInfo && evalCache) {
|
|
74
|
-
[hEval, hKick] = bitval._getCachedEvaluation(cacheInfo.heroKey, cacheInfo.heroHand, board, evalCache);
|
|
75
|
-
[vEval, vKick] = bitval._getCachedEvaluation(cacheInfo.villainKey, cacheInfo.villainHand, board, evalCache);
|
|
75
|
+
[hEval, hKick] = bitval._getCachedEvaluation(cacheInfo.heroKey, cacheInfo.heroHand, board, evalCache, matchupDeadMask);
|
|
76
|
+
[vEval, vKick] = bitval._getCachedEvaluation(cacheInfo.villainKey, cacheInfo.villainHand, board, evalCache, matchupDeadMask);
|
|
76
77
|
} else {
|
|
77
78
|
[hEval, hKick] = bitval.evaluate(heroMask | board);
|
|
78
79
|
[vEval, vKick] = bitval.evaluate(villainMask | board);
|
package/bitval.js
CHANGED
|
@@ -672,6 +672,13 @@ class BitVal {
|
|
|
672
672
|
// Generate valid matchups (always needed)
|
|
673
673
|
const validMatchups = this._generateValidMatchups(heroHands, villainHands, boardCards);
|
|
674
674
|
|
|
675
|
+
// Log all valid matchups for debugging
|
|
676
|
+
console.log(`\n[Valid Matchups] Generated ${validMatchups.length} valid matchups:`);
|
|
677
|
+
for (let i = 0; i < validMatchups.length; i++) {
|
|
678
|
+
const m = validMatchups[i];
|
|
679
|
+
console.log(` ${i + 1}. Hero: ${m.heroHand} vs Villain: ${m.villainHand}`);
|
|
680
|
+
}
|
|
681
|
+
|
|
675
682
|
// Calculate canonical mappings only if optimization is enabled
|
|
676
683
|
let hCanon = null;
|
|
677
684
|
let vCanon = null;
|
|
@@ -786,6 +793,7 @@ class BitVal {
|
|
|
786
793
|
async _evaluateMatchup(heroMask, villainMask, setup, evalCache = null, progressCallback = null, matchupIndex = 0, totalMatchups = 1) {
|
|
787
794
|
let win = 0, tie = 0, lose = 0;
|
|
788
795
|
const deadMask = heroMask | villainMask | setup.deadCardsMask;
|
|
796
|
+
const matchupDeadMask = heroMask | villainMask; // Only matchup-level dead cards (excludes setup.deadCardsMask)
|
|
789
797
|
|
|
790
798
|
// Initialize random number generator for Monte Carlo (not needed for exhaustive)
|
|
791
799
|
if (!setup.isExhaustive) {
|
|
@@ -796,8 +804,8 @@ class BitVal {
|
|
|
796
804
|
let comboArray = null;
|
|
797
805
|
let iterations = setup.iterations;
|
|
798
806
|
if (setup.isExhaustive) {
|
|
799
|
-
|
|
800
|
-
comboArray =
|
|
807
|
+
// Reuse pre-computed comboArray from setup (already computed in _compareRangeSetup)
|
|
808
|
+
comboArray = setup.comboArray;
|
|
801
809
|
iterations = comboArray.length;
|
|
802
810
|
}
|
|
803
811
|
|
|
@@ -815,8 +823,8 @@ class BitVal {
|
|
|
815
823
|
// Evaluate both hands - inlined for performance (no function call overhead)
|
|
816
824
|
let hEval, hKick, vEval, vKick;
|
|
817
825
|
if (evalCache) {
|
|
818
|
-
[hEval, hKick] = this._getCachedEvaluation(evalCache.heroKey, evalCache.heroHand, board, evalCache.cache);
|
|
819
|
-
[vEval, vKick] = this._getCachedEvaluation(evalCache.villainKey, evalCache.villainHand, board, evalCache.cache);
|
|
826
|
+
[hEval, hKick] = this._getCachedEvaluation(evalCache.heroKey, evalCache.heroHand, board, evalCache.cache, matchupDeadMask);
|
|
827
|
+
[vEval, vKick] = this._getCachedEvaluation(evalCache.villainKey, evalCache.villainHand, board, evalCache.cache, matchupDeadMask);
|
|
820
828
|
} else {
|
|
821
829
|
[hEval, hKick] = this.evaluate(heroMask | board);
|
|
822
830
|
[vEval, vKick] = this.evaluate(villainMask | board);
|
|
@@ -940,10 +948,10 @@ class BitVal {
|
|
|
940
948
|
? this._getBoardSuitCounts(setup.boardCards)
|
|
941
949
|
: null;
|
|
942
950
|
|
|
943
|
-
//
|
|
944
|
-
// This
|
|
945
|
-
|
|
946
|
-
|
|
951
|
+
// Group validMatchups by canonical key pair
|
|
952
|
+
// This ensures we only process matchups that actually exist, avoiding Cartesian product issues
|
|
953
|
+
const matchupGroups = new Map();
|
|
954
|
+
console.log(`\n[Sequential - Grouping Matchups] Processing ${setup.validMatchups.length} valid matchups:`);
|
|
947
955
|
for (const matchup of setup.validMatchups) {
|
|
948
956
|
const mHKey = this._getCanonicalKey(
|
|
949
957
|
matchup.heroHand,
|
|
@@ -958,89 +966,94 @@ class BitVal {
|
|
|
958
966
|
boardSuitCounts
|
|
959
967
|
);
|
|
960
968
|
const key = `${mHKey}:${mVKey}`;
|
|
961
|
-
|
|
962
|
-
|
|
969
|
+
|
|
970
|
+
console.log(` Hero: ${matchup.heroHand} (${mHKey}) vs Villain: ${matchup.villainHand} (${mVKey}) -> Key: ${key}`);
|
|
971
|
+
|
|
972
|
+
if (!matchupGroups.has(key)) {
|
|
973
|
+
matchupGroups.set(key, {
|
|
974
|
+
heroKey: mHKey,
|
|
975
|
+
villainKey: mVKey,
|
|
976
|
+
representative: matchup,
|
|
977
|
+
count: 0
|
|
978
|
+
});
|
|
963
979
|
}
|
|
980
|
+
matchupGroups.get(key).count++;
|
|
964
981
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
for (const [hKey] of setup.hCanon.canonicalMap) {
|
|
969
|
-
for (const [vKey] of setup.vCanon.canonicalMap) {
|
|
970
|
-
const key = `${hKey}:${vKey}`;
|
|
971
|
-
if ((setup.validCounts.get(key) || 0) > 0) {
|
|
972
|
-
totalMatchups++;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
982
|
+
console.log(`\n[Sequential - Matchup Groups] Created ${matchupGroups.size} unique canonical key pairs:`);
|
|
983
|
+
for (const [key, group] of matchupGroups) {
|
|
984
|
+
console.log(` ${key}: ${group.count} matchup(s), example: ${group.representative.heroHand} vs ${group.representative.villainHand}`);
|
|
975
985
|
}
|
|
976
986
|
|
|
987
|
+
// Calculate accurate total matchups (number of unique canonical key pairs)
|
|
988
|
+
const totalMatchups = matchupGroups.size;
|
|
989
|
+
|
|
977
990
|
let matchupIndex = 0;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
991
|
+
// Iterate over actual matchup groups (not Cartesian product)
|
|
992
|
+
console.log(`\n[Sequential - Evaluating Matchups] Starting evaluation of ${totalMatchups} unique matchups:`);
|
|
993
|
+
for (const [key, group] of matchupGroups) {
|
|
994
|
+
const validCount = group.count;
|
|
995
|
+
const validPair = group.representative;
|
|
996
|
+
|
|
997
|
+
// Log hand and canonical key mapping for debugging
|
|
998
|
+
console.log(`[Matchup] Hero: ${validPair.heroHand} (Key: ${group.heroKey}) vs Villain: ${validPair.villainHand} (Key: ${group.villainKey}) | Count: ${validCount}`);
|
|
999
|
+
|
|
1000
|
+
const cache = {
|
|
1001
|
+
heroKey: group.heroKey,
|
|
1002
|
+
heroHand: validPair.heroHand,
|
|
1003
|
+
villainKey: group.villainKey,
|
|
1004
|
+
villainHand: validPair.villainHand,
|
|
1005
|
+
cache: evalCache
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const { matchupWin, matchupTie, matchupLose } = await this._evaluateMatchup(
|
|
1009
|
+
validPair.heroMask,
|
|
1010
|
+
validPair.villainMask,
|
|
1011
|
+
setup,
|
|
1012
|
+
cache,
|
|
1013
|
+
progressCallback,
|
|
1014
|
+
matchupIndex,
|
|
1015
|
+
totalMatchups
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
// Log equity results for this matchup
|
|
1019
|
+
const totalIterations = matchupWin + matchupTie + matchupLose;
|
|
1020
|
+
const equity = totalIterations > 0 ? (matchupWin + matchupTie / 2) / totalIterations * 100 : 0;
|
|
1021
|
+
const weightedEquity = equity * validCount;
|
|
1022
|
+
console.log(`[Equity] ${validPair.heroHand} vs ${validPair.villainHand} (${validCount}x): Win=${matchupWin}, Tie=${matchupTie}, Lose=${matchupLose}, Total=${totalIterations}, Equity=${equity.toFixed(2)}%, Weighted=${weightedEquity.toFixed(2)}%`);
|
|
1023
|
+
|
|
1024
|
+
win += matchupWin * validCount;
|
|
1025
|
+
tie += matchupTie * validCount;
|
|
1026
|
+
lose += matchupLose * validCount;
|
|
1027
|
+
|
|
1028
|
+
matchupIndex++;
|
|
1029
|
+
if (progressCallback && matchupIndex % progressInterval === 0) {
|
|
1030
|
+
const now = Date.now();
|
|
1031
|
+
const shouldUpdateProgress = now - lastProgressTime >= progressUpdateInterval;
|
|
1013
1032
|
|
|
1014
|
-
|
|
1015
|
-
if (
|
|
1016
|
-
|
|
1017
|
-
const shouldUpdateProgress = now - lastProgressTime >= progressUpdateInterval;
|
|
1018
|
-
|
|
1019
|
-
// Use modern browser APIs for better scheduling when available
|
|
1020
|
-
if (typeof scheduler !== 'undefined' && scheduler.postTask) {
|
|
1021
|
-
await scheduler.postTask(() => {
|
|
1022
|
-
if (shouldUpdateProgress) {
|
|
1023
|
-
progressCallback(matchupIndex, totalMatchups, `Evaluating ${matchupIndex}`);
|
|
1024
|
-
lastProgressTime = now;
|
|
1025
|
-
}
|
|
1026
|
-
}, { priority: 'user-blocking' });
|
|
1027
|
-
} else if (typeof requestIdleCallback !== 'undefined') {
|
|
1028
|
-
await new Promise(resolve => {
|
|
1029
|
-
requestIdleCallback(() => {
|
|
1030
|
-
if (shouldUpdateProgress) {
|
|
1031
|
-
progressCallback(matchupIndex, totalMatchups, `Evaluating ${matchupIndex}`);
|
|
1032
|
-
lastProgressTime = now;
|
|
1033
|
-
}
|
|
1034
|
-
resolve();
|
|
1035
|
-
}, { timeout: 1 });
|
|
1036
|
-
});
|
|
1037
|
-
} else {
|
|
1038
|
-
// Fallback to setTimeout
|
|
1039
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1033
|
+
// Use modern browser APIs for better scheduling when available
|
|
1034
|
+
if (typeof scheduler !== 'undefined' && scheduler.postTask) {
|
|
1035
|
+
await scheduler.postTask(() => {
|
|
1040
1036
|
if (shouldUpdateProgress) {
|
|
1041
1037
|
progressCallback(matchupIndex, totalMatchups, `Evaluating ${matchupIndex}`);
|
|
1042
1038
|
lastProgressTime = now;
|
|
1043
1039
|
}
|
|
1040
|
+
}, { priority: 'user-blocking' });
|
|
1041
|
+
} else if (typeof requestIdleCallback !== 'undefined') {
|
|
1042
|
+
await new Promise(resolve => {
|
|
1043
|
+
requestIdleCallback(() => {
|
|
1044
|
+
if (shouldUpdateProgress) {
|
|
1045
|
+
progressCallback(matchupIndex, totalMatchups, `Evaluating ${matchupIndex}`);
|
|
1046
|
+
lastProgressTime = now;
|
|
1047
|
+
}
|
|
1048
|
+
resolve();
|
|
1049
|
+
}, { timeout: 1 });
|
|
1050
|
+
});
|
|
1051
|
+
} else {
|
|
1052
|
+
// Fallback to setTimeout
|
|
1053
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1054
|
+
if (shouldUpdateProgress) {
|
|
1055
|
+
progressCallback(matchupIndex, totalMatchups, `Evaluating ${matchupIndex}`);
|
|
1056
|
+
lastProgressTime = now;
|
|
1044
1057
|
}
|
|
1045
1058
|
}
|
|
1046
1059
|
}
|
|
@@ -1061,8 +1074,10 @@ class BitVal {
|
|
|
1061
1074
|
? this._getBoardSuitCounts(setup.boardCards)
|
|
1062
1075
|
: null;
|
|
1063
1076
|
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1077
|
+
// Group validMatchups by canonical key pair
|
|
1078
|
+
// This ensures we only process matchups that actually exist, avoiding Cartesian product issues
|
|
1079
|
+
const matchupGroups = new Map();
|
|
1080
|
+
console.log(`\n[Parallel - Grouping Matchups] Processing ${setup.validMatchups.length} valid matchups:`);
|
|
1066
1081
|
for (const matchup of setup.validMatchups) {
|
|
1067
1082
|
const mHKey = this._getCanonicalKey(
|
|
1068
1083
|
matchup.heroHand,
|
|
@@ -1077,49 +1092,45 @@ class BitVal {
|
|
|
1077
1092
|
boardSuitCounts
|
|
1078
1093
|
);
|
|
1079
1094
|
const key = `${mHKey}:${mVKey}`;
|
|
1080
|
-
|
|
1081
|
-
|
|
1095
|
+
|
|
1096
|
+
console.log(` Hero: ${matchup.heroHand} (${mHKey}) vs Villain: ${matchup.villainHand} (${mVKey}) -> Key: ${key}`);
|
|
1097
|
+
|
|
1098
|
+
if (!matchupGroups.has(key)) {
|
|
1099
|
+
matchupGroups.set(key, {
|
|
1100
|
+
heroKey: mHKey,
|
|
1101
|
+
villainKey: mVKey,
|
|
1102
|
+
representative: matchup,
|
|
1103
|
+
count: 0
|
|
1104
|
+
});
|
|
1082
1105
|
}
|
|
1106
|
+
matchupGroups.get(key).count++;
|
|
1083
1107
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
for (const [hKey] of setup.hCanon.canonicalMap) {
|
|
1088
|
-
for (const [vKey] of setup.vCanon.canonicalMap) {
|
|
1089
|
-
const key = `${hKey}:${vKey}`;
|
|
1090
|
-
if ((setup.validCounts.get(key) || 0) > 0) {
|
|
1091
|
-
totalMatchups++;
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1108
|
+
console.log(`\n[Parallel - Matchup Groups] Created ${matchupGroups.size} unique canonical key pairs:`);
|
|
1109
|
+
for (const [key, group] of matchupGroups) {
|
|
1110
|
+
console.log(` ${key}: ${group.count} matchup(s), example: ${group.representative.heroHand} vs ${group.representative.villainHand}`);
|
|
1094
1111
|
}
|
|
1095
1112
|
|
|
1096
|
-
// Collect all matchups to evaluate
|
|
1113
|
+
// Collect all matchups to evaluate (only those that actually exist)
|
|
1097
1114
|
const matchupsToEvaluate = [];
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
key,
|
|
1113
|
-
validCount,
|
|
1114
|
-
heroKey: hKey,
|
|
1115
|
-
heroHand: validPair.heroHand,
|
|
1116
|
-
heroMask: validPair.heroMask,
|
|
1117
|
-
villainKey: vKey,
|
|
1118
|
-
villainHand: validPair.villainHand,
|
|
1119
|
-
villainMask: validPair.villainMask
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1115
|
+
console.log(`\n[Parallel - Collecting Matchups] Building matchup list from ${matchupGroups.size} groups:`);
|
|
1116
|
+
for (const [key, group] of matchupGroups) {
|
|
1117
|
+
const validPair = group.representative;
|
|
1118
|
+
console.log(` Adding: ${key} (${group.count}x) - Hero: ${validPair.heroHand} vs Villain: ${validPair.villainHand}`);
|
|
1119
|
+
matchupsToEvaluate.push({
|
|
1120
|
+
key,
|
|
1121
|
+
validCount: group.count,
|
|
1122
|
+
heroKey: group.heroKey,
|
|
1123
|
+
heroHand: validPair.heroHand,
|
|
1124
|
+
heroMask: validPair.heroMask,
|
|
1125
|
+
villainKey: group.villainKey,
|
|
1126
|
+
villainHand: validPair.villainHand,
|
|
1127
|
+
villainMask: validPair.villainMask
|
|
1128
|
+
});
|
|
1122
1129
|
}
|
|
1130
|
+
console.log(`\n[Parallel - Matchups to Evaluate] Total: ${matchupsToEvaluate.length} unique matchups`);
|
|
1131
|
+
|
|
1132
|
+
// Calculate total matchups (number of unique canonical key pairs)
|
|
1133
|
+
const totalMatchups = matchupsToEvaluate.length;
|
|
1123
1134
|
|
|
1124
1135
|
// Check if workers should be used
|
|
1125
1136
|
const shouldUseWorkers = useWorkers &&
|
|
@@ -1159,7 +1170,7 @@ class BitVal {
|
|
|
1159
1170
|
}, progressUpdateInterval) : null;
|
|
1160
1171
|
|
|
1161
1172
|
for (let i = 0; i < batches.length; i++) {
|
|
1162
|
-
const worker = new Worker('bitval-worker.js');
|
|
1173
|
+
const worker = new Worker('./bitval-worker.js');
|
|
1163
1174
|
workers.push(worker);
|
|
1164
1175
|
|
|
1165
1176
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -1197,6 +1208,18 @@ class BitVal {
|
|
|
1197
1208
|
|
|
1198
1209
|
for (const batchResults of allResults) {
|
|
1199
1210
|
for (const result of batchResults) {
|
|
1211
|
+
// Log equity results for this matchup
|
|
1212
|
+
const totalIterations = result.matchupWin + result.matchupTie + result.matchupLose;
|
|
1213
|
+
const equity = totalIterations > 0 ? (result.matchupWin + result.matchupTie / 2) / totalIterations * 100 : 0;
|
|
1214
|
+
const weightedEquity = equity * result.validCount;
|
|
1215
|
+
|
|
1216
|
+
// Find the matchup details from matchupsToEvaluate
|
|
1217
|
+
const matchup = matchupsToEvaluate.find(m => m.key === result.key);
|
|
1218
|
+
const heroHand = matchup ? matchup.heroHand : 'Unknown';
|
|
1219
|
+
const villainHand = matchup ? matchup.villainHand : 'Unknown';
|
|
1220
|
+
|
|
1221
|
+
console.log(`[Equity] ${heroHand} vs ${villainHand} (${result.validCount}x): Win=${result.matchupWin}, Tie=${result.matchupTie}, Lose=${result.matchupLose}, Total=${totalIterations}, Equity=${equity.toFixed(2)}%, Weighted=${weightedEquity.toFixed(2)}%`);
|
|
1222
|
+
|
|
1200
1223
|
win += result.matchupWin * result.validCount;
|
|
1201
1224
|
tie += result.matchupTie * result.validCount;
|
|
1202
1225
|
lose += result.matchupLose * result.validCount;
|
|
@@ -1226,12 +1249,34 @@ class BitVal {
|
|
|
1226
1249
|
return { win, tie, lose };
|
|
1227
1250
|
}
|
|
1228
1251
|
|
|
1252
|
+
/**
|
|
1253
|
+
* Generates a numeric hash key from canonical key and board for faster cache lookups.
|
|
1254
|
+
* @private
|
|
1255
|
+
*/
|
|
1256
|
+
_getCacheKeyHash(canonicalKey, board, matchupDeadMask = 0n) {
|
|
1257
|
+
// Hash the canonical key string
|
|
1258
|
+
let hash = 0;
|
|
1259
|
+
for (let i = 0; i < canonicalKey.length; i++) {
|
|
1260
|
+
hash = ((hash << 5) - hash) + canonicalKey.charCodeAt(i);
|
|
1261
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1262
|
+
}
|
|
1263
|
+
// Combine with board bits (use lower 32 bits of BigInt for fast XOR)
|
|
1264
|
+
const boardBits = Number(board & 0xFFFFFFFFn);
|
|
1265
|
+
hash = hash ^ boardBits;
|
|
1266
|
+
|
|
1267
|
+
// Include matchup dead mask to prevent cross-matchup cache collisions
|
|
1268
|
+
// This ensures different matchups (with different dead cards) don't share cache entries
|
|
1269
|
+
const deadBits = Number(matchupDeadMask & 0xFFFFFFFFn);
|
|
1270
|
+
return hash ^ deadBits;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1229
1273
|
/**
|
|
1230
1274
|
* Gets cached evaluation or evaluates and caches the result.
|
|
1275
|
+
* Uses numeric hash keys for faster cache lookups.
|
|
1231
1276
|
* @private
|
|
1232
1277
|
*/
|
|
1233
|
-
_getCachedEvaluation(canonicalKey, originalHand, completeBoard, evalCache) {
|
|
1234
|
-
const cacheKey = canonicalKey
|
|
1278
|
+
_getCachedEvaluation(canonicalKey, originalHand, completeBoard, evalCache, matchupDeadMask = 0n) {
|
|
1279
|
+
const cacheKey = this._getCacheKeyHash(canonicalKey, completeBoard, matchupDeadMask);
|
|
1235
1280
|
if (evalCache.has(cacheKey)) return evalCache.get(cacheKey);
|
|
1236
1281
|
const handMask = this.getBitMasked(this._handStringToCards(originalHand));
|
|
1237
1282
|
const evaluation = this.evaluate(handMask | completeBoard);
|
|
@@ -1311,13 +1356,24 @@ class BitVal {
|
|
|
1311
1356
|
const canonicalMap = new Map();
|
|
1312
1357
|
const canonicalToHand = new Map();
|
|
1313
1358
|
const boardSuitCounts = boardCards.length > 0 ? this._getBoardSuitCounts(boardCards) : null;
|
|
1359
|
+
|
|
1360
|
+
console.log(`\n[Canonical Mapping] Processing ${hands.length} hands:`);
|
|
1314
1361
|
for (const hand of hands) {
|
|
1315
1362
|
const key = this._getCanonicalKey(hand, boardCards, numberOfBoardCards, boardSuitCounts);
|
|
1316
1363
|
canonicalMap.set(key, (canonicalMap.get(key) || 0) + 1);
|
|
1317
1364
|
if (!canonicalToHand.has(key)) {
|
|
1318
1365
|
canonicalToHand.set(key, hand);
|
|
1319
1366
|
}
|
|
1367
|
+
console.log(` Hand: ${hand} -> Canonical Key: ${key}`);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Show summary of canonical key groupings
|
|
1371
|
+
console.log(`\n[Canonical Summary] ${canonicalMap.size} unique canonical keys from ${hands.length} hands:`);
|
|
1372
|
+
for (const [key, count] of canonicalMap.entries()) {
|
|
1373
|
+
const representativeHand = canonicalToHand.get(key);
|
|
1374
|
+
console.log(` ${key}: ${count} hand(s), example: ${representativeHand}`);
|
|
1320
1375
|
}
|
|
1376
|
+
|
|
1321
1377
|
return { canonicalMap, canonicalToHand };
|
|
1322
1378
|
}
|
|
1323
1379
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poker-bitvaljs",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "High-performance poker hand evaluator optimized for browser environments, using bitwise operations for rapid poker hand analysis and equity calculations.",
|
|
5
5
|
"main": "bitval.js",
|
|
6
6
|
"scripts": {
|