poker-bitvaljs 1.0.1 → 1.1.0
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/README.md +67 -6
- package/bitval.js +242 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,10 @@ High-performance poker hand evaluator optimized for browser environments, using
|
|
|
8
8
|
|
|
9
9
|
- **Fast hand evaluation** using bitwise operations
|
|
10
10
|
- **Range vs range equity calculations** with canonical key caching optimization
|
|
11
|
+
- **Web Workers support** for parallel processing across multiple CPU cores (3-7x speedup)
|
|
11
12
|
- **Monte Carlo simulation** for preflop and postflop scenarios
|
|
12
13
|
- **Exhaustive enumeration** for flop/turn scenarios (2 or fewer cards to come)
|
|
13
|
-
- **Progress callbacks**
|
|
14
|
+
- **Progress callbacks** with configurable update intervals
|
|
14
15
|
- **Browser and Node.js compatible**
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
@@ -53,7 +54,9 @@ const result = await bitval.compareRange(
|
|
|
53
54
|
5, // number of board cards
|
|
54
55
|
10000, // iterations
|
|
55
56
|
true, // optimize (use canonical caching)
|
|
56
|
-
null // progress callback (optional)
|
|
57
|
+
null, // progress callback (optional)
|
|
58
|
+
100, // progress interval (optional, default: 100)
|
|
59
|
+
true // use workers (optional, default: true)
|
|
57
60
|
);
|
|
58
61
|
|
|
59
62
|
// Result: { win: 8120, tie: 30, lose: 1850 }
|
|
@@ -73,7 +76,27 @@ const result = await bitval.compareRange(
|
|
|
73
76
|
true,
|
|
74
77
|
(current, total, message) => {
|
|
75
78
|
console.log(`${Math.round((current/total)*100)}% - ${message}`);
|
|
76
|
-
}
|
|
79
|
+
},
|
|
80
|
+
100, // Update progress every 100 matchups
|
|
81
|
+
true // Use Web Workers for parallelization
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With Web Workers Disabled
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// Disable Web Workers (useful for debugging or when workers aren't supported)
|
|
89
|
+
const result = await bitval.compareRange(
|
|
90
|
+
heroHands,
|
|
91
|
+
villainHands,
|
|
92
|
+
[],
|
|
93
|
+
[],
|
|
94
|
+
5,
|
|
95
|
+
10000,
|
|
96
|
+
true,
|
|
97
|
+
null,
|
|
98
|
+
100,
|
|
99
|
+
false // Disable Web Workers, use sequential execution
|
|
77
100
|
);
|
|
78
101
|
```
|
|
79
102
|
|
|
@@ -134,9 +157,9 @@ Simulates a single hand vs hand matchup.
|
|
|
134
157
|
**Returns:**
|
|
135
158
|
- `{ win, tie, lose }`: Results object
|
|
136
159
|
|
|
137
|
-
### `compareRange(heroHands, villainHands, boardCards, deadCards, numberOfBoardCards, iterations, optimize, progressCallback)`
|
|
160
|
+
### `compareRange(heroHands, villainHands, boardCards, deadCards, numberOfBoardCards, iterations, optimize, progressCallback, progressInterval, useWorkers)`
|
|
138
161
|
|
|
139
|
-
Compares two ranges of hands with optional optimization.
|
|
162
|
+
Compares two ranges of hands with optional optimization and Web Workers parallelization.
|
|
140
163
|
|
|
141
164
|
**Parameters:**
|
|
142
165
|
- `heroHands` (Array): Array of hero hand strings (e.g., `['AsAh', 'AsAd']`)
|
|
@@ -146,20 +169,58 @@ Compares two ranges of hands with optional optimization.
|
|
|
146
169
|
- `numberOfBoardCards` (Number): Total board cards (default: `5`)
|
|
147
170
|
- `iterations` (Number): Number of simulations per matchup (default: `10000`)
|
|
148
171
|
- `optimize` (Boolean): Use canonical key caching for performance (default: `true`). **Note:** Optimizations may result in a ±0.5% margin of error compared to unoptimized calculations.
|
|
149
|
-
- `progressCallback` (Function): Optional callback `(current, total, message) => {}`
|
|
172
|
+
- `progressCallback` (Function): Optional callback `(current, total, message) => {}` (default: `null`)
|
|
173
|
+
- `progressInterval` (Number): Update progress callback every N matchups (default: `100`)
|
|
174
|
+
- `useWorkers` (Boolean): Use Web Workers for parallelization across CPU cores (default: `true`). Automatically falls back to sequential execution if workers are unavailable or workload is too small (< 4 matchups).
|
|
150
175
|
|
|
151
176
|
**Returns:**
|
|
152
177
|
- `Promise<{ win, tie, lose }>`: Results object
|
|
153
178
|
|
|
179
|
+
**Web Workers Notes:**
|
|
180
|
+
- Web Workers provide 3-7x speedup on multi-core systems by parallelizing matchup evaluations
|
|
181
|
+
- Workers are automatically disabled when:
|
|
182
|
+
- `useWorkers` is `false`
|
|
183
|
+
- Workers are not supported by the browser
|
|
184
|
+
- There are fewer than 4 matchups (overhead not worth it)
|
|
185
|
+
- For local testing with `file://` protocol, use a local web server (see [Testing Locally](#testing-locally))
|
|
186
|
+
|
|
154
187
|
## Performance
|
|
155
188
|
|
|
156
189
|
- **Optimized for browser environments** with efficient bitwise operations
|
|
190
|
+
- **Web Workers parallelization** provides 3-7x speedup on multi-core systems
|
|
157
191
|
- Canonical key caching reduces redundant evaluations (may introduce ±0.5% margin of error)
|
|
158
192
|
- Exhaustive enumeration for flop/turn (2 or fewer cards to come)
|
|
159
193
|
- Monte Carlo simulation for preflop and river scenarios
|
|
194
|
+
- Adaptive progress reporting to minimize UI blocking
|
|
160
195
|
|
|
161
196
|
**Test performance and benchmark online:** [https://darraghgeo.github.io/poker-bitvaljs/](https://darraghgeo.github.io/poker-bitvaljs/)
|
|
162
197
|
|
|
198
|
+
### Performance Tips
|
|
199
|
+
|
|
200
|
+
- Enable Web Workers (default) for best performance on multi-core systems
|
|
201
|
+
- Use canonical caching (`optimize: true`) for large range comparisons
|
|
202
|
+
- Adjust `progressInterval` to balance UI responsiveness vs. performance
|
|
203
|
+
- For very small workloads (< 4 matchups), workers are automatically disabled to avoid overhead
|
|
204
|
+
|
|
205
|
+
## Testing Locally
|
|
206
|
+
|
|
207
|
+
When testing locally, Web Workers require a proper HTTP origin. If you open `index.html` directly from the file system (`file://` protocol), workers will fail with a `SecurityError`.
|
|
208
|
+
|
|
209
|
+
**Solution: Use a local web server**
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Python 3
|
|
213
|
+
python3 -m http.server 8000
|
|
214
|
+
|
|
215
|
+
# Node.js (if you have npx)
|
|
216
|
+
npx http-server -p 8000
|
|
217
|
+
|
|
218
|
+
# PHP (if installed)
|
|
219
|
+
php -S localhost:8000
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Then open `http://localhost:8000` in your browser. The application will automatically fall back to sequential execution if workers are unavailable.
|
|
223
|
+
|
|
163
224
|
## License
|
|
164
225
|
|
|
165
226
|
MIT
|
package/bitval.js
CHANGED
|
@@ -366,11 +366,13 @@ class BitVal {
|
|
|
366
366
|
* @param {Number} iterations - Number of simulations per matchup (default: 10000)
|
|
367
367
|
* @param {Boolean} optimize - Use canonical key caching (default: true)
|
|
368
368
|
* @param {Function} progressCallback - Optional progress callback (current, total, message)
|
|
369
|
+
* @param {Number} progressInterval - Update progress every N matchups (default: 100)
|
|
370
|
+
* @param {Boolean} useWorkers - Use Web Workers for parallelization (default: true)
|
|
369
371
|
* @returns {Promise<Object>} { win, tie, lose } - Results object
|
|
370
372
|
*/
|
|
371
|
-
async compareRange(heroHands, villainHands, boardCards = [], deadCards = [], numberOfBoardCards = 5, iterations = 10000, optimize = true, progressCallback = null) {
|
|
373
|
+
async compareRange(heroHands, villainHands, boardCards = [], deadCards = [], numberOfBoardCards = 5, iterations = 10000, optimize = true, progressCallback = null, progressInterval = 100, useWorkers = true) {
|
|
372
374
|
const setup = this._compareRangeSetup(heroHands, villainHands, boardCards, deadCards, numberOfBoardCards, iterations, optimize);
|
|
373
|
-
return optimize ? await this._compareRangeOptimized(setup, progressCallback) : await this._compareRangeUnoptimized(heroHands, villainHands, setup, progressCallback);
|
|
375
|
+
return optimize ? await this._compareRangeOptimized(setup, progressCallback, progressInterval, useWorkers) : await this._compareRangeUnoptimized(heroHands, villainHands, setup, progressCallback, progressInterval);
|
|
374
376
|
}
|
|
375
377
|
|
|
376
378
|
// ============================================
|
|
@@ -719,6 +721,63 @@ class BitVal {
|
|
|
719
721
|
};
|
|
720
722
|
}
|
|
721
723
|
|
|
724
|
+
/**
|
|
725
|
+
* Serializes setup object for worker communication (converts BigInts to strings).
|
|
726
|
+
* Note: Maps and complex objects are not sent to workers - workers only need basic setup data.
|
|
727
|
+
* @private
|
|
728
|
+
*/
|
|
729
|
+
_serializeSetup(setup) {
|
|
730
|
+
return {
|
|
731
|
+
boardMask: setup.boardMask.toString(),
|
|
732
|
+
deadCardsMask: setup.deadCardsMask.toString(),
|
|
733
|
+
comboArray: setup.comboArray ? setup.comboArray.map(m => m.toString()) : null,
|
|
734
|
+
boardCards: setup.boardCards,
|
|
735
|
+
numberOfBoardCards: setup.numberOfBoardCards,
|
|
736
|
+
iterations: setup.iterations,
|
|
737
|
+
isExhaustive: setup.isExhaustive,
|
|
738
|
+
numberOfCardsToDeal: setup.numberOfCardsToDeal
|
|
739
|
+
// Note: hCanon, vCanon, validMatchups, validCounts are not needed in workers
|
|
740
|
+
// as matchups are pre-serialized and sent separately
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Deserializes setup data from worker (converts string BigInts back to BigInt).
|
|
746
|
+
* @private
|
|
747
|
+
*/
|
|
748
|
+
_deserializeSetup(setupData) {
|
|
749
|
+
return {
|
|
750
|
+
...setupData,
|
|
751
|
+
boardMask: BigInt(setupData.boardMask),
|
|
752
|
+
deadCardsMask: BigInt(setupData.deadCardsMask),
|
|
753
|
+
comboArray: setupData.comboArray ? setupData.comboArray.map(s => BigInt(s)) : null
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Serializes matchup data for worker communication (converts BigInt masks to strings).
|
|
759
|
+
* @private
|
|
760
|
+
*/
|
|
761
|
+
_serializeMatchup(matchup) {
|
|
762
|
+
return {
|
|
763
|
+
...matchup,
|
|
764
|
+
heroMask: matchup.heroMask.toString(),
|
|
765
|
+
villainMask: matchup.villainMask.toString()
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Deserializes matchup data from worker (converts string BigInts back to BigInt).
|
|
771
|
+
* @private
|
|
772
|
+
*/
|
|
773
|
+
_deserializeMatchup(matchupData) {
|
|
774
|
+
return {
|
|
775
|
+
...matchupData,
|
|
776
|
+
heroMask: BigInt(matchupData.heroMask),
|
|
777
|
+
villainMask: BigInt(matchupData.villainMask)
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
722
781
|
/**
|
|
723
782
|
* Evaluates a single matchup with optional caching.
|
|
724
783
|
* Optimized for browser performance with adaptive yielding and inlined hot loop.
|
|
@@ -819,7 +878,7 @@ class BitVal {
|
|
|
819
878
|
* Compares ranges without optimization (evaluates all matchups individually).
|
|
820
879
|
* @private
|
|
821
880
|
*/
|
|
822
|
-
async _compareRangeUnoptimized(heroHands, villainHands, setup, progressCallback = null) {
|
|
881
|
+
async _compareRangeUnoptimized(heroHands, villainHands, setup, progressCallback = null, progressInterval = 100) {
|
|
823
882
|
let win = 0, tie = 0, lose = 0;
|
|
824
883
|
const total = setup.validMatchups.length;
|
|
825
884
|
let lastProgressTime = 0;
|
|
@@ -830,7 +889,7 @@ class BitVal {
|
|
|
830
889
|
const { matchupWin, matchupTie, matchupLose } = await this._evaluateMatchup(heroMask, villainMask, setup, null, progressCallback, i, total);
|
|
831
890
|
win += matchupWin; tie += matchupTie; lose += matchupLose;
|
|
832
891
|
|
|
833
|
-
if (progressCallback && i %
|
|
892
|
+
if (progressCallback && i % progressInterval === 0) {
|
|
834
893
|
const now = Date.now();
|
|
835
894
|
const shouldUpdateProgress = now - lastProgressTime >= progressUpdateInterval;
|
|
836
895
|
|
|
@@ -867,10 +926,10 @@ class BitVal {
|
|
|
867
926
|
}
|
|
868
927
|
|
|
869
928
|
/**
|
|
870
|
-
* Compares ranges with canonical key caching optimization.
|
|
929
|
+
* Compares ranges with canonical key caching optimization (sequential version).
|
|
871
930
|
* @private
|
|
872
931
|
*/
|
|
873
|
-
async
|
|
932
|
+
async _compareRangeOptimizedSequential(setup, progressCallback = null, progressInterval = 100) {
|
|
874
933
|
let win = 0, tie = 0, lose = 0;
|
|
875
934
|
const evalCache = new FastestAutoClearingCache(16000000);
|
|
876
935
|
let lastProgressTime = 0;
|
|
@@ -953,7 +1012,7 @@ class BitVal {
|
|
|
953
1012
|
lose += matchupLose * validCount;
|
|
954
1013
|
|
|
955
1014
|
matchupIndex++;
|
|
956
|
-
if (progressCallback && matchupIndex %
|
|
1015
|
+
if (progressCallback && matchupIndex % progressInterval === 0) {
|
|
957
1016
|
const now = Date.now();
|
|
958
1017
|
const shouldUpdateProgress = now - lastProgressTime >= progressUpdateInterval;
|
|
959
1018
|
|
|
@@ -991,6 +1050,182 @@ class BitVal {
|
|
|
991
1050
|
return { win, tie, lose };
|
|
992
1051
|
}
|
|
993
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Compares ranges with canonical key caching optimization.
|
|
1055
|
+
* Supports Web Workers for parallelization when enabled.
|
|
1056
|
+
* @private
|
|
1057
|
+
*/
|
|
1058
|
+
async _compareRangeOptimized(setup, progressCallback = null, progressInterval = 100, useWorkers = true) {
|
|
1059
|
+
// Pre-calculate board suit counts once (constant for all matchups)
|
|
1060
|
+
const boardSuitCounts = setup.boardCards.length > 0
|
|
1061
|
+
? this._getBoardSuitCounts(setup.boardCards)
|
|
1062
|
+
: null;
|
|
1063
|
+
|
|
1064
|
+
// Pre-build lookup map: canonical key pair -> valid matchup
|
|
1065
|
+
const canonicalToMatchup = new Map();
|
|
1066
|
+
for (const matchup of setup.validMatchups) {
|
|
1067
|
+
const mHKey = this._getCanonicalKey(
|
|
1068
|
+
matchup.heroHand,
|
|
1069
|
+
setup.boardCards,
|
|
1070
|
+
setup.numberOfBoardCards,
|
|
1071
|
+
boardSuitCounts
|
|
1072
|
+
);
|
|
1073
|
+
const mVKey = this._getCanonicalKey(
|
|
1074
|
+
matchup.villainHand,
|
|
1075
|
+
setup.boardCards,
|
|
1076
|
+
setup.numberOfBoardCards,
|
|
1077
|
+
boardSuitCounts
|
|
1078
|
+
);
|
|
1079
|
+
const key = `${mHKey}:${mVKey}`;
|
|
1080
|
+
if (!canonicalToMatchup.has(key)) {
|
|
1081
|
+
canonicalToMatchup.set(key, matchup);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Calculate accurate total matchups (excluding invalid pairs with validCount=0)
|
|
1086
|
+
let totalMatchups = 0;
|
|
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
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Collect all matchups to evaluate
|
|
1097
|
+
const matchupsToEvaluate = [];
|
|
1098
|
+
for (const [hKey] of setup.hCanon.canonicalMap) {
|
|
1099
|
+
for (const [vKey] of setup.vCanon.canonicalMap) {
|
|
1100
|
+
const key = `${hKey}:${vKey}`;
|
|
1101
|
+
const validCount = setup.validCounts.get(key) || 0;
|
|
1102
|
+
|
|
1103
|
+
if (validCount === 0) continue;
|
|
1104
|
+
|
|
1105
|
+
const validPair = canonicalToMatchup.get(key);
|
|
1106
|
+
if (!validPair) {
|
|
1107
|
+
console.error(`[DIAG] ERROR: No valid pair found for ${hKey}:${vKey} despite validCount=${validCount}`);
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
matchupsToEvaluate.push({
|
|
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
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Check if workers should be used
|
|
1125
|
+
const shouldUseWorkers = useWorkers &&
|
|
1126
|
+
typeof Worker !== 'undefined' &&
|
|
1127
|
+
matchupsToEvaluate.length >= 4;
|
|
1128
|
+
|
|
1129
|
+
if (!shouldUseWorkers) {
|
|
1130
|
+
// Fallback to sequential execution
|
|
1131
|
+
return await this._compareRangeOptimizedSequential(setup, progressCallback, progressInterval);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Use Web Workers for parallelization
|
|
1135
|
+
const numWorkers = Math.min(navigator.hardwareConcurrency || 4, matchupsToEvaluate.length);
|
|
1136
|
+
const batchSize = Math.ceil(matchupsToEvaluate.length / numWorkers);
|
|
1137
|
+
const batches = [];
|
|
1138
|
+
for (let i = 0; i < matchupsToEvaluate.length; i += batchSize) {
|
|
1139
|
+
batches.push(matchupsToEvaluate.slice(i, i + batchSize));
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Serialize setup for workers
|
|
1143
|
+
const serializedSetup = this._serializeSetup(setup);
|
|
1144
|
+
|
|
1145
|
+
// Create workers and distribute work
|
|
1146
|
+
const workers = [];
|
|
1147
|
+
const promises = [];
|
|
1148
|
+
let completedMatchups = 0;
|
|
1149
|
+
|
|
1150
|
+
// Progress tracking
|
|
1151
|
+
let lastProgressTime = 0;
|
|
1152
|
+
const progressUpdateInterval = 100;
|
|
1153
|
+
const progressIntervalId = progressCallback ? setInterval(() => {
|
|
1154
|
+
const now = Date.now();
|
|
1155
|
+
if (now - lastProgressTime >= progressUpdateInterval) {
|
|
1156
|
+
progressCallback(completedMatchups, totalMatchups, `Evaluating with ${numWorkers} workers: ${completedMatchups}/${totalMatchups}`);
|
|
1157
|
+
lastProgressTime = now;
|
|
1158
|
+
}
|
|
1159
|
+
}, progressUpdateInterval) : null;
|
|
1160
|
+
|
|
1161
|
+
for (let i = 0; i < batches.length; i++) {
|
|
1162
|
+
const worker = new Worker('bitval-worker.js');
|
|
1163
|
+
workers.push(worker);
|
|
1164
|
+
|
|
1165
|
+
const promise = new Promise((resolve, reject) => {
|
|
1166
|
+
worker.onmessage = (e) => {
|
|
1167
|
+
if (e.data.success) {
|
|
1168
|
+
completedMatchups += e.data.results.length;
|
|
1169
|
+
resolve(e.data.results);
|
|
1170
|
+
} else {
|
|
1171
|
+
reject(new Error(e.data.error || 'Worker error'));
|
|
1172
|
+
}
|
|
1173
|
+
worker.terminate();
|
|
1174
|
+
};
|
|
1175
|
+
worker.onerror = (error) => {
|
|
1176
|
+
reject(error);
|
|
1177
|
+
worker.terminate();
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// Serialize matchups for this batch
|
|
1181
|
+
const serializedBatch = batches[i].map(m => this._serializeMatchup(m));
|
|
1182
|
+
|
|
1183
|
+
worker.postMessage({
|
|
1184
|
+
matchups: serializedBatch,
|
|
1185
|
+
setupData: serializedSetup,
|
|
1186
|
+
workerId: i
|
|
1187
|
+
});
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
promises.push(promise);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Wait for all workers and aggregate results
|
|
1194
|
+
let win = 0, tie = 0, lose = 0;
|
|
1195
|
+
try {
|
|
1196
|
+
const allResults = await Promise.all(promises);
|
|
1197
|
+
|
|
1198
|
+
for (const batchResults of allResults) {
|
|
1199
|
+
for (const result of batchResults) {
|
|
1200
|
+
win += result.matchupWin * result.validCount;
|
|
1201
|
+
tie += result.matchupTie * result.validCount;
|
|
1202
|
+
lose += result.matchupLose * result.validCount;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
} finally {
|
|
1206
|
+
// Clean up progress interval
|
|
1207
|
+
if (progressIntervalId) {
|
|
1208
|
+
clearInterval(progressIntervalId);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Ensure all workers are terminated
|
|
1212
|
+
workers.forEach(worker => {
|
|
1213
|
+
try {
|
|
1214
|
+
worker.terminate();
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
// Ignore termination errors
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Final progress update
|
|
1221
|
+
if (progressCallback) {
|
|
1222
|
+
progressCallback(totalMatchups, totalMatchups, 'Complete');
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return { win, tie, lose };
|
|
1227
|
+
}
|
|
1228
|
+
|
|
994
1229
|
/**
|
|
995
1230
|
* Gets cached evaluation or evaluates and caches the result.
|
|
996
1231
|
* @private
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poker-bitvaljs",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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": {
|