poker-irongrid 1.4.0 → 2.0.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 +15 -0
- package/dist/poker-grid/PokerGridManager.d.ts +65 -8
- package/dist/poker-grid/PokerGridManager.js +134 -18
- package/dist/poker-grid/components/Grid.svelte +75 -83
- package/dist/poker-grid/components/HandSelector.svelte +468 -406
- package/dist/poker-grid/components/HandSelector.svelte.d.ts +6 -2
- package/dist/poker-grid/components/PokerGridPanel.svelte +15 -4
- package/dist/poker-grid/theme.css +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -217,6 +217,21 @@ The component includes comprehensive filtering options:
|
|
|
217
217
|
- Visual distinction between colored and uncolored hands
|
|
218
218
|
- Color statistics display
|
|
219
219
|
|
|
220
|
+
### Design (2.0)
|
|
221
|
+
|
|
222
|
+
- **Grid:** small tiles with rounded corners and 2px gaps (`--irongrid-cell-gap`, `--irongrid-cell-radius`),
|
|
223
|
+
two-character labels (AK above the diagonal is suited, below is offsuit). Hands in the range are filled with
|
|
224
|
+
their color (lighter when only some combos are in); hands outside it get a faint suited / offsuit / pair tint
|
|
225
|
+
(`--irongrid-cell-suited-bg`, `--irongrid-cell-offsuit-bg`, `--irongrid-cell-paired-bg`) and grey text.
|
|
226
|
+
Selected colored hands get a ring (`--irongrid-cell-ring-color`). With `subgrid`, the 3x3 blocks are set apart.
|
|
227
|
+
- **Loading a range** (range select) fills a fresh grid with the first color; nothing is selected.
|
|
228
|
+
- **Controls** (`showControls`): color chips showing each color's share of the range (%), combos (#) or hero's
|
|
229
|
+
equity (Eq), and × to clear; the share selected with All / Invert; a **Made × Draw** table (made hand by draw:
|
|
230
|
+
none, straight, flush, combo, backdoor flush) or **Made × Equity** (hero's equity against the hands, in 20%
|
|
231
|
+
bands); rank filters; and a suit matrix with Suited / Offsuit. Draws must use a hole card (poker-extval with
|
|
232
|
+
`holeCards`). Clicking any table, rank or suit cell selects its hands (ctrl/cmd adds, shift removes); a color
|
|
233
|
+
chip then colors the selection.
|
|
234
|
+
|
|
220
235
|
### Equity Evaluation
|
|
221
236
|
|
|
222
237
|
When `evaluator={true}`, hero's equity is calculated live (asynchronously) whenever the hero cards,
|
|
@@ -226,6 +226,52 @@ export class PokerGridManager {
|
|
|
226
226
|
* @returns {Promise<Object>} Matching hands
|
|
227
227
|
*/
|
|
228
228
|
getHandsByDraw(targetDraw: string, hands?: any, boardCards?: string[], boardLength?: number): Promise<any>;
|
|
229
|
+
/** The color of a hand's values (its hsl entry), or null. */
|
|
230
|
+
colorOf(values: any): any;
|
|
231
|
+
/**
|
|
232
|
+
* Combos in the range: those with a color or selected (dead combos are expected to be filtered out already).
|
|
233
|
+
* @param {Object} hands - Hands object (visible hands)
|
|
234
|
+
* @returns {string[]}
|
|
235
|
+
*/
|
|
236
|
+
getRangeCombos(hands?: any): string[];
|
|
237
|
+
/**
|
|
238
|
+
* Select combos with the usual modifiers: plain replaces the selection, ctrl/cmd adds, shift removes.
|
|
239
|
+
* @param {Object} hands - Hands object
|
|
240
|
+
* @param {string[]} combos - Combos to select
|
|
241
|
+
* @param {{ctrl?: boolean, shift?: boolean}} modifiers
|
|
242
|
+
* @returns {Object} Updated hands object
|
|
243
|
+
*/
|
|
244
|
+
selectCombos(hands: any, combos: string[], modifiers?: {
|
|
245
|
+
ctrl?: boolean;
|
|
246
|
+
shift?: boolean;
|
|
247
|
+
}): any;
|
|
248
|
+
/**
|
|
249
|
+
* Invert the selection within the range.
|
|
250
|
+
* @param {Object} hands - Hands object
|
|
251
|
+
* @returns {Object} Updated hands object
|
|
252
|
+
*/
|
|
253
|
+
invertRangeSelection(hands: any): any;
|
|
254
|
+
/**
|
|
255
|
+
* Classify combos on a board for the Made x Draw table: made hand (row) and draw (column), from
|
|
256
|
+
* poker-extval with the hole cards, so draws must use a hole card. Cached per board.
|
|
257
|
+
* Made: highCard, onePair, twoPair, trips, straight, flush, fullHouse, quads, straightFlush.
|
|
258
|
+
* Draw (flop/turn only): combo (flush + straight draw), flush, straight, backdoor (backdoor flush draw,
|
|
259
|
+
* flop only), none.
|
|
260
|
+
* @param {string[]} combos - Combos
|
|
261
|
+
* @param {string[]} boardCards - Board (3-5 cards)
|
|
262
|
+
* @returns {Map<string, {made: string, draw: string}>}
|
|
263
|
+
*/
|
|
264
|
+
classifyCombos(combos: string[], boardCards?: string[]): Map<string, {
|
|
265
|
+
made: string;
|
|
266
|
+
draw: string;
|
|
267
|
+
}>;
|
|
268
|
+
classifyCacheBoard: any;
|
|
269
|
+
classifyCache: Map<any, any>;
|
|
270
|
+
/** Uncached classifyCombos for one combo, or null if the cards clash. */
|
|
271
|
+
classifyCombo(combo: any, boardCards: any): {
|
|
272
|
+
made: string;
|
|
273
|
+
draw: string;
|
|
274
|
+
};
|
|
229
275
|
/**
|
|
230
276
|
* Apply color to hands.
|
|
231
277
|
* @param {Object} hands - Current hands object
|
|
@@ -244,19 +290,18 @@ export class PokerGridManager {
|
|
|
244
290
|
/**
|
|
245
291
|
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
246
292
|
* poker-bitvaljs compareRangeHands call (each villain combo's result against hero's hand).
|
|
247
|
-
* Villain hands are the combos
|
|
248
|
-
*
|
|
293
|
+
* Villain hands are the combos in the range (colored or selected); hands holding a dead, board or hero
|
|
294
|
+
* card are left out.
|
|
249
295
|
* @param {Object} hands - Hands object
|
|
250
296
|
* @param {string[]} heroCards - Hero cards (two)
|
|
251
297
|
* @param {string[]} boardCards - Board cards (0-5)
|
|
252
298
|
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
253
299
|
* @param {string[]} colors - Colors array
|
|
254
|
-
* @param {Map<string, string>|null} autoColors - Colors applyEquityBands set (combo -> color)
|
|
255
300
|
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object, byCombo: Object}|null>}
|
|
256
301
|
* Equities in percent (null when hero meets no villain combo); byCombo is hero's equity against each
|
|
257
302
|
* villain combo. Null without two hero cards.
|
|
258
303
|
*/
|
|
259
|
-
calculateEquity(hands?: any, heroCards?: string[], boardCards?: string[], deadCards?: string[], colors?: string[]
|
|
304
|
+
calculateEquity(hands?: any, heroCards?: string[], boardCards?: string[], deadCards?: string[], colors?: string[]): Promise<{
|
|
260
305
|
hero: number | null;
|
|
261
306
|
villain: number | null;
|
|
262
307
|
combos: number;
|
|
@@ -267,16 +312,17 @@ export class PokerGridManager {
|
|
|
267
312
|
* Color villain combos by hero's equity against them: one equal-width band per color, colors[0] for
|
|
268
313
|
* hero's lowest equity (villain's strongest hands) up to the last color for hero's highest. With 6
|
|
269
314
|
* colors the bands are 0-16.7%, 16.7-33.3%, ... 83.3-100%.
|
|
270
|
-
* Only combos without a color,
|
|
271
|
-
*
|
|
272
|
-
* `byCombo` (
|
|
315
|
+
* Only combos without a color, still holding the color this method gave them, or holding their base
|
|
316
|
+
* color (e.g. from loading a range) are recolored, so hand-applied colors stay. Combos this method colored
|
|
317
|
+
* that are no longer in `byCombo` (dead, or no equity) go back to their base color, or lose the color.
|
|
273
318
|
* @param {Object} hands - Hands object
|
|
274
319
|
* @param {Object} byCombo - Hero's equity (%) against each villain combo, from calculateEquity
|
|
275
320
|
* @param {string[]} colors - Colors, one per band
|
|
276
321
|
* @param {Map<string, string>} autoColors - Colors set by earlier calls (combo -> color)
|
|
322
|
+
* @param {Map<string, string>} baseColors - Colors combos had before banding (combo -> color)
|
|
277
323
|
* @returns {{hands: Object, autoColors: Map<string, string>, changed: boolean}}
|
|
278
324
|
*/
|
|
279
|
-
applyEquityBands(hands: any, byCombo: any, colors: string[], autoColors?: Map<string, string>): {
|
|
325
|
+
applyEquityBands(hands: any, byCombo: any, colors: string[], autoColors?: Map<string, string>, baseColors?: Map<string, string>): {
|
|
280
326
|
hands: any;
|
|
281
327
|
autoColors: Map<string, string>;
|
|
282
328
|
changed: boolean;
|
|
@@ -286,6 +332,17 @@ export class PokerGridManager {
|
|
|
286
332
|
* @returns {Promise<Object>} Color equities object
|
|
287
333
|
*/
|
|
288
334
|
calculateColorEquities(hands?: any, heroCards?: any, boardCards?: any, deadCards?: any, colors?: any): Promise<any>;
|
|
335
|
+
/**
|
|
336
|
+
* Load a predefined range as a fresh grid: every combo gets `color` and nothing is selected.
|
|
337
|
+
* @param {string} rangeName - Range name
|
|
338
|
+
* @param {Array} ranges - Ranges array
|
|
339
|
+
* @param {string} color - Color for the range's combos
|
|
340
|
+
* @returns {{hands: Object, rangeName: string|null}}
|
|
341
|
+
*/
|
|
342
|
+
loadRange(rangeName: string, ranges: any[], color: string): {
|
|
343
|
+
hands: any;
|
|
344
|
+
rangeName: string | null;
|
|
345
|
+
};
|
|
289
346
|
/**
|
|
290
347
|
* Apply predefined range.
|
|
291
348
|
* @param {string} rangeName - Range name
|
|
@@ -617,6 +617,112 @@ export class PokerGridManager {
|
|
|
617
617
|
return this.rangeManagerToMatching(drawOnlyRm, hands);
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
+
// ==================== Range Views (design) ====================
|
|
621
|
+
|
|
622
|
+
/** The color of a hand's values (its hsl entry), or null. */
|
|
623
|
+
colorOf(values) {
|
|
624
|
+
return (values || []).find(item => typeof item === 'string' && item.startsWith('hsl(')) || null;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Combos in the range: those with a color or selected (dead combos are expected to be filtered out already).
|
|
629
|
+
* @param {Object} hands - Hands object (visible hands)
|
|
630
|
+
* @returns {string[]}
|
|
631
|
+
*/
|
|
632
|
+
getRangeCombos(hands = this.hands) {
|
|
633
|
+
return Object.keys(hands || {}).filter(h => isCombo(h) && (this.hasExplicitColor(hands[h]) || this.isSelected(hands[h])));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Select combos with the usual modifiers: plain replaces the selection, ctrl/cmd adds, shift removes.
|
|
638
|
+
* @param {Object} hands - Hands object
|
|
639
|
+
* @param {string[]} combos - Combos to select
|
|
640
|
+
* @param {{ctrl?: boolean, shift?: boolean}} modifiers
|
|
641
|
+
* @returns {Object} Updated hands object
|
|
642
|
+
*/
|
|
643
|
+
selectCombos(hands, combos, modifiers = {}) {
|
|
644
|
+
const newHands = modifiers.ctrl || modifiers.shift ? { ...hands } : this.unselectAll(hands);
|
|
645
|
+
combos.forEach(h => {
|
|
646
|
+
const values = (newHands[h] || []).filter(item => item !== 'selected');
|
|
647
|
+
newHands[h] = modifiers.shift ? values : [...values, 'selected'];
|
|
648
|
+
});
|
|
649
|
+
return newHands;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Invert the selection within the range.
|
|
654
|
+
* @param {Object} hands - Hands object
|
|
655
|
+
* @returns {Object} Updated hands object
|
|
656
|
+
*/
|
|
657
|
+
invertRangeSelection(hands) {
|
|
658
|
+
const newHands = { ...hands };
|
|
659
|
+
this.getRangeCombos(hands).forEach(h => {
|
|
660
|
+
const values = newHands[h] || [];
|
|
661
|
+
newHands[h] = values.includes('selected') ? values.filter(item => item !== 'selected') : [...values, 'selected'];
|
|
662
|
+
});
|
|
663
|
+
return newHands;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Classify combos on a board for the Made x Draw table: made hand (row) and draw (column), from
|
|
668
|
+
* poker-extval with the hole cards, so draws must use a hole card. Cached per board.
|
|
669
|
+
* Made: highCard, onePair, twoPair, trips, straight, flush, fullHouse, quads, straightFlush.
|
|
670
|
+
* Draw (flop/turn only): combo (flush + straight draw), flush, straight, backdoor (backdoor flush draw,
|
|
671
|
+
* flop only), none.
|
|
672
|
+
* @param {string[]} combos - Combos
|
|
673
|
+
* @param {string[]} boardCards - Board (3-5 cards)
|
|
674
|
+
* @returns {Map<string, {made: string, draw: string}>}
|
|
675
|
+
*/
|
|
676
|
+
classifyCombos(combos, boardCards = this.boardCards) {
|
|
677
|
+
const result = new Map();
|
|
678
|
+
if (!boardCards || boardCards.length < 3) return result;
|
|
679
|
+
const key = boardCards.join('');
|
|
680
|
+
if (this.classifyCacheBoard !== key) {
|
|
681
|
+
this.classifyCacheBoard = key;
|
|
682
|
+
this.classifyCache = new Map();
|
|
683
|
+
}
|
|
684
|
+
combos.forEach(h => {
|
|
685
|
+
if (!this.classifyCache.has(h)) {
|
|
686
|
+
this.classifyCache.set(h, this.classifyCombo(h, boardCards));
|
|
687
|
+
}
|
|
688
|
+
const value = this.classifyCache.get(h);
|
|
689
|
+
if (value) result.set(h, value);
|
|
690
|
+
});
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Uncached classifyCombos for one combo, or null if the cards clash. */
|
|
695
|
+
classifyCombo(combo, boardCards) {
|
|
696
|
+
const hole = [combo.substring(0, 2), combo.substring(2, 4)];
|
|
697
|
+
if (hole.some(card => boardCards.includes(card))) return null;
|
|
698
|
+
try {
|
|
699
|
+
const evaluation = Object.values(evaluateHand([...boardCards, ...hole], { holeCards: hole }))[0];
|
|
700
|
+
if (!evaluation) return null;
|
|
701
|
+
let made = 'highCard';
|
|
702
|
+
if (evaluation.isStraightFlush || evaluation.isRoyalFlush) made = 'straightFlush';
|
|
703
|
+
else if (evaluation.isFourOfAKind) made = 'quads';
|
|
704
|
+
else if (evaluation.isFullHouse) made = 'fullHouse';
|
|
705
|
+
else if (evaluation.isFlush) made = 'flush';
|
|
706
|
+
else if (evaluation.isStraight) made = 'straight';
|
|
707
|
+
else if (evaluation.isThreeOfAKind) made = 'trips';
|
|
708
|
+
else if (evaluation.isTwoPair) made = 'twoPair';
|
|
709
|
+
else if (evaluation.isPair) made = 'onePair';
|
|
710
|
+
|
|
711
|
+
let draw = 'none';
|
|
712
|
+
if (boardCards.length < 5) {
|
|
713
|
+
const flushDraw = !!evaluation.isFlushDraw;
|
|
714
|
+
const straightDraw = !!evaluation.isStraightDraw;
|
|
715
|
+
if (flushDraw && straightDraw) draw = 'combo';
|
|
716
|
+
else if (flushDraw) draw = 'flush';
|
|
717
|
+
else if (straightDraw) draw = 'straight';
|
|
718
|
+
else if (boardCards.length === 3 && evaluation.isBackdoorFlushDraw) draw = 'backdoor';
|
|
719
|
+
}
|
|
720
|
+
return { made, draw };
|
|
721
|
+
} catch (error) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
620
726
|
// ==================== Color Methods ====================
|
|
621
727
|
|
|
622
728
|
/**
|
|
@@ -654,32 +760,26 @@ export class PokerGridManager {
|
|
|
654
760
|
/**
|
|
655
761
|
* Hero's equity against the villain hands on the grid, overall and per color, from one
|
|
656
762
|
* poker-bitvaljs compareRangeHands call (each villain combo's result against hero's hand).
|
|
657
|
-
* Villain hands are the combos
|
|
658
|
-
*
|
|
763
|
+
* Villain hands are the combos in the range (colored or selected); hands holding a dead, board or hero
|
|
764
|
+
* card are left out.
|
|
659
765
|
* @param {Object} hands - Hands object
|
|
660
766
|
* @param {string[]} heroCards - Hero cards (two)
|
|
661
767
|
* @param {string[]} boardCards - Board cards (0-5)
|
|
662
768
|
* @param {string[]} deadCards - Dead cards (may include hero and board cards)
|
|
663
769
|
* @param {string[]} colors - Colors array
|
|
664
|
-
* @param {Map<string, string>|null} autoColors - Colors applyEquityBands set (combo -> color)
|
|
665
770
|
* @returns {Promise<{hero: number|null, villain: number|null, combos: number, byColor: Object, byCombo: Object}|null>}
|
|
666
771
|
* Equities in percent (null when hero meets no villain combo); byCombo is hero's equity against each
|
|
667
772
|
* villain combo. Null without two hero cards.
|
|
668
773
|
*/
|
|
669
|
-
async calculateEquity(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors
|
|
774
|
+
async calculateEquity(hands = this.hands, heroCards = this.heroCards, boardCards = this.boardCards, deadCards = this.deadCards, colors = this.colors) {
|
|
670
775
|
if (heroCards?.length !== 2) return null;
|
|
671
776
|
const board = boardCards || [];
|
|
672
777
|
const otherDead = (deadCards || []).filter(card => !heroCards.includes(card) && !board.includes(card));
|
|
673
778
|
const blocked = new Set([...heroCards, ...board, ...otherDead]);
|
|
674
779
|
|
|
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
|
-
};
|
|
680
780
|
const villainHands = Object.keys(hands || {}).filter(h =>
|
|
681
781
|
isCombo(h) &&
|
|
682
|
-
(
|
|
782
|
+
(this.hasExplicitColor(hands[h]) || this.isSelected(hands[h])) &&
|
|
683
783
|
!blocked.has(h.substring(0, 2)) && !blocked.has(h.substring(2, 4))
|
|
684
784
|
);
|
|
685
785
|
|
|
@@ -729,16 +829,17 @@ export class PokerGridManager {
|
|
|
729
829
|
* Color villain combos by hero's equity against them: one equal-width band per color, colors[0] for
|
|
730
830
|
* hero's lowest equity (villain's strongest hands) up to the last color for hero's highest. With 6
|
|
731
831
|
* colors the bands are 0-16.7%, 16.7-33.3%, ... 83.3-100%.
|
|
732
|
-
* Only combos without a color,
|
|
733
|
-
*
|
|
734
|
-
* `byCombo` (
|
|
832
|
+
* Only combos without a color, still holding the color this method gave them, or holding their base
|
|
833
|
+
* color (e.g. from loading a range) are recolored, so hand-applied colors stay. Combos this method colored
|
|
834
|
+
* that are no longer in `byCombo` (dead, or no equity) go back to their base color, or lose the color.
|
|
735
835
|
* @param {Object} hands - Hands object
|
|
736
836
|
* @param {Object} byCombo - Hero's equity (%) against each villain combo, from calculateEquity
|
|
737
837
|
* @param {string[]} colors - Colors, one per band
|
|
738
838
|
* @param {Map<string, string>} autoColors - Colors set by earlier calls (combo -> color)
|
|
839
|
+
* @param {Map<string, string>} baseColors - Colors combos had before banding (combo -> color)
|
|
739
840
|
* @returns {{hands: Object, autoColors: Map<string, string>, changed: boolean}}
|
|
740
841
|
*/
|
|
741
|
-
applyEquityBands(hands, byCombo, colors, autoColors = new Map()) {
|
|
842
|
+
applyEquityBands(hands, byCombo, colors, autoColors = new Map(), baseColors = new Map()) {
|
|
742
843
|
const next = new Map();
|
|
743
844
|
const newHands = { ...hands };
|
|
744
845
|
let changed = false;
|
|
@@ -754,7 +855,7 @@ export class PokerGridManager {
|
|
|
754
855
|
Object.entries(byCombo || {}).forEach(([h, equity]) => {
|
|
755
856
|
if (equity === null || equity === undefined || Number.isNaN(equity)) return;
|
|
756
857
|
const current = colorOf(newHands[h]);
|
|
757
|
-
if (current && autoColors.get(h) !== current) return; // colored by hand
|
|
858
|
+
if (current && autoColors.get(h) !== current && baseColors.get(h) !== current) return; // colored by hand
|
|
758
859
|
const color = colors[Math.min(colors.length - 1, Math.floor(equity / bandWidth))];
|
|
759
860
|
next.set(h, color);
|
|
760
861
|
if (current !== color) setColor(h, color);
|
|
@@ -764,7 +865,7 @@ export class PokerGridManager {
|
|
|
764
865
|
// Combos that lost their equity color (no longer villain hands, or no equity)
|
|
765
866
|
autoColors.forEach((color, h) => {
|
|
766
867
|
if (next.has(h) || !newHands[h]) return;
|
|
767
|
-
if (colorOf(newHands[h]) === color) setColor(h, null);
|
|
868
|
+
if (colorOf(newHands[h]) === color) setColor(h, baseColors.get(h) || null);
|
|
768
869
|
});
|
|
769
870
|
|
|
770
871
|
return { hands: changed ? newHands : hands, autoColors: next, changed };
|
|
@@ -783,6 +884,21 @@ export class PokerGridManager {
|
|
|
783
884
|
|
|
784
885
|
// ==================== Range Methods ====================
|
|
785
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Load a predefined range as a fresh grid: every combo gets `color` and nothing is selected.
|
|
889
|
+
* @param {string} rangeName - Range name
|
|
890
|
+
* @param {Array} ranges - Ranges array
|
|
891
|
+
* @param {string} color - Color for the range's combos
|
|
892
|
+
* @returns {{hands: Object, rangeName: string|null}}
|
|
893
|
+
*/
|
|
894
|
+
loadRange(rangeName, ranges, color) {
|
|
895
|
+
const selectedRange = ranges.find(r => r.name === rangeName);
|
|
896
|
+
if (!selectedRange) return { hands: {}, rangeName: null };
|
|
897
|
+
const hands = {};
|
|
898
|
+
new RangeManager(selectedRange.abbreviation).toArray().forEach(h => { hands[h] = [color]; });
|
|
899
|
+
return { hands, rangeName };
|
|
900
|
+
}
|
|
901
|
+
|
|
786
902
|
/**
|
|
787
903
|
* Apply predefined range.
|
|
788
904
|
* @param {string} rangeName - Range name
|
|
@@ -1087,8 +1203,8 @@ export class PokerGridManager {
|
|
|
1087
1203
|
newSelection[h] = (newSelection[h] || []).filter(item => item !== 'selected');
|
|
1088
1204
|
});
|
|
1089
1205
|
} else {
|
|
1090
|
-
// Normal: Apply color to selected hands
|
|
1091
|
-
newSelection = this.applyColor(newSelection, color, selectedHands);
|
|
1206
|
+
// Normal: Apply color to selected hands, then clear the selection
|
|
1207
|
+
newSelection = this.unselectAll(this.applyColor(newSelection, color, selectedHands));
|
|
1092
1208
|
}
|
|
1093
1209
|
|
|
1094
1210
|
return newSelection;
|
|
@@ -29,20 +29,6 @@
|
|
|
29
29
|
return index + 1 + (split ? (index >= 5 ? 1 : 0) + (index >= 9 ? 1 : 0) : 0);
|
|
30
30
|
}
|
|
31
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
32
|
/** The grid cell under a point (or null), from the cells' own positions, so it works with any spacing. */
|
|
47
33
|
function cellAtPoint(gridElement, x, y) {
|
|
48
34
|
const cells = gridElement.querySelectorAll('[data-row]');
|
|
@@ -82,25 +68,52 @@
|
|
|
82
68
|
return manager ? manager.getCellInfo(rowIndex, colIndex) : { hand: '', display: '', type: '' };
|
|
83
69
|
}
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
/**
|
|
72
|
+
* How a cell looks: in range (combos colored or selected), the range color, lighter when only some of the
|
|
73
|
+
* hand's live combos are in the range; out of range, a faint suited/offsuit/pair tint. Selected colored
|
|
74
|
+
* combos get a ring.
|
|
75
|
+
*/
|
|
76
|
+
/** Out-of-range tints (theme variables, with defaults). */
|
|
77
|
+
const TINTS = {
|
|
78
|
+
suited: 'var(--irongrid-cell-suited-bg, #eef0f9)',
|
|
79
|
+
offsuit: 'var(--irongrid-cell-offsuit-bg, #f6f3ee)',
|
|
80
|
+
paired: 'var(--irongrid-cell-paired-bg, #f1f2f4)'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function cellLook(hand, type, handsMap, dead) {
|
|
84
|
+
const combos = getCombinations(hand);
|
|
85
|
+
const live = combos.filter(c => !dead.includes(c.substring(0, 2)) && !dead.includes(c.substring(2, 4)));
|
|
86
|
+
const counts = new Map();
|
|
87
|
+
let inRange = 0;
|
|
88
|
+
let selected = 0;
|
|
89
|
+
let coloredSelected = 0;
|
|
90
|
+
live.forEach(c => {
|
|
91
|
+
const values = handsMap[c] || [];
|
|
92
|
+
const color = manager ? manager.colorOf(values) : null;
|
|
93
|
+
const isSelected = values.includes('selected');
|
|
94
|
+
if (!color && !isSelected) return;
|
|
95
|
+
inRange++;
|
|
96
|
+
if (isSelected) selected++;
|
|
97
|
+
if (color && isSelected) coloredSelected++;
|
|
98
|
+
const key = color || defaultSelectedColor;
|
|
99
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
100
|
+
});
|
|
101
|
+
if (live.length === 0) return { background: TINTS[type], inRange: false, dead: true, ring: '' };
|
|
102
|
+
if (inRange === 0) return { background: TINTS[type], inRange: false, dead: false, ring: '' };
|
|
103
|
+
let dominant = null;
|
|
104
|
+
counts.forEach((n, color) => { if (!dominant || n > counts.get(dominant)) dominant = color; });
|
|
105
|
+
const strength = Math.round(45 + 55 * (inRange / live.length));
|
|
106
|
+
const ring = coloredSelected === 0 ? '' : selected === inRange ? 'full' : 'partial';
|
|
107
|
+
return { background: `color-mix(in srgb, ${dominant} ${strength}%, white)`, inRange: true, dead: false, ring };
|
|
87
108
|
}
|
|
88
|
-
|
|
89
|
-
$:
|
|
90
|
-
$: gradients = (() => {
|
|
109
|
+
|
|
110
|
+
$: looks = (() => {
|
|
91
111
|
const result = {};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
void totalKeys;
|
|
96
|
-
void selected;
|
|
97
|
-
} catch (e) {
|
|
98
|
-
// Ignore gradient recompute errors
|
|
99
|
-
}
|
|
100
|
-
ranks.forEach((rowRank, rowIndex) => {
|
|
101
|
-
ranks.forEach((colRank, colIndex) => {
|
|
112
|
+
const dead = deadCards || [];
|
|
113
|
+
ranks.forEach((_, rowIndex) => {
|
|
114
|
+
ranks.forEach((__, colIndex) => {
|
|
102
115
|
const cellInfo = getCellInfo(rowIndex, colIndex);
|
|
103
|
-
result[cellInfo.hand] =
|
|
116
|
+
result[cellInfo.hand] = cellLook(cellInfo.hand, cellInfo.type, hands || {}, dead);
|
|
104
117
|
});
|
|
105
118
|
});
|
|
106
119
|
return result;
|
|
@@ -445,7 +458,11 @@
|
|
|
445
458
|
<button
|
|
446
459
|
type="button"
|
|
447
460
|
class="grid-cell {isDragging ? 'dragging' : ''} {interactive ? '' : showTips ? 'non-interactive show-tips' : 'non-interactive'}"
|
|
448
|
-
|
|
461
|
+
class:in-range={looks[cellInfo.hand].inRange}
|
|
462
|
+
class:dead={looks[cellInfo.hand].dead}
|
|
463
|
+
class:ring-full={looks[cellInfo.hand].ring === 'full'}
|
|
464
|
+
class:ring-partial={looks[cellInfo.hand].ring === 'partial'}
|
|
465
|
+
style="background: {looks[cellInfo.hand].background} !important; grid-row: {gridLine(rowIndex, subgrid)}; grid-column: {gridLine(colIndex, subgrid)};"
|
|
449
466
|
data-row={rowIndex}
|
|
450
467
|
data-col={colIndex}
|
|
451
468
|
onclick={(e) => handleClick(e, cellInfo)}
|
|
@@ -455,18 +472,20 @@
|
|
|
455
472
|
onkeydown={(e) => handleKeyDown(e, cellInfo)}
|
|
456
473
|
title={cellInfo.display}
|
|
457
474
|
>
|
|
458
|
-
<span class="cell-text">{cellInfo.
|
|
475
|
+
<span class="cell-text">{cellInfo.hand.substring(0, 2)}</span>
|
|
459
476
|
</button>
|
|
460
477
|
{:else}
|
|
461
478
|
<div
|
|
462
479
|
class="grid-cell non-interactive"
|
|
463
|
-
|
|
480
|
+
class:in-range={looks[cellInfo.hand].inRange}
|
|
481
|
+
class:dead={looks[cellInfo.hand].dead}
|
|
482
|
+
style="background: {looks[cellInfo.hand].background} !important; grid-row: {gridLine(rowIndex, subgrid)}; grid-column: {gridLine(colIndex, subgrid)};"
|
|
464
483
|
data-row={rowIndex}
|
|
465
484
|
data-col={colIndex}
|
|
466
485
|
role="presentation"
|
|
467
486
|
title={cellInfo.display}
|
|
468
487
|
>
|
|
469
|
-
<span class="cell-text">{cellInfo.
|
|
488
|
+
<span class="cell-text">{cellInfo.hand.substring(0, 2)}</span>
|
|
470
489
|
</div>
|
|
471
490
|
{/if}
|
|
472
491
|
{/each}
|
|
@@ -496,33 +515,29 @@
|
|
|
496
515
|
.poker-grid {
|
|
497
516
|
display: grid;
|
|
498
517
|
grid-template-columns: repeat(13, 1fr);
|
|
499
|
-
grid-template-rows: repeat(13,
|
|
500
|
-
gap:
|
|
501
|
-
border: var(--irongrid-border-width) solid var(--irongrid-color-border);
|
|
518
|
+
grid-template-rows: repeat(13, auto);
|
|
519
|
+
gap: var(--irongrid-cell-gap, 2px);
|
|
502
520
|
}
|
|
503
521
|
/* 3x3 blocks: spacer tracks after T and 6 (A-T | 9-6 | 5-2) */
|
|
504
522
|
.poker-grid.subgrid {
|
|
505
|
-
grid-template-columns: repeat(5, 1fr) var(--irongrid-subgrid-gap,
|
|
506
|
-
grid-template-rows: repeat(5, auto) var(--irongrid-subgrid-gap,
|
|
507
|
-
border: none;
|
|
523
|
+
grid-template-columns: repeat(5, 1fr) var(--irongrid-subgrid-gap, 4px) repeat(4, 1fr) var(--irongrid-subgrid-gap, 4px) repeat(4, 1fr);
|
|
524
|
+
grid-template-rows: repeat(5, auto) var(--irongrid-subgrid-gap, 4px) repeat(4, auto) var(--irongrid-subgrid-gap, 4px) repeat(4, auto);
|
|
508
525
|
}
|
|
509
526
|
.grid-cell {
|
|
510
527
|
aspect-ratio: 1;
|
|
528
|
+
min-width: 0;
|
|
511
529
|
display: flex;
|
|
512
530
|
align-items: center;
|
|
513
531
|
justify-content: center;
|
|
514
532
|
cursor: pointer;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
border-right: var(--irongrid-border-width) solid var(--irongrid-color-border);
|
|
518
|
-
border-bottom: var(--irongrid-border-width) solid var(--irongrid-color-border);
|
|
533
|
+
border-radius: var(--irongrid-cell-radius, 3px);
|
|
534
|
+
transition: background-color var(--irongrid-transition-fast), box-shadow var(--irongrid-transition-fast);
|
|
519
535
|
user-select: none;
|
|
520
536
|
-webkit-user-select: none;
|
|
521
537
|
-moz-user-select: none;
|
|
522
538
|
-ms-user-select: none;
|
|
523
539
|
}
|
|
524
540
|
button.grid-cell {
|
|
525
|
-
background: none;
|
|
526
541
|
border: none;
|
|
527
542
|
padding: 0;
|
|
528
543
|
margin: 0;
|
|
@@ -530,11 +545,14 @@
|
|
|
530
545
|
color: inherit;
|
|
531
546
|
text-align: inherit;
|
|
532
547
|
}
|
|
533
|
-
.grid-cell
|
|
534
|
-
|
|
548
|
+
.grid-cell.ring-full {
|
|
549
|
+
box-shadow: inset 0 0 0 2px var(--irongrid-cell-ring-color, #1f2937);
|
|
535
550
|
}
|
|
536
|
-
.grid-cell
|
|
537
|
-
|
|
551
|
+
.grid-cell.ring-partial {
|
|
552
|
+
box-shadow: inset 0 0 0 1px var(--irongrid-cell-ring-color, #1f2937);
|
|
553
|
+
}
|
|
554
|
+
.grid-cell.dead {
|
|
555
|
+
opacity: 0.45;
|
|
538
556
|
}
|
|
539
557
|
.grid-cell.dragging {
|
|
540
558
|
cursor: crosshair;
|
|
@@ -548,48 +566,22 @@
|
|
|
548
566
|
pointer-events: auto;
|
|
549
567
|
}
|
|
550
568
|
.cell-text {
|
|
551
|
-
font-size: var(--irongrid-font-size
|
|
552
|
-
font-weight:
|
|
553
|
-
|
|
569
|
+
font-size: var(--irongrid-cell-font-size, 0.8125rem);
|
|
570
|
+
font-weight: 500;
|
|
571
|
+
letter-spacing: 0.01em;
|
|
572
|
+
color: var(--irongrid-cell-muted-text, #a3a8b3);
|
|
554
573
|
pointer-events: none;
|
|
555
574
|
text-rendering: optimizeLegibility;
|
|
556
575
|
-webkit-font-smoothing: antialiased;
|
|
557
576
|
-moz-osx-font-smoothing: grayscale;
|
|
558
577
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
.poker-grid {
|
|
564
|
-
gap: 0;
|
|
565
|
-
}
|
|
566
|
-
.grid-cell {
|
|
567
|
-
min-height: var(--irongrid-grid-min-cell-height-tablet);
|
|
568
|
-
}
|
|
569
|
-
.cell-text {
|
|
570
|
-
font-size: var(--irongrid-font-size-sm);
|
|
571
|
-
}
|
|
578
|
+
.grid-cell.in-range .cell-text {
|
|
579
|
+
font-weight: 700;
|
|
580
|
+
color: var(--irongrid-color-text-primary);
|
|
572
581
|
}
|
|
573
582
|
@media (max-width: 480px) {
|
|
574
|
-
.poker-grid-container {
|
|
575
|
-
padding: var(--irongrid-grid-container-padding);
|
|
576
|
-
}
|
|
577
|
-
.grid-cell {
|
|
578
|
-
min-height: var(--irongrid-grid-min-cell-height-mobile);
|
|
579
|
-
}
|
|
580
|
-
.cell-text {
|
|
581
|
-
font-size: var(--irongrid-font-size-xxs);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
@media (max-width: 375px) {
|
|
585
|
-
.poker-grid-container {
|
|
586
|
-
padding: var(--irongrid-grid-container-padding-small);
|
|
587
|
-
}
|
|
588
|
-
.grid-cell {
|
|
589
|
-
min-height: var(--irongrid-grid-min-cell-height-small);
|
|
590
|
-
}
|
|
591
583
|
.cell-text {
|
|
592
|
-
font-size: var(--irongrid-font-size-
|
|
584
|
+
font-size: var(--irongrid-cell-font-size-mobile, 0.6875rem);
|
|
593
585
|
}
|
|
594
586
|
}
|
|
595
587
|
|