poker-irongrid 1.0.0 → 1.2.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 +21 -10
- package/dist/poker-grid/PokerGridManager.d.ts +45 -7
- package/dist/poker-grid/PokerGridManager.js +169 -175
- package/dist/poker-grid/components/DeadCardsInput.svelte +17 -17
- package/dist/poker-grid/components/DeadCardsInput.svelte.d.ts +2 -2
- package/dist/poker-grid/components/DetailsPanel.svelte +32 -32
- package/dist/poker-grid/components/DetailsPanel.svelte.d.ts +2 -2
- package/dist/poker-grid/components/Grid.svelte +112 -26
- package/dist/poker-grid/components/HandSelector.svelte +44 -126
- package/dist/poker-grid/components/HandSelector.svelte.d.ts +6 -4
- package/dist/poker-grid/components/PokerGridPanel.svelte +55 -6
- package/dist/poker-grid/components/PokerGridPanel.svelte.d.ts +4 -2
- package/dist/poker-grid/components/RangeSelect.svelte +7 -7
- package/dist/poker-grid/components/SelectionSummary.svelte +29 -174
- package/dist/poker-grid/components/SelectionSummary.svelte.d.ts +4 -6
- package/dist/poker-grid/index.d.ts +1 -0
- package/dist/poker-grid/index.js +4 -0
- package/dist/poker-grid/theme.css +84 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ npm install poker-irongrid
|
|
|
11
11
|
## Peer Dependencies
|
|
12
12
|
|
|
13
13
|
- `svelte`: ^5.0.0
|
|
14
|
-
- `poker-bitvaljs`: ^1.0
|
|
15
|
-
- `poker-extval`: ^
|
|
16
|
-
- `poker-rangeman`: ^
|
|
14
|
+
- `poker-bitvaljs`: ^1.13.0 (for equity evaluation)
|
|
15
|
+
- `poker-extval`: ^6.0.0-beta.6 (for hand evaluation)
|
|
16
|
+
- `poker-rangeman`: ^4.0.0-beta.11 (for range parsing and filtering)
|
|
17
17
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
@@ -84,7 +84,8 @@ npm install poker-irongrid
|
|
|
84
84
|
| `colors` | `Array<string>` | `[]` | Color palette for highlighting hands (HSL format recommended) |
|
|
85
85
|
| `defaultColor` | `string` | `'hsl(0, 0%, 100%)'` | Default background color for uncolored hands |
|
|
86
86
|
| `defaultSelectedColor` | `string` | `'hsl(0, 0%, 85%)'` | Default color for selected hands without explicit color |
|
|
87
|
-
| `evaluator` | `boolean` | `false` |
|
|
87
|
+
| `evaluator` | `boolean` | `false` | Show hero's live equity against the hands on the grid (requires hero cards) |
|
|
88
|
+
| `preflopTableUrl` | `string \| null` | `null` | URL of poker-bitvaljs's `bench/preflop-table.bin` for exact preflop equity (Monte Carlo without it) |
|
|
88
89
|
|
|
89
90
|
#### Initial State Props
|
|
90
91
|
|
|
@@ -216,12 +217,22 @@ The component includes comprehensive filtering options:
|
|
|
216
217
|
|
|
217
218
|
### Equity Evaluation
|
|
218
219
|
|
|
219
|
-
When `evaluator={true}
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
|
|
220
|
+
When `evaluator={true}`, hero's equity is calculated live (asynchronously) whenever the hero cards,
|
|
221
|
+
board, dead cards or the hands on the grid change; there is no button to press.
|
|
222
|
+
- The summary shows hero vs. villain equity against every selected or colored combo
|
|
223
|
+
- The color buttons' **Eq** mode shows hero's equity against each color's hands
|
|
224
|
+
- Both come from one poker-bitvaljs `compareRangeHands` call: exact on the flop, turn and river
|
|
225
|
+
(a few ms), Monte Carlo preflop and on 1-2 card boards
|
|
226
|
+
- Pass `preflopTableUrl` for exact, near-instant preflop equity (the table is ~1.1 MB, fetched once):
|
|
227
|
+
|
|
228
|
+
```svelte
|
|
229
|
+
<script>
|
|
230
|
+
import PokerGridPanel from 'poker-irongrid';
|
|
231
|
+
import preflopTableUrl from 'poker-bitvaljs/bench/preflop-table.bin?url'; // Vite
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<PokerGridPanel evaluator={true} {preflopTableUrl} />
|
|
235
|
+
```
|
|
225
236
|
|
|
226
237
|
### Responsive Design
|
|
227
238
|
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads poker-bitvaljs's preflop table (bench/preflop-table.bin, ~1.1 MB) once, for exact, near-instant
|
|
3
|
+
* preflop equity; without it preflop equity is a Monte Carlo estimate (within about ±0.3%).
|
|
4
|
+
* @param {string} url - Where the app serves the table
|
|
5
|
+
* @returns {Promise<boolean>} Whether the table loaded
|
|
6
|
+
*/
|
|
7
|
+
export function loadPreflopTable(url: string): Promise<boolean>;
|
|
1
8
|
export class PokerGridManager {
|
|
2
9
|
constructor(options?: {});
|
|
3
10
|
hands: any;
|
|
@@ -114,6 +121,14 @@ export class PokerGridManager {
|
|
|
114
121
|
* @returns {Promise<Object>} Filtered hands object
|
|
115
122
|
*/
|
|
116
123
|
getFilteredHandsByCriteria(criteria: string[], boardCards?: string[], hands?: any): Promise<any>;
|
|
124
|
+
/**
|
|
125
|
+
* Keep the combos of a hands object that pass a test.
|
|
126
|
+
* @param {Object} hands - Hands object
|
|
127
|
+
* @param {(c1: string, c2: string) => boolean} test - Gets the two cards of each combo
|
|
128
|
+
* @param {string[]} deadCards - Combos holding these cards are left out
|
|
129
|
+
* @returns {Object} Matching hands
|
|
130
|
+
*/
|
|
131
|
+
filterCombos(hands: any, test: (c1: string, c2: string) => boolean, deadCards?: string[]): any;
|
|
117
132
|
/**
|
|
118
133
|
* Get hands matching a rank.
|
|
119
134
|
* @param {string} rank - Rank (A, K, Q, etc.)
|
|
@@ -123,7 +138,7 @@ export class PokerGridManager {
|
|
|
123
138
|
*/
|
|
124
139
|
getHandsByRank(rank: string, hands?: any, deadCards?: string[]): any;
|
|
125
140
|
/**
|
|
126
|
-
* Get hands
|
|
141
|
+
* Get hands holding at least one card of a suit.
|
|
127
142
|
* @param {string} suitName - Suit name (clubs, diamonds, hearts, spades)
|
|
128
143
|
* @param {Object} hands - Hands object
|
|
129
144
|
* @param {string[]} deadCards - Dead cards to exclude
|
|
@@ -153,7 +168,7 @@ export class PokerGridManager {
|
|
|
153
168
|
*/
|
|
154
169
|
getSuitedHands(hands?: any, deadCards?: string[]): any;
|
|
155
170
|
/**
|
|
156
|
-
* Get offsuit hands.
|
|
171
|
+
* Get offsuit hands (different suits; includes pocket pairs).
|
|
157
172
|
* @param {Object} hands - Hands object
|
|
158
173
|
* @param {string[]} deadCards - Dead cards to exclude
|
|
159
174
|
* @returns {Object} Matching hands
|
|
@@ -185,6 +200,15 @@ export class PokerGridManager {
|
|
|
185
200
|
* @returns {string|null} Hand strength type
|
|
186
201
|
*/
|
|
187
202
|
getHandStrengthType(hand: string, boardCards?: string[]): string | null;
|
|
203
|
+
strengthCacheBoard: any;
|
|
204
|
+
strengthCache: Map<any, any>;
|
|
205
|
+
/**
|
|
206
|
+
* Uncached hand strength type (see getHandStrengthType).
|
|
207
|
+
* @param {string} hand - Hand code
|
|
208
|
+
* @param {string[]} boardCards - Board cards
|
|
209
|
+
* @returns {string|null} Hand strength type
|
|
210
|
+
*/
|
|
211
|
+
computeHandStrengthType(hand: string, boardCards: string[]): string | null;
|
|
188
212
|
/**
|
|
189
213
|
* Get hands by hand strength.
|
|
190
214
|
* @param {string} targetStrength - Strength type
|
|
@@ -218,15 +242,29 @@ export class PokerGridManager {
|
|
|
218
242
|
*/
|
|
219
243
|
removeColor(hands: any, color: string): any;
|
|
220
244
|
/**
|
|
221
|
-
*
|
|
245
|
+
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
246
|
+
* poker-bitvaljs compareRangeHands call (each villain combo's result against hero's hand).
|
|
247
|
+
* Villain hands are the combos that are selected or colored; hands holding a dead, board or
|
|
248
|
+
* hero card are left out.
|
|
222
249
|
* @param {Object} hands - Hands object
|
|
223
|
-
* @param {string[]} heroCards - Hero cards
|
|
224
|
-
* @param {string[]} boardCards - Board cards
|
|
225
|
-
* @param {string[]} deadCards - Dead cards
|
|
250
|
+
* @param {string[]} heroCards - Hero cards (two)
|
|
251
|
+
* @param {string[]} boardCards - Board cards (0-5)
|
|
252
|
+
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
226
253
|
* @param {string[]} colors - Colors array
|
|
254
|
+
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object}|null>}
|
|
255
|
+
* Equities in percent (null when hero meets no villain combo); null without two hero cards.
|
|
256
|
+
*/
|
|
257
|
+
calculateEquity(hands?: any, heroCards?: string[], boardCards?: string[], deadCards?: string[], colors?: string[]): Promise<{
|
|
258
|
+
hero: number | null;
|
|
259
|
+
villain: number | null;
|
|
260
|
+
combos: number;
|
|
261
|
+
byColor: any;
|
|
262
|
+
} | null>;
|
|
263
|
+
/**
|
|
264
|
+
* Hero's equity against each color's hands, rounded (0 when a color has no hands).
|
|
227
265
|
* @returns {Promise<Object>} Color equities object
|
|
228
266
|
*/
|
|
229
|
-
calculateColorEquities(hands?: any, heroCards?:
|
|
267
|
+
calculateColorEquities(hands?: any, heroCards?: any, boardCards?: any, deadCards?: any, colors?: any): Promise<any>;
|
|
230
268
|
/**
|
|
231
269
|
* Apply predefined range.
|
|
232
270
|
* @param {string} rangeName - Range name
|
|
@@ -11,6 +11,50 @@ import { SUITS } from './utils/constants.js';
|
|
|
11
11
|
// Handle CommonJS export
|
|
12
12
|
const BitVal = BitValModule?.default || BitValModule;
|
|
13
13
|
|
|
14
|
+
/** One shared evaluator: poker-bitvaljs keeps its Web Workers between calls. */
|
|
15
|
+
let sharedBitVal = null;
|
|
16
|
+
function getBitVal() {
|
|
17
|
+
if (!sharedBitVal) {
|
|
18
|
+
sharedBitVal = new BitVal();
|
|
19
|
+
// Monte Carlo spots (preflop, 1-2 card boards) sample boards once for every hand: much faster
|
|
20
|
+
sharedBitVal.mcSharedBoards = true;
|
|
21
|
+
}
|
|
22
|
+
return sharedBitVal;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let preflopTableLoad = null;
|
|
26
|
+
/**
|
|
27
|
+
* Loads poker-bitvaljs's preflop table (bench/preflop-table.bin, ~1.1 MB) once, for exact, near-instant
|
|
28
|
+
* preflop equity; without it preflop equity is a Monte Carlo estimate (within about ±0.3%).
|
|
29
|
+
* @param {string} url - Where the app serves the table
|
|
30
|
+
* @returns {Promise<boolean>} Whether the table loaded
|
|
31
|
+
*/
|
|
32
|
+
export function loadPreflopTable(url) {
|
|
33
|
+
if (!url) return Promise.resolve(false);
|
|
34
|
+
preflopTableLoad ||= fetch(url)
|
|
35
|
+
.then(response => {
|
|
36
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
37
|
+
return response.arrayBuffer();
|
|
38
|
+
})
|
|
39
|
+
.then(buffer => {
|
|
40
|
+
getBitVal().loadPreflopTable(buffer);
|
|
41
|
+
return true;
|
|
42
|
+
})
|
|
43
|
+
.catch(error => {
|
|
44
|
+
console.warn('poker-irongrid: preflop table not loaded, using Monte Carlo preflop equity', error);
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
return preflopTableLoad;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Order-independent key for a two-card combo ('AcKd' and 'KdAc' match). */
|
|
51
|
+
const comboKey = (combo) => {
|
|
52
|
+
const a = combo.substring(0, 2);
|
|
53
|
+
const b = combo.substring(2, 4);
|
|
54
|
+
return a < b ? a + b : b + a;
|
|
55
|
+
};
|
|
56
|
+
const isCombo = (hand) => typeof hand === 'string' && hand.length === 4;
|
|
57
|
+
|
|
14
58
|
// Criteria mappings for RangeManager.hitsHand()
|
|
15
59
|
const strengthCriteriaMap = {
|
|
16
60
|
'onePair': ['Pair'],
|
|
@@ -278,9 +322,13 @@ export class PokerGridManager {
|
|
|
278
322
|
* @returns {Object} Matching hands object
|
|
279
323
|
*/
|
|
280
324
|
rangeManagerToMatching(rmResult, hands = this.hands) {
|
|
325
|
+
// Match by both cards, whatever order poker-rangeman lists them in
|
|
326
|
+
const keyed = new Map();
|
|
327
|
+
Object.keys(hands || {}).forEach(h => { if (isCombo(h)) keyed.set(comboKey(h), h); });
|
|
281
328
|
const matching = {};
|
|
282
329
|
rmResult.toArray().forEach(hand => {
|
|
283
|
-
|
|
330
|
+
const original = keyed.get(comboKey(hand));
|
|
331
|
+
if (original) matching[original] = hands[original];
|
|
284
332
|
});
|
|
285
333
|
return matching;
|
|
286
334
|
}
|
|
@@ -308,6 +356,25 @@ export class PokerGridManager {
|
|
|
308
356
|
}
|
|
309
357
|
}
|
|
310
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Keep the combos of a hands object that pass a test.
|
|
361
|
+
* @param {Object} hands - Hands object
|
|
362
|
+
* @param {(c1: string, c2: string) => boolean} test - Gets the two cards of each combo
|
|
363
|
+
* @param {string[]} deadCards - Combos holding these cards are left out
|
|
364
|
+
* @returns {Object} Matching hands
|
|
365
|
+
*/
|
|
366
|
+
filterCombos(hands, test, deadCards = []) {
|
|
367
|
+
const matching = {};
|
|
368
|
+
Object.keys(hands || {}).forEach(h => {
|
|
369
|
+
if (!isCombo(h)) return;
|
|
370
|
+
const c1 = h.substring(0, 2);
|
|
371
|
+
const c2 = h.substring(2, 4);
|
|
372
|
+
if (deadCards.includes(c1) || deadCards.includes(c2)) return;
|
|
373
|
+
if (test(c1, c2)) matching[h] = hands[h];
|
|
374
|
+
});
|
|
375
|
+
return matching;
|
|
376
|
+
}
|
|
377
|
+
|
|
311
378
|
/**
|
|
312
379
|
* Get hands matching a rank.
|
|
313
380
|
* @param {string} rank - Rank (A, K, Q, etc.)
|
|
@@ -316,20 +383,11 @@ export class PokerGridManager {
|
|
|
316
383
|
* @returns {Object} Matching hands
|
|
317
384
|
*/
|
|
318
385
|
getHandsByRank(rank, hands = this.hands, deadCards = []) {
|
|
319
|
-
|
|
320
|
-
if (allHands.length === 0) return {};
|
|
321
|
-
try {
|
|
322
|
-
const rankRange = `${rank}Xs,${rank}Xo`;
|
|
323
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
324
|
-
const matched = currentRm.intersect(rankRange);
|
|
325
|
-
return this.rangeManagerToMatching(matched, hands);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
return {};
|
|
328
|
-
}
|
|
386
|
+
return this.filterCombos(hands, (c1, c2) => c1[0] === rank || c2[0] === rank, deadCards);
|
|
329
387
|
}
|
|
330
388
|
|
|
331
389
|
/**
|
|
332
|
-
* Get hands
|
|
390
|
+
* Get hands holding at least one card of a suit.
|
|
333
391
|
* @param {string} suitName - Suit name (clubs, diamonds, hearts, spades)
|
|
334
392
|
* @param {Object} hands - Hands object
|
|
335
393
|
* @param {string[]} deadCards - Dead cards to exclude
|
|
@@ -338,15 +396,7 @@ export class PokerGridManager {
|
|
|
338
396
|
getHandsBySuit(suitName, hands = this.hands, deadCards = []) {
|
|
339
397
|
const suitCode = SUITS.find(s => s.name === suitName)?.code;
|
|
340
398
|
if (!suitCode) return {};
|
|
341
|
-
|
|
342
|
-
if (allHands.length === 0) return {};
|
|
343
|
-
try {
|
|
344
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
345
|
-
const matched = currentRm.hasSuit(suitCode);
|
|
346
|
-
return this.rangeManagerToMatching(matched, hands);
|
|
347
|
-
} catch (error) {
|
|
348
|
-
return {};
|
|
349
|
-
}
|
|
399
|
+
return this.filterCombos(hands, (c1, c2) => c1[1] === suitCode || c2[1] === suitCode, deadCards);
|
|
350
400
|
}
|
|
351
401
|
|
|
352
402
|
/**
|
|
@@ -359,15 +409,7 @@ export class PokerGridManager {
|
|
|
359
409
|
getHandsByDoubleSuit(suitName, hands = this.hands, deadCards = []) {
|
|
360
410
|
const suitCode = SUITS.find(s => s.name === suitName)?.code;
|
|
361
411
|
if (!suitCode) return {};
|
|
362
|
-
|
|
363
|
-
if (allHands.length === 0) return {};
|
|
364
|
-
try {
|
|
365
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
366
|
-
const matched = currentRm.suitedOf(suitCode);
|
|
367
|
-
return this.rangeManagerToMatching(matched, hands);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
return {};
|
|
370
|
-
}
|
|
412
|
+
return this.filterCombos(hands, (c1, c2) => c1[1] === suitCode && c2[1] === suitCode, deadCards);
|
|
371
413
|
}
|
|
372
414
|
|
|
373
415
|
/**
|
|
@@ -377,15 +419,7 @@ export class PokerGridManager {
|
|
|
377
419
|
* @returns {Object} Matching hands
|
|
378
420
|
*/
|
|
379
421
|
getPairedHands(hands = this.hands, deadCards = []) {
|
|
380
|
-
|
|
381
|
-
if (allHands.length === 0) return {};
|
|
382
|
-
try {
|
|
383
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
384
|
-
const matched = currentRm.intersect('22+');
|
|
385
|
-
return this.rangeManagerToMatching(matched, hands);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
return {};
|
|
388
|
-
}
|
|
422
|
+
return this.filterCombos(hands, (c1, c2) => c1[0] === c2[0], deadCards);
|
|
389
423
|
}
|
|
390
424
|
|
|
391
425
|
/**
|
|
@@ -395,46 +429,17 @@ export class PokerGridManager {
|
|
|
395
429
|
* @returns {Object} Matching hands
|
|
396
430
|
*/
|
|
397
431
|
getSuitedHands(hands = this.hands, deadCards = []) {
|
|
398
|
-
|
|
399
|
-
if (allHands.length === 0) return {};
|
|
400
|
-
try {
|
|
401
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
402
|
-
// Use method chaining to combine all suited hands
|
|
403
|
-
let allSuited = new RangeManager([]);
|
|
404
|
-
['c', 'd', 'h', 's'].forEach(suit => {
|
|
405
|
-
allSuited = allSuited.include(currentRm.suitedOf(suit));
|
|
406
|
-
});
|
|
407
|
-
return this.rangeManagerToMatching(allSuited, hands);
|
|
408
|
-
} catch (error) {
|
|
409
|
-
return {};
|
|
410
|
-
}
|
|
432
|
+
return this.filterCombos(hands, (c1, c2) => c1[1] === c2[1], deadCards);
|
|
411
433
|
}
|
|
412
434
|
|
|
413
435
|
/**
|
|
414
|
-
* Get offsuit hands.
|
|
436
|
+
* Get offsuit hands (different suits; includes pocket pairs).
|
|
415
437
|
* @param {Object} hands - Hands object
|
|
416
438
|
* @param {string[]} deadCards - Dead cards to exclude
|
|
417
439
|
* @returns {Object} Matching hands
|
|
418
440
|
*/
|
|
419
441
|
getOffsuitHands(hands = this.hands, deadCards = []) {
|
|
420
|
-
|
|
421
|
-
if (allHands.length === 0) return {};
|
|
422
|
-
try {
|
|
423
|
-
const currentRm = this.getHandsRangeManager(hands, deadCards);
|
|
424
|
-
// Use method chaining to combine all offsuit hands
|
|
425
|
-
// For offsuit: get hands that have the suit but exclude suited hands
|
|
426
|
-
let allOffsuit = new RangeManager([]);
|
|
427
|
-
['c', 'd', 'h', 's'].forEach(suit => {
|
|
428
|
-
// Get hands that have this suit, then exclude suited hands
|
|
429
|
-
const hasSuit = currentRm.hasSuit(suit);
|
|
430
|
-
const suited = currentRm.suitedOf(suit);
|
|
431
|
-
const offsuit = hasSuit.exclude(suited);
|
|
432
|
-
allOffsuit = allOffsuit.include(offsuit);
|
|
433
|
-
});
|
|
434
|
-
return this.rangeManagerToMatching(allOffsuit, hands);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
return {};
|
|
437
|
-
}
|
|
442
|
+
return this.filterCombos(hands, (c1, c2) => c1[1] !== c2[1], deadCards);
|
|
438
443
|
}
|
|
439
444
|
|
|
440
445
|
/**
|
|
@@ -485,6 +490,24 @@ export class PokerGridManager {
|
|
|
485
490
|
*/
|
|
486
491
|
getHandStrengthType(hand, boardCards = this.boardCards) {
|
|
487
492
|
if (!boardCards || boardCards.length < 3) return null;
|
|
493
|
+
const boardKey = boardCards.join('');
|
|
494
|
+
if (this.strengthCacheBoard !== boardKey) {
|
|
495
|
+
this.strengthCacheBoard = boardKey;
|
|
496
|
+
this.strengthCache = new Map();
|
|
497
|
+
}
|
|
498
|
+
if (this.strengthCache.has(hand)) return this.strengthCache.get(hand);
|
|
499
|
+
const type = this.computeHandStrengthType(hand, boardCards);
|
|
500
|
+
this.strengthCache.set(hand, type);
|
|
501
|
+
return type;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Uncached hand strength type (see getHandStrengthType).
|
|
506
|
+
* @param {string} hand - Hand code
|
|
507
|
+
* @param {string[]} boardCards - Board cards
|
|
508
|
+
* @returns {string|null} Hand strength type
|
|
509
|
+
*/
|
|
510
|
+
computeHandStrengthType(hand, boardCards) {
|
|
488
511
|
try {
|
|
489
512
|
const handCard1 = hand.substring(0, 2);
|
|
490
513
|
const handCard2 = hand.substring(2, 4);
|
|
@@ -507,7 +530,6 @@ export class PokerGridManager {
|
|
|
507
530
|
return null;
|
|
508
531
|
} catch (error) {
|
|
509
532
|
return null;
|
|
510
|
-
return null;
|
|
511
533
|
}
|
|
512
534
|
}
|
|
513
535
|
|
|
@@ -630,83 +652,79 @@ export class PokerGridManager {
|
|
|
630
652
|
}
|
|
631
653
|
|
|
632
654
|
/**
|
|
633
|
-
*
|
|
655
|
+
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
656
|
+
* poker-bitvaljs compareRangeHands call (each villain combo's result against hero's hand).
|
|
657
|
+
* Villain hands are the combos that are selected or colored; hands holding a dead, board or
|
|
658
|
+
* hero card are left out.
|
|
634
659
|
* @param {Object} hands - Hands object
|
|
635
|
-
* @param {string[]} heroCards - Hero cards
|
|
636
|
-
* @param {string[]} boardCards - Board cards
|
|
637
|
-
* @param {string[]} deadCards - Dead cards
|
|
660
|
+
* @param {string[]} heroCards - Hero cards (two)
|
|
661
|
+
* @param {string[]} boardCards - Board cards (0-5)
|
|
662
|
+
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
638
663
|
* @param {string[]} colors - Colors array
|
|
639
|
-
* @returns {Promise<Object>}
|
|
664
|
+
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object}|null>}
|
|
665
|
+
* Equities in percent (null when hero meets no villain combo); null without two hero cards.
|
|
640
666
|
*/
|
|
641
|
-
async
|
|
642
|
-
if (heroCards?.length !== 2)
|
|
643
|
-
|
|
644
|
-
|
|
667
|
+
async calculateEquity(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors) {
|
|
668
|
+
if (heroCards?.length !== 2) return null;
|
|
669
|
+
const board = boardCards || [];
|
|
670
|
+
const otherDead = (deadCards || []).filter(card => !heroCards.includes(card) && !board.includes(card));
|
|
671
|
+
const blocked = new Set([...heroCards, ...board, ...otherDead]);
|
|
672
|
+
|
|
673
|
+
const villainHands = Object.keys(hands || {}).filter(h =>
|
|
674
|
+
isCombo(h) &&
|
|
675
|
+
(this.hasExplicitColor(hands[h]) || this.isSelected(hands[h])) &&
|
|
676
|
+
!blocked.has(h.substring(0, 2)) && !blocked.has(h.substring(2, 4))
|
|
677
|
+
);
|
|
645
678
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
);
|
|
650
|
-
|
|
651
|
-
if (coloredHands.length === 0) {
|
|
652
|
-
const equities = {};
|
|
653
|
-
colors.forEach(c => equities[c] = 0);
|
|
654
|
-
return equities;
|
|
655
|
-
}
|
|
679
|
+
const byColor = {};
|
|
680
|
+
(colors || []).forEach(c => { byColor[c] = null; });
|
|
681
|
+
if (villainHands.length === 0) return { hero: null, villain: null, combos: 0, byColor };
|
|
656
682
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const heroHands = [heroHand];
|
|
660
|
-
const numberOfBoardCards = 5;
|
|
661
|
-
|
|
662
|
-
const deadCardsFromInput = (deadCards || []).filter(card =>
|
|
663
|
-
!(heroCards || []).includes(card) && !(boardCards || []).includes(card)
|
|
664
|
-
);
|
|
665
|
-
const deadCardsForBitval = [...heroCards, ...deadCardsFromInput];
|
|
666
|
-
|
|
667
|
-
const boardCardsCount = (boardCards || []).length;
|
|
668
|
-
const optimize = boardCardsCount < 3;
|
|
683
|
+
// Preflop, wait for the table if one is loading (otherwise the result would be Monte Carlo)
|
|
684
|
+
if (board.length === 0 && preflopTableLoad) await preflopTableLoad;
|
|
669
685
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const colorHands = coloredHands.filter(h =>
|
|
676
|
-
hands[h].some(item => item === color)
|
|
677
|
-
);
|
|
686
|
+
// compareRangeHands(hero, villain, board, dead, numberOfBoardCards, iterations, optimize,
|
|
687
|
+
// progressCallback, progressInterval, useWorkers): exact on the flop/turn/river, Monte Carlo otherwise
|
|
688
|
+
const result = await getBitVal().compareRangeHands(
|
|
689
|
+
[heroCards[0] + heroCards[1]], villainHands, board, otherDead, 5, 20000, true, null, 100, true
|
|
690
|
+
);
|
|
678
691
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
692
|
+
// Villain rows are each villain combo's own win/tie/lose against hero
|
|
693
|
+
const rowOf = new Map();
|
|
694
|
+
result.villain.hands.forEach((h, i) => rowOf.set(comboKey(h), i));
|
|
695
|
+
const heroEquityAgainst = (combos) => {
|
|
696
|
+
let heroShare = 0;
|
|
697
|
+
let total = 0;
|
|
698
|
+
combos.forEach(h => {
|
|
699
|
+
const i = rowOf.get(comboKey(h));
|
|
700
|
+
if (i === undefined) return;
|
|
701
|
+
const win = result.villain.win[i];
|
|
702
|
+
const tie = result.villain.tie[i];
|
|
703
|
+
const lose = result.villain.lose[i];
|
|
704
|
+
heroShare += lose + tie / 2;
|
|
705
|
+
total += win + tie + lose;
|
|
706
|
+
});
|
|
707
|
+
return total > 0 ? (heroShare / total) * 100 : null;
|
|
708
|
+
};
|
|
683
709
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
deadCardsForBitval,
|
|
690
|
-
numberOfBoardCards,
|
|
691
|
-
10000,
|
|
692
|
-
optimize,
|
|
693
|
-
null
|
|
694
|
-
);
|
|
710
|
+
const hero = heroEquityAgainst(villainHands);
|
|
711
|
+
(colors || []).forEach(color => {
|
|
712
|
+
const colorHands = villainHands.filter(h => hands[h].includes(color));
|
|
713
|
+
byColor[color] = colorHands.length > 0 ? heroEquityAgainst(colorHands) : null;
|
|
714
|
+
});
|
|
695
715
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
return {};
|
|
709
|
-
}
|
|
716
|
+
return { hero, villain: hero === null ? null : 100 - hero, combos: villainHands.length, byColor };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Hero's equity against each color's hands, rounded (0 when a color has no hands).
|
|
721
|
+
* @returns {Promise<Object>} Color equities object
|
|
722
|
+
*/
|
|
723
|
+
async calculateColorEquities(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors) {
|
|
724
|
+
const equity = await this.calculateEquity(hands, heroCards, boardCards, deadCards, colors);
|
|
725
|
+
const equities = {};
|
|
726
|
+
(colors || []).forEach(c => { equities[c] = Math.round(equity?.byColor?.[c] ?? 0); });
|
|
727
|
+
return equities;
|
|
710
728
|
}
|
|
711
729
|
|
|
712
730
|
// ==================== Range Methods ====================
|
|
@@ -1037,47 +1055,24 @@ export class PokerGridManager {
|
|
|
1037
1055
|
if (activeHands.length === 0) return false;
|
|
1038
1056
|
|
|
1039
1057
|
try {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1058
|
+
const active = {};
|
|
1059
|
+
activeHands.forEach(h => { active[h] = hands[h]; });
|
|
1060
|
+
const allMatch = (matching) => Object.keys(matching).length === activeHands.length;
|
|
1061
|
+
|
|
1042
1062
|
switch (filterType) {
|
|
1043
1063
|
case 'rank':
|
|
1044
|
-
|
|
1045
|
-
const rankRange = `${filterValue}${filterValue},${filterValue}2s+,${filterValue}2o+`;
|
|
1046
|
-
return activeRm.intersect(rankRange).size() === activeHands.length;
|
|
1047
|
-
|
|
1064
|
+
return allMatch(this.getHandsByRank(filterValue, active));
|
|
1048
1065
|
case 'suit':
|
|
1049
|
-
|
|
1050
|
-
if (!suitCode) return false;
|
|
1051
|
-
return activeRm.hasSuit(suitCode).size() === activeHands.length;
|
|
1052
|
-
|
|
1066
|
+
return allMatch(this.getHandsBySuit(filterValue, active));
|
|
1053
1067
|
case 'doubleSuit':
|
|
1054
|
-
|
|
1055
|
-
if (!doubleSuitCode) return false;
|
|
1056
|
-
return activeRm.suitedOf(doubleSuitCode).size() === activeHands.length;
|
|
1057
|
-
|
|
1068
|
+
return allMatch(this.getHandsByDoubleSuit(filterValue, active));
|
|
1058
1069
|
case 'paired':
|
|
1059
|
-
return
|
|
1060
|
-
|
|
1070
|
+
return allMatch(this.getPairedHands(active));
|
|
1061
1071
|
case 'suited':
|
|
1062
|
-
|
|
1063
|
-
return ['c', 'd', 'h', 's'].some(suit =>
|
|
1064
|
-
activeRm.suitedOf(suit).size() === activeHands.length
|
|
1065
|
-
);
|
|
1066
|
-
|
|
1072
|
+
return allMatch(this.getSuitedHands(active));
|
|
1067
1073
|
case 'unsuited':
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
return ['c', 'd', 'h', 's'].some(suit => {
|
|
1071
|
-
try {
|
|
1072
|
-
const hasSuit = activeRm.hasSuit(suit);
|
|
1073
|
-
const suited = activeRm.suitedOf(suit);
|
|
1074
|
-
const offsuit = hasSuit.exclude(suited);
|
|
1075
|
-
return offsuit.size() === activeHands.length;
|
|
1076
|
-
} catch (error) {
|
|
1077
|
-
return false;
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1074
|
+
return allMatch(this.getOffsuitHands(active));
|
|
1075
|
+
|
|
1081
1076
|
case 'colored':
|
|
1082
1077
|
return activeHands.every(h =>
|
|
1083
1078
|
Array.isArray(hands[h]) && hands[h].some(item => typeof item === 'string' && item.startsWith('hsl('))
|
|
@@ -1123,7 +1118,6 @@ export class PokerGridManager {
|
|
|
1123
1118
|
}
|
|
1124
1119
|
} catch (error) {
|
|
1125
1120
|
return false;
|
|
1126
|
-
return false;
|
|
1127
1121
|
}
|
|
1128
1122
|
}
|
|
1129
1123
|
|