poker-irongrid 1.2.1 → 1.4.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 +7 -1
- package/dist/poker-grid/PokerGridManager.d.ts +26 -5
- package/dist/poker-grid/PokerGridManager.js +62 -8
- package/dist/poker-grid/components/Grid.svelte +56 -13
- package/dist/poker-grid/components/Grid.svelte.d.ts +2 -0
- package/dist/poker-grid/components/PokerGridPanel.svelte +34 -3
- package/dist/poker-grid/components/PokerGridPanel.svelte.d.ts +6 -2
- package/dist/poker-grid/theme.css +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,8 @@ npm install poker-irongrid
|
|
|
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
87
|
| `evaluator` | `boolean` | `false` | Show hero's live equity against the hands on the grid (requires hero cards) |
|
|
88
|
+
| `equityColors` | `boolean` | `true` | With `evaluator`, color villain hands by hero's equity against each combo: equal-width bands, one per color (`colors[0]` = hero's lowest equity). Hands you color yourself keep their color |
|
|
89
|
+
| `subgrid` | `boolean` | `false` | Split the grid into 3x3 blocks (A–T, 9–6, 5–2) with space after the T and 6 rows/columns; spacing set by `--irongrid-subgrid-gap` (default `6px`) |
|
|
88
90
|
| `preflopTableUrl` | `string \| null` | `null` | URL of poker-bitvaljs's `bench/preflop-table.bin` for exact preflop equity (Monte Carlo without it) |
|
|
89
91
|
|
|
90
92
|
#### Initial State Props
|
|
@@ -221,7 +223,11 @@ When `evaluator={true}`, hero's equity is calculated live (asynchronously) whene
|
|
|
221
223
|
board, dead cards or the hands on the grid change; there is no button to press.
|
|
222
224
|
- The summary shows hero vs. villain equity against every selected or colored combo
|
|
223
225
|
- The color buttons' **Eq** mode shows hero's equity against each color's hands
|
|
224
|
-
-
|
|
226
|
+
- With `equityColors` (default), the villain hands are colored by hero's equity against each combo, in
|
|
227
|
+
one equal-width band per color (6 colors: 0–16.7%, 16.7–33.3%, … 83.3–100%, `colors[0]` lowest), so the
|
|
228
|
+
color buttons show each band's count, share and equity. Hands you color yourself keep their color; band
|
|
229
|
+
colors follow the selection and go away without hero cards
|
|
230
|
+
- All of it comes from one poker-bitvaljs `compareRangeHands` call: exact on the flop, turn and river
|
|
225
231
|
(a few ms), Monte Carlo preflop and on 1-2 card boards
|
|
226
232
|
- Pass `preflopTableUrl` for exact, near-instant preflop equity (the table is ~1.1 MB, fetched once):
|
|
227
233
|
|
|
@@ -244,22 +244,43 @@ export class PokerGridManager {
|
|
|
244
244
|
/**
|
|
245
245
|
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
246
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
|
|
248
|
-
* hero card are left out.
|
|
247
|
+
* Villain hands are the combos that are selected or colored by hand (colors in `autoColors`, set by
|
|
248
|
+
* applyEquityBands, don't count); hands holding a dead, board or hero card are left out.
|
|
249
249
|
* @param {Object} hands - Hands object
|
|
250
250
|
* @param {string[]} heroCards - Hero cards (two)
|
|
251
251
|
* @param {string[]} boardCards - Board cards (0-5)
|
|
252
252
|
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
253
253
|
* @param {string[]} colors - Colors array
|
|
254
|
-
* @
|
|
255
|
-
*
|
|
254
|
+
* @param {Map<string, string>|null} autoColors - Colors applyEquityBands set (combo -> color)
|
|
255
|
+
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object, byCombo: Object}|null>}
|
|
256
|
+
* Equities in percent (null when hero meets no villain combo); byCombo is hero's equity against each
|
|
257
|
+
* villain combo. Null without two hero cards.
|
|
256
258
|
*/
|
|
257
|
-
calculateEquity(hands?: any, heroCards?: string[], boardCards?: string[], deadCards?: string[], colors?: string[]): Promise<{
|
|
259
|
+
calculateEquity(hands?: any, heroCards?: string[], boardCards?: string[], deadCards?: string[], colors?: string[], autoColors?: Map<string, string> | null): Promise<{
|
|
258
260
|
hero: number | null;
|
|
259
261
|
villain: number | null;
|
|
260
262
|
combos: number;
|
|
261
263
|
byColor: any;
|
|
264
|
+
byCombo: any;
|
|
262
265
|
} | null>;
|
|
266
|
+
/**
|
|
267
|
+
* Color villain combos by hero's equity against them: one equal-width band per color, colors[0] for
|
|
268
|
+
* hero's lowest equity (villain's strongest hands) up to the last color for hero's highest. With 6
|
|
269
|
+
* colors the bands are 0-16.7%, 16.7-33.3%, ... 83.3-100%.
|
|
270
|
+
* Only combos without a color, or still holding the color this method gave them, are recolored, so
|
|
271
|
+
* hand-applied colors stay. Colors this method set are removed from combos that are no longer in
|
|
272
|
+
* `byCombo` (deselected, dead, or no equity).
|
|
273
|
+
* @param {Object} hands - Hands object
|
|
274
|
+
* @param {Object} byCombo - Hero's equity (%) against each villain combo, from calculateEquity
|
|
275
|
+
* @param {string[]} colors - Colors, one per band
|
|
276
|
+
* @param {Map<string, string>} autoColors - Colors set by earlier calls (combo -> color)
|
|
277
|
+
* @returns {{hands: Object, autoColors: Map<string, string>, changed: boolean}}
|
|
278
|
+
*/
|
|
279
|
+
applyEquityBands(hands: any, byCombo: any, colors: string[], autoColors?: Map<string, string>): {
|
|
280
|
+
hands: any;
|
|
281
|
+
autoColors: Map<string, string>;
|
|
282
|
+
changed: boolean;
|
|
283
|
+
};
|
|
263
284
|
/**
|
|
264
285
|
* Hero's equity against each color's hands, rounded (0 when a color has no hands).
|
|
265
286
|
* @returns {Promise<Object>} Color equities object
|
|
@@ -654,31 +654,38 @@ export class PokerGridManager {
|
|
|
654
654
|
/**
|
|
655
655
|
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
656
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
|
|
658
|
-
* hero card are left out.
|
|
657
|
+
* Villain hands are the combos that are selected or colored by hand (colors in `autoColors`, set by
|
|
658
|
+
* applyEquityBands, don't count); hands holding a dead, board or hero card are left out.
|
|
659
659
|
* @param {Object} hands - Hands object
|
|
660
660
|
* @param {string[]} heroCards - Hero cards (two)
|
|
661
661
|
* @param {string[]} boardCards - Board cards (0-5)
|
|
662
662
|
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
663
663
|
* @param {string[]} colors - Colors array
|
|
664
|
-
* @
|
|
665
|
-
*
|
|
664
|
+
* @param {Map<string, string>|null} autoColors - Colors applyEquityBands set (combo -> color)
|
|
665
|
+
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object, byCombo: Object}|null>}
|
|
666
|
+
* Equities in percent (null when hero meets no villain combo); byCombo is hero's equity against each
|
|
667
|
+
* villain combo. Null without two hero cards.
|
|
666
668
|
*/
|
|
667
|
-
async calculateEquity(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors) {
|
|
669
|
+
async calculateEquity(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors, autoColors = null) {
|
|
668
670
|
if (heroCards?.length !== 2) return null;
|
|
669
671
|
const board = boardCards || [];
|
|
670
672
|
const otherDead = (deadCards || []).filter(card => !heroCards.includes(card) && !board.includes(card));
|
|
671
673
|
const blocked = new Set([...heroCards, ...board, ...otherDead]);
|
|
672
674
|
|
|
675
|
+
const colorOf = (h) => (hands[h] || []).find(item => typeof item === 'string' && item.startsWith('hsl('));
|
|
676
|
+
const coloredByHand = (h) => {
|
|
677
|
+
const color = colorOf(h);
|
|
678
|
+
return !!color && !(autoColors && autoColors.get(h) === color);
|
|
679
|
+
};
|
|
673
680
|
const villainHands = Object.keys(hands || {}).filter(h =>
|
|
674
681
|
isCombo(h) &&
|
|
675
|
-
(
|
|
682
|
+
(coloredByHand(h) || this.isSelected(hands[h])) &&
|
|
676
683
|
!blocked.has(h.substring(0, 2)) && !blocked.has(h.substring(2, 4))
|
|
677
684
|
);
|
|
678
685
|
|
|
679
686
|
const byColor = {};
|
|
680
687
|
(colors || []).forEach(c => { byColor[c] = null; });
|
|
681
|
-
if (villainHands.length === 0) return { hero: null, villain: null, combos: 0, byColor };
|
|
688
|
+
if (villainHands.length === 0) return { hero: null, villain: null, combos: 0, byColor, byCombo: {} };
|
|
682
689
|
|
|
683
690
|
// Preflop, wait for the table if one is loading (otherwise the result would be Monte Carlo)
|
|
684
691
|
if (board.length === 0 && preflopTableLoad) await preflopTableLoad;
|
|
@@ -708,12 +715,59 @@ export class PokerGridManager {
|
|
|
708
715
|
};
|
|
709
716
|
|
|
710
717
|
const hero = heroEquityAgainst(villainHands);
|
|
718
|
+
const byCombo = {};
|
|
719
|
+
villainHands.forEach(h => { byCombo[h] = heroEquityAgainst([h]); });
|
|
711
720
|
(colors || []).forEach(color => {
|
|
712
721
|
const colorHands = villainHands.filter(h => hands[h].includes(color));
|
|
713
722
|
byColor[color] = colorHands.length > 0 ? heroEquityAgainst(colorHands) : null;
|
|
714
723
|
});
|
|
715
724
|
|
|
716
|
-
return { hero, villain: hero === null ? null : 100 - hero, combos: villainHands.length, byColor };
|
|
725
|
+
return { hero, villain: hero === null ? null : 100 - hero, combos: villainHands.length, byColor, byCombo };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Color villain combos by hero's equity against them: one equal-width band per color, colors[0] for
|
|
730
|
+
* hero's lowest equity (villain's strongest hands) up to the last color for hero's highest. With 6
|
|
731
|
+
* colors the bands are 0-16.7%, 16.7-33.3%, ... 83.3-100%.
|
|
732
|
+
* Only combos without a color, or still holding the color this method gave them, are recolored, so
|
|
733
|
+
* hand-applied colors stay. Colors this method set are removed from combos that are no longer in
|
|
734
|
+
* `byCombo` (deselected, dead, or no equity).
|
|
735
|
+
* @param {Object} hands - Hands object
|
|
736
|
+
* @param {Object} byCombo - Hero's equity (%) against each villain combo, from calculateEquity
|
|
737
|
+
* @param {string[]} colors - Colors, one per band
|
|
738
|
+
* @param {Map<string, string>} autoColors - Colors set by earlier calls (combo -> color)
|
|
739
|
+
* @returns {{hands: Object, autoColors: Map<string, string>, changed: boolean}}
|
|
740
|
+
*/
|
|
741
|
+
applyEquityBands(hands, byCombo, colors, autoColors = new Map()) {
|
|
742
|
+
const next = new Map();
|
|
743
|
+
const newHands = { ...hands };
|
|
744
|
+
let changed = false;
|
|
745
|
+
const colorOf = (values) => (values || []).find(item => typeof item === 'string' && item.startsWith('hsl('));
|
|
746
|
+
const setColor = (h, color) => {
|
|
747
|
+
const kept = (newHands[h] || []).filter(item => !(typeof item === 'string' && item.startsWith('hsl(')));
|
|
748
|
+
newHands[h] = color ? [...kept, color] : kept;
|
|
749
|
+
changed = true;
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
if (colors && colors.length > 0) {
|
|
753
|
+
const bandWidth = 100 / colors.length;
|
|
754
|
+
Object.entries(byCombo || {}).forEach(([h, equity]) => {
|
|
755
|
+
if (equity === null || equity === undefined || Number.isNaN(equity)) return;
|
|
756
|
+
const current = colorOf(newHands[h]);
|
|
757
|
+
if (current && autoColors.get(h) !== current) return; // colored by hand
|
|
758
|
+
const color = colors[Math.min(colors.length - 1, Math.floor(equity / bandWidth))];
|
|
759
|
+
next.set(h, color);
|
|
760
|
+
if (current !== color) setColor(h, color);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Combos that lost their equity color (no longer villain hands, or no equity)
|
|
765
|
+
autoColors.forEach((color, h) => {
|
|
766
|
+
if (next.has(h) || !newHands[h]) return;
|
|
767
|
+
if (colorOf(newHands[h]) === color) setColor(h, null);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
return { hands: changed ? newHands : hands, autoColors: next, changed };
|
|
717
771
|
}
|
|
718
772
|
|
|
719
773
|
/**
|
|
@@ -18,6 +18,42 @@
|
|
|
18
18
|
export let showTips = false;
|
|
19
19
|
/** Whether to enable double-click details panel. */
|
|
20
20
|
export let enableDetailsPanel = false;
|
|
21
|
+
/**
|
|
22
|
+
* Split the grid into 3x3 blocks: extra space after the T and 6 rows/columns (A-T, 9-6, 5-2).
|
|
23
|
+
* The space is set by the --irongrid-subgrid-gap CSS variable.
|
|
24
|
+
*/
|
|
25
|
+
export let subgrid = false;
|
|
26
|
+
|
|
27
|
+
/** CSS grid line for a row/column index: with subgrid, spacer tracks follow T (index 4) and 6 (index 8). */
|
|
28
|
+
function gridLine(index, split) {
|
|
29
|
+
return index + 1 + (split ? (index >= 5 ? 1 : 0) + (index >= 9 ? 1 : 0) : 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Hairline frame around each 3x3 block (subgrid): inset shadows on the block's edge cells, so cells stay
|
|
34
|
+
* flush with their neighbours and only the blocks are outlined.
|
|
35
|
+
*/
|
|
36
|
+
function blockFrame(row, col, split) {
|
|
37
|
+
if (!split) return '';
|
|
38
|
+
const shadows = [];
|
|
39
|
+
if (row === 0 || row === 5 || row === 9) shadows.push(`inset 0 var(--irongrid-border-width) 0 var(--irongrid-color-border)`);
|
|
40
|
+
if (row === 4 || row === 8 || row === 12) shadows.push(`inset 0 calc(-1 * var(--irongrid-border-width)) 0 var(--irongrid-color-border)`);
|
|
41
|
+
if (col === 0 || col === 5 || col === 9) shadows.push(`inset var(--irongrid-border-width) 0 0 var(--irongrid-color-border)`);
|
|
42
|
+
if (col === 4 || col === 8 || col === 12) shadows.push(`inset calc(-1 * var(--irongrid-border-width)) 0 0 var(--irongrid-color-border)`);
|
|
43
|
+
return shadows.length ? `box-shadow: ${shadows.join(', ')};` : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The grid cell under a point (or null), from the cells' own positions, so it works with any spacing. */
|
|
47
|
+
function cellAtPoint(gridElement, x, y) {
|
|
48
|
+
const cells = gridElement.querySelectorAll('[data-row]');
|
|
49
|
+
for (const cell of cells) {
|
|
50
|
+
const r = cell.getBoundingClientRect();
|
|
51
|
+
if (x >= r.left && x < r.right && y >= r.top && y < r.bottom) {
|
|
52
|
+
return { element: cell, rect: r, row: Number(cell.dataset.row), col: Number(cell.dataset.col) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
21
57
|
|
|
22
58
|
let isDragging = false;
|
|
23
59
|
let tooltip = { visible: false, x: 0, y: 0, content: '' };
|
|
@@ -187,19 +223,16 @@
|
|
|
187
223
|
function handleMouseMove(event) {
|
|
188
224
|
if (!isDragging) return;
|
|
189
225
|
const gridElement = event.currentTarget;
|
|
190
|
-
const rect = gridElement.getBoundingClientRect();
|
|
191
|
-
const cellSize = rect.width / 13;
|
|
192
226
|
const coords = getEventCoordinates(event);
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
if (colIndex < 0 || colIndex >= 13 || rowIndex < 0 || rowIndex >= 13) return;
|
|
198
|
-
const cellInfo = getCellInfo(rowIndex, colIndex);
|
|
199
|
-
const cellRect = { left: colIndex * cellSize, top: rowIndex * cellSize, width: cellSize, height: cellSize };
|
|
227
|
+
const hit = cellAtPoint(gridElement, coords.x, coords.y);
|
|
228
|
+
if (!hit) return;
|
|
229
|
+
const cellInfo = getCellInfo(hit.row, hit.col);
|
|
230
|
+
const cellSize = Math.min(hit.rect.width, hit.rect.height);
|
|
200
231
|
const bufferSize = cellSize * 0.75;
|
|
201
232
|
const bufferOffset = (cellSize - bufferSize) / 2;
|
|
202
|
-
const
|
|
233
|
+
const x = coords.x - hit.rect.left;
|
|
234
|
+
const y = coords.y - hit.rect.top;
|
|
235
|
+
const inHitZone = x >= bufferOffset && x <= bufferOffset + bufferSize && y >= bufferOffset && y <= bufferOffset + bufferSize;
|
|
203
236
|
if (!inHitZone) return;
|
|
204
237
|
if (!dragCells.has(cellInfo.hand)) {
|
|
205
238
|
dragCells.add(cellInfo.hand);
|
|
@@ -404,7 +437,7 @@
|
|
|
404
437
|
</script>
|
|
405
438
|
|
|
406
439
|
<div class="poker-grid-container" role="region" aria-label="Poker hand grid container">
|
|
407
|
-
<div class="poker-grid" bind:this={gridElement} onmousemove={handleMouseMove} role="application" aria-label="Poker hand selection grid" tabindex={interactive ? 0 : -1} onmouseup={handleMouseUp}>
|
|
440
|
+
<div class="poker-grid" class:subgrid bind:this={gridElement} onmousemove={handleMouseMove} role="application" aria-label="Poker hand selection grid" tabindex={interactive ? 0 : -1} onmouseup={handleMouseUp}>
|
|
408
441
|
{#each Array(13) as _, rowIndex}
|
|
409
442
|
{#each Array(13) as __, colIndex}
|
|
410
443
|
{@const cellInfo = getCellInfo(rowIndex, colIndex)}
|
|
@@ -412,7 +445,9 @@
|
|
|
412
445
|
<button
|
|
413
446
|
type="button"
|
|
414
447
|
class="grid-cell {isDragging ? 'dragging' : ''} {interactive ? '' : showTips ? 'non-interactive show-tips' : 'non-interactive'}"
|
|
415
|
-
style="background: {gradients[cellInfo.hand]} !important"
|
|
448
|
+
style="background: {gradients[cellInfo.hand]} !important; grid-row: {gridLine(rowIndex, subgrid)}; grid-column: {gridLine(colIndex, subgrid)}; {blockFrame(rowIndex, colIndex, subgrid)}"
|
|
449
|
+
data-row={rowIndex}
|
|
450
|
+
data-col={colIndex}
|
|
416
451
|
onclick={(e) => handleClick(e, cellInfo)}
|
|
417
452
|
ondblclick={(e) => handleDoubleClick(e, cellInfo)}
|
|
418
453
|
onmousedown={(e) => handleMouseDown(e, cellInfo)}
|
|
@@ -425,7 +460,9 @@
|
|
|
425
460
|
{:else}
|
|
426
461
|
<div
|
|
427
462
|
class="grid-cell non-interactive"
|
|
428
|
-
style="background: {gradients[cellInfo.hand]} !important"
|
|
463
|
+
style="background: {gradients[cellInfo.hand]} !important; grid-row: {gridLine(rowIndex, subgrid)}; grid-column: {gridLine(colIndex, subgrid)}; {blockFrame(rowIndex, colIndex, subgrid)}"
|
|
464
|
+
data-row={rowIndex}
|
|
465
|
+
data-col={colIndex}
|
|
429
466
|
role="presentation"
|
|
430
467
|
title={cellInfo.display}
|
|
431
468
|
>
|
|
@@ -463,6 +500,12 @@
|
|
|
463
500
|
gap: 0;
|
|
464
501
|
border: var(--irongrid-border-width) solid var(--irongrid-color-border);
|
|
465
502
|
}
|
|
503
|
+
/* 3x3 blocks: spacer tracks after T and 6 (A-T | 9-6 | 5-2) */
|
|
504
|
+
.poker-grid.subgrid {
|
|
505
|
+
grid-template-columns: repeat(5, 1fr) var(--irongrid-subgrid-gap, 6px) repeat(4, 1fr) var(--irongrid-subgrid-gap, 6px) repeat(4, 1fr);
|
|
506
|
+
grid-template-rows: repeat(5, auto) var(--irongrid-subgrid-gap, 6px) repeat(4, auto) var(--irongrid-subgrid-gap, 6px) repeat(4, auto);
|
|
507
|
+
border: none;
|
|
508
|
+
}
|
|
466
509
|
.grid-cell {
|
|
467
510
|
aspect-ratio: 1;
|
|
468
511
|
display: flex;
|
|
@@ -8,6 +8,7 @@ type Grid = SvelteComponent<{
|
|
|
8
8
|
interactive?: boolean;
|
|
9
9
|
showTips?: boolean;
|
|
10
10
|
enableDetailsPanel?: boolean;
|
|
11
|
+
subgrid?: boolean;
|
|
11
12
|
}, {
|
|
12
13
|
cellDetails: CustomEvent<any>;
|
|
13
14
|
cellClick: CustomEvent<any>;
|
|
@@ -26,6 +27,7 @@ declare const Grid: $$__sveltets_2_IsomorphicComponent<{
|
|
|
26
27
|
interactive?: boolean;
|
|
27
28
|
showTips?: boolean;
|
|
28
29
|
enableDetailsPanel?: boolean;
|
|
30
|
+
subgrid?: boolean;
|
|
29
31
|
}, {
|
|
30
32
|
cellDetails: CustomEvent<any>;
|
|
31
33
|
cellClick: CustomEvent<any>;
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
export let showSummary = true;
|
|
26
26
|
export let showTips = false;
|
|
27
27
|
export let enableDetailsPanel = false;
|
|
28
|
+
/** Split the grid into 3x3 blocks (A-T, 9-6, 5-2) with space between them. */
|
|
29
|
+
export let subgrid = false;
|
|
28
30
|
export let evaluator = false;
|
|
29
31
|
export let defaultColor = 'hsl(0, 0%, 100%)';
|
|
30
32
|
export let defaultSelectedColor = 'hsl(0, 0%, 85%)';
|
|
@@ -35,6 +37,11 @@
|
|
|
35
37
|
* Without it, preflop equity is a Monte Carlo estimate.
|
|
36
38
|
*/
|
|
37
39
|
export let preflopTableUrl = null;
|
|
40
|
+
/**
|
|
41
|
+
* With `evaluator`, color the villain hands by hero's equity against each combo: one equal-width band per
|
|
42
|
+
* color (colors[0] = hero's lowest equity). Hands you color yourself keep their color.
|
|
43
|
+
*/
|
|
44
|
+
export let equityColors = true;
|
|
38
45
|
$: if (evaluator && preflopTableUrl) loadPreflopTable(preflopTableUrl);
|
|
39
46
|
/** Predefined ranges for range selection. Array of {name: string, abbreviation: string} */
|
|
40
47
|
export let ranges = [];
|
|
@@ -111,27 +118,49 @@
|
|
|
111
118
|
let lastEquityKey = '';
|
|
112
119
|
|
|
113
120
|
$: equityKey = evaluator
|
|
114
|
-
? JSON.stringify({ heroCards, boardCards, deadCards, hands: visibleHands, colors })
|
|
121
|
+
? JSON.stringify({ heroCards, boardCards, deadCards, hands: visibleHands, colors, equityColors })
|
|
115
122
|
: '';
|
|
116
123
|
$: if (equityKey !== lastEquityKey) {
|
|
117
124
|
lastEquityKey = equityKey;
|
|
118
125
|
scheduleEquity();
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
/** Colors set by equity banding (combo -> color), so they can be updated or removed later. */
|
|
129
|
+
let autoColors = new Map();
|
|
130
|
+
|
|
131
|
+
/** Apply (or, with no equity, remove) the equity band colors. */
|
|
132
|
+
function applyEquityColors(byCombo) {
|
|
133
|
+
if (!equityColors && autoColors.size === 0) return;
|
|
134
|
+
const result = manager.applyEquityBands(internalHands, equityColors ? byCombo || {} : {}, colors, autoColors);
|
|
135
|
+
autoColors = result.autoColors;
|
|
136
|
+
if (result.changed) {
|
|
137
|
+
internalHands = result.hands;
|
|
138
|
+
hands = result.hands;
|
|
139
|
+
dispatch('selectionChange', { hands: result.hands, visibleHands });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
121
143
|
function scheduleEquity() {
|
|
122
144
|
clearTimeout(equityTimer);
|
|
123
145
|
const request = ++equityRequest;
|
|
124
146
|
if (!evaluator || heroCards?.length !== 2) {
|
|
125
147
|
equity = null;
|
|
126
148
|
equityPending = false;
|
|
149
|
+
// Remove the band colors outside this reactive update (as results are applied, after it)
|
|
150
|
+
equityTimer = setTimeout(() => {
|
|
151
|
+
if (request === equityRequest) applyEquityColors(null);
|
|
152
|
+
}, 0);
|
|
127
153
|
return;
|
|
128
154
|
}
|
|
129
155
|
equityPending = true;
|
|
130
156
|
// A short debounce coalesces bursts (typing cards, dragging across the grid)
|
|
131
157
|
equityTimer = setTimeout(async () => {
|
|
132
158
|
try {
|
|
133
|
-
const result = await manager.calculateEquity(visibleHands, heroCards, boardCards, deadCards, colors);
|
|
134
|
-
if (request === equityRequest)
|
|
159
|
+
const result = await manager.calculateEquity(visibleHands, heroCards, boardCards, deadCards, colors, autoColors);
|
|
160
|
+
if (request === equityRequest) {
|
|
161
|
+
equity = result;
|
|
162
|
+
applyEquityColors(result?.byCombo);
|
|
163
|
+
}
|
|
135
164
|
} catch (error) {
|
|
136
165
|
if (request === equityRequest) equity = null;
|
|
137
166
|
} finally {
|
|
@@ -265,6 +294,7 @@
|
|
|
265
294
|
{interactive}
|
|
266
295
|
{showTips}
|
|
267
296
|
{enableDetailsPanel}
|
|
297
|
+
{subgrid}
|
|
268
298
|
{manager}
|
|
269
299
|
on:selectionChange={handleSelectionChange}
|
|
270
300
|
on:cellDetails={handleCellDetails}
|
|
@@ -312,6 +342,7 @@
|
|
|
312
342
|
on:clearAll={() => {
|
|
313
343
|
internalHands = {};
|
|
314
344
|
hands = {};
|
|
345
|
+
autoColors = new Map();
|
|
315
346
|
}}
|
|
316
347
|
/>
|
|
317
348
|
</div>
|
|
@@ -10,6 +10,7 @@ type PokerGridPanel = SvelteComponent<{
|
|
|
10
10
|
interactive?: boolean;
|
|
11
11
|
showTips?: boolean;
|
|
12
12
|
enableDetailsPanel?: boolean;
|
|
13
|
+
subgrid?: boolean;
|
|
13
14
|
evaluator?: boolean;
|
|
14
15
|
visibleHands?: {};
|
|
15
16
|
ranges?: any[];
|
|
@@ -18,13 +19,14 @@ type PokerGridPanel = SvelteComponent<{
|
|
|
18
19
|
showControls?: boolean;
|
|
19
20
|
showSummary?: boolean;
|
|
20
21
|
preflopTableUrl?: any;
|
|
22
|
+
equityColors?: boolean;
|
|
21
23
|
initialHands?: any;
|
|
22
24
|
initialBoard?: any;
|
|
23
25
|
initialDead?: any;
|
|
24
26
|
}, {
|
|
25
27
|
cellClick: CustomEvent<any>;
|
|
26
|
-
rangeSelected: CustomEvent<any>;
|
|
27
28
|
selectionChange: CustomEvent<any>;
|
|
29
|
+
rangeSelected: CustomEvent<any>;
|
|
28
30
|
deadCardsChange: CustomEvent<any>;
|
|
29
31
|
} & {
|
|
30
32
|
[evt: string]: CustomEvent<any>;
|
|
@@ -42,6 +44,7 @@ declare const PokerGridPanel: $$__sveltets_2_IsomorphicComponent<{
|
|
|
42
44
|
interactive?: boolean;
|
|
43
45
|
showTips?: boolean;
|
|
44
46
|
enableDetailsPanel?: boolean;
|
|
47
|
+
subgrid?: boolean;
|
|
45
48
|
evaluator?: boolean;
|
|
46
49
|
visibleHands?: {};
|
|
47
50
|
ranges?: any[];
|
|
@@ -50,13 +53,14 @@ declare const PokerGridPanel: $$__sveltets_2_IsomorphicComponent<{
|
|
|
50
53
|
showControls?: boolean;
|
|
51
54
|
showSummary?: boolean;
|
|
52
55
|
preflopTableUrl?: any;
|
|
56
|
+
equityColors?: boolean;
|
|
53
57
|
initialHands?: any;
|
|
54
58
|
initialBoard?: any;
|
|
55
59
|
initialDead?: any;
|
|
56
60
|
}, {
|
|
57
61
|
cellClick: CustomEvent<any>;
|
|
58
|
-
rangeSelected: CustomEvent<any>;
|
|
59
62
|
selectionChange: CustomEvent<any>;
|
|
63
|
+
rangeSelected: CustomEvent<any>;
|
|
60
64
|
deadCardsChange: CustomEvent<any>;
|
|
61
65
|
} & {
|
|
62
66
|
[evt: string]: CustomEvent<any>;
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
/* Border Variables */
|
|
55
55
|
--irongrid-border-width: 1px;
|
|
56
|
+
--irongrid-subgrid-gap: 6px; /* space between the 3x3 blocks with subgrid */
|
|
56
57
|
--irongrid-border-radius-sm: 0.125rem;
|
|
57
58
|
--irongrid-border-radius-md: 0.25rem;
|
|
58
59
|
--irongrid-border-radius-lg: 0.375rem;
|