poker-extval 1.1.0 → 3.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.
Files changed (3) hide show
  1. package/README.md +95 -15
  2. package/evaluator.js +549 -294
  3. package/package.json +1 -1
package/evaluator.js CHANGED
@@ -166,36 +166,36 @@ class HandEvaluator {
166
166
  this.evaluation = {};
167
167
 
168
168
  // Call all evaluation methods - they will populate this.evaluation
169
- this.isFourOfAKind();
170
- this.isFullHouse();
171
- this.isThreeOfAKind();
172
- this.isTwoPair();
173
- this.isPair();
174
- this.isFlush();
175
- this.isTopPair();
176
- this.isMiddlePair();
177
- this.isBottomPair();
178
- this.isTopAndMiddlePair();
179
- this.isTopAndBottomPair();
180
- this.isMiddleAndBottomPair();
181
- this.isTopThreeOfAKind();
182
- this.isMiddleThreeOfAKind();
183
- this.isBottomThreeOfAKind();
184
- this.isFlushDraw();
185
- this.isBackdoorFlushDraw();
186
- this.isStraight();
187
- this.isRoyalFlush();
188
- this.isStraightFlush();
189
- this.isOpenEndedStraightDraw();
190
- this.isInsideStraightDraw();
191
- this.isStraightDraw();
192
- this.isHighCard();
169
+ this.#isFourOfAKind();
170
+ this.#isFullHouse();
171
+ this.#isThreeOfAKind();
172
+ this.#isTwoPair();
173
+ this.#isPair();
174
+ this.#isFlush();
175
+ this.#isTopPair();
176
+ this.#isMiddlePair();
177
+ this.#isBottomPair();
178
+ this.#isTopAndMiddlePair();
179
+ this.#isTopAndBottomPair();
180
+ this.#isMiddleAndBottomPair();
181
+ this.#isTopThreeOfAKind();
182
+ this.#isMiddleThreeOfAKind();
183
+ this.#isBottomThreeOfAKind();
184
+ this.#isFlushDraw();
185
+ this.#isBackdoorFlushDraw();
186
+ this.#isStraight();
187
+ this.#isRoyalFlush();
188
+ this.#isStraightFlush();
189
+ this.#isOpenEndedStraightDraw();
190
+ this.#isInsideStraightDraw();
191
+ this.#isStraightDraw();
192
+ this.#isHighCard();
193
193
 
194
194
  // Add keyCards and kickerCards before restoring original evaluation
195
195
  // (these methods need access to this.evaluation)
196
196
  const evaluation = this.evaluation;
197
- evaluation.keyCards = this.#getKeyCards();
198
- evaluation.kickerCards = this.#getKickerCards();
197
+ evaluation.keyCards = this.#buildKeyCardsObject();
198
+ evaluation.kickerCards = this.#buildKickerCardsObject(evaluation.keyCards);
199
199
 
200
200
  // Restore original evaluation
201
201
  this.evaluation = originalEvaluation;
@@ -281,18 +281,12 @@ class HandEvaluator {
281
281
  * Sorts cards from highest to lowest rank using standard poker ranking (Ace high).
282
282
  * Modifies the array in place. If no cards parameter is provided, sorts this.cards.
283
283
  *
284
+ * @private
284
285
  * @param {string[]} [cards=null] - Optional array of card strings to sort.
285
286
  * If null or undefined, sorts this.cards instead.
286
287
  * @returns {void}
287
- * @example
288
- * const evaluator = new HandEvaluator(['2s', 'As', 'Ks', '5s', 'Ts']);
289
- * evaluator.sort(); // Modifies evaluator.cards to ['As', 'Ks', 'Ts', '5s', '2s']
290
- *
291
- * @example
292
- * const cards = ['2s', 'As', 'Ks'];
293
- * evaluator.sort(cards); // Modifies cards array to ['As', 'Ks', '2s']
294
288
  */
295
- sort(cards = null) {
289
+ #sort(cards = null) {
296
290
  const cardsToSort = cards || this.cards;
297
291
  cardsToSort.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
298
292
  }
@@ -302,14 +296,12 @@ class HandEvaluator {
302
296
  * Modifies the array in place. Used for evaluating wheel straights (A-2-3-4-5).
303
297
  * If no cards parameter is provided, sorts this.cards.
304
298
  *
299
+ * @private
305
300
  * @param {string[]} [cards=null] - Optional array of card strings to sort.
306
301
  * If null or undefined, sorts this.cards instead.
307
302
  * @returns {void}
308
- * @example
309
- * const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
310
- * evaluator.sortWheel(); // Modifies evaluator.cards to ['5s', '4s', '3s', '2s', 'As']
311
303
  */
312
- sortWheel(cards = null) {
304
+ #sortWheel(cards = null) {
313
305
  const cardsToSort = cards || this.cards;
314
306
  cardsToSort.sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
315
307
  }
@@ -318,18 +310,12 @@ class HandEvaluator {
318
310
  * Calculates the distance (rank difference) between the highest and lowest card.
319
311
  * Uses standard poker ranking (Ace high). Distance of 4 indicates a possible straight.
320
312
  *
313
+ * @private
321
314
  * @param {string[]} [cards=null] - Optional array of card strings to evaluate.
322
315
  * If null or undefined, uses this.cards instead.
323
316
  * @returns {number} Distance between highest and lowest card (0-12)
324
- * @example
325
- * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
326
- * evaluator.getDistance(); // Returns 4 (A=13, T=9, 13-9=4)
327
- *
328
- * @example
329
- * evaluator.getDistance(['As', '2s', '3s', '4s', '5s']);
330
- * // Returns 12 (A=13, 2=1, 13-1=12)
331
317
  */
332
- getDistance(cards = null) {
318
+ #getDistance(cards = null) {
333
319
  if (cards) {
334
320
  // If cards array provided, sort a copy and calculate distance
335
321
  if (cards.length === 0) return 0;
@@ -338,7 +324,7 @@ class HandEvaluator {
338
324
  }
339
325
  // If no cards provided, use instance cards property
340
326
  if (this.cards.length === 0) return 0;
341
- this.sort();
327
+ this.#sort();
342
328
  return this.#getRankValue(this.cards[0]) - this.#getRankValue(this.cards[this.cards.length - 1]);
343
329
  }
344
330
 
@@ -346,14 +332,12 @@ class HandEvaluator {
346
332
  * Calculates the distance (rank difference) between the highest and lowest card using wheel ranking.
347
333
  * Uses wheel ranking (Ace low). Distance of 4 indicates a possible wheel straight (A-2-3-4-5).
348
334
  *
335
+ * @private
349
336
  * @param {string[]} [cards=null] - Optional array of card strings to evaluate.
350
337
  * If null or undefined, uses this.cards instead.
351
338
  * @returns {number} Distance between highest and lowest card using wheel ranking (0-12)
352
- * @example
353
- * const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
354
- * evaluator.getDistanceWheel(); // Returns 4 (5=5, A=1, 5-1=4) - wheel straight
355
339
  */
356
- getDistanceWheel(cards = null) {
340
+ #getDistanceWheel(cards = null) {
357
341
  if (cards) {
358
342
  // If cards array provided, sort a copy using wheel values and calculate distance
359
343
  if (cards.length === 0) return 0;
@@ -362,24 +346,18 @@ class HandEvaluator {
362
346
  }
363
347
  // If no cards provided, use instance cards property
364
348
  if (this.cards.length === 0) return 0;
365
- this.sortWheel();
349
+ this.#sortWheel();
366
350
  return this.#getRankValueWheel(this.cards[0]) - this.#getRankValueWheel(this.cards[this.cards.length - 1]);
367
351
  }
368
352
 
369
353
  /**
370
354
  * Counts the occurrences of each suit in the hand.
371
355
  *
356
+ * @private
372
357
  * @returns {Object<string, number>} Object mapping suit to count:
373
358
  * { s: count, h: count, d: count, c: count }
374
- * @example
375
- * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
376
- * evaluator.countSuits(); // Returns { s: 5 }
377
- *
378
- * @example
379
- * const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', 'Jc', 'Ts']);
380
- * evaluator.countSuits(); // Returns { s: 2, h: 1, d: 1, c: 1 }
381
359
  */
382
- countSuits() {
360
+ #countSuits() {
383
361
  return this.cards.reduce((acc, card) => ({ ...acc, [card.slice(-1)]: (acc[card.slice(-1)] || 0) + 1 }), {});
384
362
  }
385
363
 
@@ -387,17 +365,11 @@ class HandEvaluator {
387
365
  * Counts the occurrences of each rank and returns sorted counts (descending).
388
366
  * Used to identify pairs, three of a kind, four of a kind, etc.
389
367
  *
368
+ * @private
390
369
  * @returns {number[]} Array of rank counts sorted in descending order
391
- * @example
392
- * const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Ks']);
393
- * evaluator.countRanks(); // Returns [3, 2] (three aces, two kings)
394
- *
395
- * @example
396
- * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
397
- * evaluator.countRanks(); // Returns [2, 2, 1] (two aces, two kings, one queen)
398
370
  */
399
- countRanks() {
400
- this.sort();
371
+ #countRanks() {
372
+ this.#sort();
401
373
  const rankCounts = this.cards.reduce((acc, card) => (acc[card.slice(0, -1)] = (acc[card.slice(0, -1)] || 0) + 1, acc), {});
402
374
  return Object.values(rankCounts).sort((a, b) => b - a);
403
375
  }
@@ -412,7 +384,7 @@ class HandEvaluator {
412
384
  * // Returns { A: 3, K: 2 }
413
385
  */
414
386
  #getRankCounts() {
415
- this.sort();
387
+ this.#sort();
416
388
  return this.cards.reduce((acc, card) => (acc[card.slice(0, -1)] = (acc[card.slice(0, -1)] || 0) + 1, acc), {});
417
389
  }
418
390
 
@@ -446,7 +418,7 @@ class HandEvaluator {
446
418
  * // Returns [1, 1, 1, 2] (gap of 2 between J and 9)
447
419
  */
448
420
  #getGaps() {
449
- this.sort();
421
+ this.#sort();
450
422
  const gaps = [];
451
423
  for (let i = 0; i < 4; i++) {
452
424
  gaps.push(this.#getRankValue(this.cards[i]) - this.#getRankValue(this.cards[i + 1]));
@@ -466,7 +438,7 @@ class HandEvaluator {
466
438
  * // Returns [1, 1, 1, 1] (all gaps are 1)
467
439
  */
468
440
  #getGapsWheel() {
469
- this.sortWheel();
441
+ this.#sortWheel();
470
442
  const gaps = [];
471
443
  for (let i = 0; i < 4; i++) {
472
444
  gaps.push(this.#getRankValueWheel(this.cards[i]) - this.#getRankValueWheel(this.cards[i + 1]));
@@ -484,9 +456,9 @@ class HandEvaluator {
484
456
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Ac', 'Ks']);
485
457
  * evaluator.isFourOfAKind(); // Returns true
486
458
  */
487
- isFourOfAKind() {
459
+ #isFourOfAKind() {
488
460
  if (this.evaluation.isFourOfAKind !== undefined) return this.evaluation.isFourOfAKind;
489
- const ranks = this.countRanks();
461
+ const ranks = this.#countRanks();
490
462
  return this.evaluation.isFourOfAKind = ranks[0] === 4 && ranks[1] === 1;
491
463
  }
492
464
 
@@ -498,9 +470,9 @@ class HandEvaluator {
498
470
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Ks']);
499
471
  * evaluator.isFullHouse(); // Returns true
500
472
  */
501
- isFullHouse() {
473
+ #isFullHouse() {
502
474
  if (this.evaluation.isFullHouse !== undefined) return this.evaluation.isFullHouse;
503
- const ranks = this.countRanks();
475
+ const ranks = this.#countRanks();
504
476
  return this.evaluation.isFullHouse = ranks[0] === 3 && ranks[1] === 2;
505
477
  }
506
478
 
@@ -512,9 +484,9 @@ class HandEvaluator {
512
484
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Qs']);
513
485
  * evaluator.isThreeOfAKind(); // Returns true
514
486
  */
515
- isThreeOfAKind() {
487
+ #isThreeOfAKind() {
516
488
  if (this.evaluation.isThreeOfAKind !== undefined) return this.evaluation.isThreeOfAKind;
517
- const ranks = this.countRanks();
489
+ const ranks = this.#countRanks();
518
490
  return this.evaluation.isThreeOfAKind = ranks[0] === 3 && ranks[1] === 1 && ranks[2] === 1;
519
491
  }
520
492
 
@@ -526,9 +498,9 @@ class HandEvaluator {
526
498
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
527
499
  * evaluator.isTwoPair(); // Returns true
528
500
  */
529
- isTwoPair() {
501
+ #isTwoPair() {
530
502
  if (this.evaluation.isTwoPair !== undefined) return this.evaluation.isTwoPair;
531
- const ranks = this.countRanks();
503
+ const ranks = this.#countRanks();
532
504
  return this.evaluation.isTwoPair = ranks[0] === 2 && ranks[1] === 2 && ranks[2] === 1;
533
505
  }
534
506
 
@@ -540,9 +512,9 @@ class HandEvaluator {
540
512
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', 'Js']);
541
513
  * evaluator.isPair(); // Returns true
542
514
  */
543
- isPair() {
515
+ #isPair() {
544
516
  if (this.evaluation.isPair !== undefined) return this.evaluation.isPair;
545
- const ranks = this.countRanks();
517
+ const ranks = this.#countRanks();
546
518
  return this.evaluation.isPair = ranks[0] === 2 && ranks[1] === 1 && ranks[2] === 1 && ranks[3] === 1;
547
519
  }
548
520
 
@@ -557,9 +529,9 @@ class HandEvaluator {
557
529
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', 'Js']);
558
530
  * evaluator.isTopPair(); // Returns true (pair of aces, highest card)
559
531
  */
560
- isTopPair() {
532
+ #isTopPair() {
561
533
  if (this.evaluation.isTopPair !== undefined) return this.evaluation.isTopPair;
562
- if (this.evaluation.isPair === undefined) this.isPair();
534
+ if (this.evaluation.isPair === undefined) this.#isPair();
563
535
  if (!this.evaluation.isPair) return this.evaluation.isTopPair = false;
564
536
  const rankCounts = this.#getRankCounts();
565
537
  return this.evaluation.isTopPair = rankCounts[this.cards[0].slice(0, -1)] === 2;
@@ -574,10 +546,10 @@ class HandEvaluator {
574
546
  * const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', 'Qc', 'Js']);
575
547
  * evaluator.isMiddlePair(); // Returns true (pair of kings, not highest or lowest)
576
548
  */
577
- isMiddlePair() {
549
+ #isMiddlePair() {
578
550
  if (this.evaluation.isMiddlePair !== undefined) return this.evaluation.isMiddlePair;
579
- if (this.evaluation.isPair === undefined) this.isPair();
580
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
551
+ if (this.evaluation.isPair === undefined) this.#isPair();
552
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
581
553
  if (!this.evaluation.isPair || this.evaluation.isTwoPair) return this.evaluation.isMiddlePair = false;
582
554
  const ranks = this.#getRankCountsSorted();
583
555
  return this.evaluation.isMiddlePair = ranks.length === 4 && (ranks[1].count === 2 || ranks[2].count === 2);
@@ -592,9 +564,9 @@ class HandEvaluator {
592
564
  * const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', '2c', '2s']);
593
565
  * evaluator.isBottomPair(); // Returns true (pair of deuces, lowest card)
594
566
  */
595
- isBottomPair() {
567
+ #isBottomPair() {
596
568
  if (this.evaluation.isBottomPair !== undefined) return this.evaluation.isBottomPair;
597
- if (this.evaluation.isPair === undefined) this.isPair();
569
+ if (this.evaluation.isPair === undefined) this.#isPair();
598
570
  if (!this.evaluation.isPair) return this.evaluation.isBottomPair = false;
599
571
  const rankCounts = this.#getRankCounts();
600
572
  return this.evaluation.isBottomPair = rankCounts[this.cards[this.cards.length - 1].slice(0, -1)] === 2;
@@ -611,9 +583,9 @@ class HandEvaluator {
611
583
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
612
584
  * evaluator.isTopAndMiddlePair(); // Returns true (aces and kings)
613
585
  */
614
- isTopAndMiddlePair() {
586
+ #isTopAndMiddlePair() {
615
587
  if (this.evaluation.isTopAndMiddlePair !== undefined) return this.evaluation.isTopAndMiddlePair;
616
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
588
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
617
589
  if (!this.evaluation.isTwoPair) return this.evaluation.isTopAndMiddlePair = false;
618
590
  const ranks = this.#getRankCountsSorted();
619
591
  return this.evaluation.isTopAndMiddlePair = ranks[0].count === 2 && ranks[1].count === 2;
@@ -628,9 +600,9 @@ class HandEvaluator {
628
600
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', '2s', '2h']);
629
601
  * evaluator.isTopAndBottomPair(); // Returns true (aces and deuces)
630
602
  */
631
- isTopAndBottomPair() {
603
+ #isTopAndBottomPair() {
632
604
  if (this.evaluation.isTopAndBottomPair !== undefined) return this.evaluation.isTopAndBottomPair;
633
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
605
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
634
606
  if (!this.evaluation.isTwoPair) return this.evaluation.isTopAndBottomPair = false;
635
607
  const ranks = this.#getRankCountsSorted();
636
608
  return this.evaluation.isTopAndBottomPair = ranks[0].count === 2 && ranks[ranks.length - 1].count === 2;
@@ -645,9 +617,9 @@ class HandEvaluator {
645
617
  * const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', '2c', '2s']);
646
618
  * evaluator.isMiddleAndBottomPair(); // Returns true (kings and deuces)
647
619
  */
648
- isMiddleAndBottomPair() {
620
+ #isMiddleAndBottomPair() {
649
621
  if (this.evaluation.isMiddleAndBottomPair !== undefined) return this.evaluation.isMiddleAndBottomPair;
650
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
622
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
651
623
  if (!this.evaluation.isTwoPair) return this.evaluation.isMiddleAndBottomPair = false;
652
624
  const ranks = this.#getRankCountsSorted();
653
625
  return this.evaluation.isMiddleAndBottomPair = ranks.length === 3 && ranks[1].count === 2 && ranks[2].count === 2;
@@ -664,9 +636,9 @@ class HandEvaluator {
664
636
  * const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Qs']);
665
637
  * evaluator.isTopThreeOfAKind(); // Returns true (three aces)
666
638
  */
667
- isTopThreeOfAKind() {
639
+ #isTopThreeOfAKind() {
668
640
  if (this.evaluation.isTopThreeOfAKind !== undefined) return this.evaluation.isTopThreeOfAKind;
669
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
641
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
670
642
  if (!this.evaluation.isThreeOfAKind) return this.evaluation.isTopThreeOfAKind = false;
671
643
  const rankCounts = this.#getRankCounts();
672
644
  return this.evaluation.isTopThreeOfAKind = rankCounts[this.cards[0].slice(0, -1)] === 3;
@@ -681,9 +653,9 @@ class HandEvaluator {
681
653
  * const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', 'Kc', 'Qs']);
682
654
  * evaluator.isMiddleThreeOfAKind(); // Returns true (three kings)
683
655
  */
684
- isMiddleThreeOfAKind() {
656
+ #isMiddleThreeOfAKind() {
685
657
  if (this.evaluation.isMiddleThreeOfAKind !== undefined) return this.evaluation.isMiddleThreeOfAKind;
686
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
658
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
687
659
  if (!this.evaluation.isThreeOfAKind) return this.evaluation.isMiddleThreeOfAKind = false;
688
660
  const ranks = this.#getRankCountsSorted();
689
661
  return this.evaluation.isMiddleThreeOfAKind = ranks.length === 3 && ranks[1].count === 3;
@@ -698,9 +670,9 @@ class HandEvaluator {
698
670
  * const evaluator = new HandEvaluator(['As', 'Kh', '2d', '2c', '2s']);
699
671
  * evaluator.isBottomThreeOfAKind(); // Returns true (three deuces)
700
672
  */
701
- isBottomThreeOfAKind() {
673
+ #isBottomThreeOfAKind() {
702
674
  if (this.evaluation.isBottomThreeOfAKind !== undefined) return this.evaluation.isBottomThreeOfAKind;
703
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
675
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
704
676
  if (!this.evaluation.isThreeOfAKind) return this.evaluation.isBottomThreeOfAKind = false;
705
677
  const rankCounts = this.#getRankCounts();
706
678
  return this.evaluation.isBottomThreeOfAKind = rankCounts[this.cards[this.cards.length - 1].slice(0, -1)] === 3;
@@ -716,9 +688,9 @@ class HandEvaluator {
716
688
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9s']);
717
689
  * evaluator.isFlush(); // Returns true (all spades)
718
690
  */
719
- isFlush() {
691
+ #isFlush() {
720
692
  if (this.evaluation.isFlush !== undefined) return this.evaluation.isFlush;
721
- const suits = this.countSuits();
693
+ const suits = this.#countSuits();
722
694
  return this.evaluation.isFlush = Object.values(suits).some(count => count === 5);
723
695
  }
724
696
 
@@ -731,11 +703,11 @@ class HandEvaluator {
731
703
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
732
704
  * evaluator.isFlushDraw(); // Returns true (4 spades, 1 heart)
733
705
  */
734
- isFlushDraw() {
706
+ #isFlushDraw() {
735
707
  if (this.evaluation.isFlushDraw !== undefined) return this.evaluation.isFlushDraw;
736
- if (this.evaluation.isFlush === undefined) this.isFlush();
708
+ if (this.evaluation.isFlush === undefined) this.#isFlush();
737
709
  if (this.evaluation.isFlush) return this.evaluation.isFlushDraw = false;
738
- const suits = this.countSuits();
710
+ const suits = this.#countSuits();
739
711
  return this.evaluation.isFlushDraw = Object.values(suits).some(count => count === 4);
740
712
  }
741
713
 
@@ -748,12 +720,12 @@ class HandEvaluator {
748
720
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Jh', '9h']);
749
721
  * evaluator.isBackdoorFlushDraw(); // Returns true (3 spades, 2 hearts)
750
722
  */
751
- isBackdoorFlushDraw() {
723
+ #isBackdoorFlushDraw() {
752
724
  if (this.evaluation.isBackdoorFlushDraw !== undefined) return this.evaluation.isBackdoorFlushDraw;
753
- if (this.evaluation.isFlush === undefined) this.isFlush();
754
- if (this.evaluation.isFlushDraw === undefined) this.isFlushDraw();
725
+ if (this.evaluation.isFlush === undefined) this.#isFlush();
726
+ if (this.evaluation.isFlushDraw === undefined) this.#isFlushDraw();
755
727
  if (this.evaluation.isFlush || this.evaluation.isFlushDraw) return this.evaluation.isBackdoorFlushDraw = false;
756
- const suits = this.countSuits();
728
+ const suits = this.#countSuits();
757
729
  return this.evaluation.isBackdoorFlushDraw = Object.values(suits).some(count => count === 3);
758
730
  }
759
731
 
@@ -773,18 +745,18 @@ class HandEvaluator {
773
745
  * const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
774
746
  * evaluator.isStraight(); // Returns true (wheel: A-2-3-4-5)
775
747
  */
776
- isStraight() {
748
+ #isStraight() {
777
749
  if (this.evaluation.isStraight !== undefined) return this.evaluation.isStraight;
778
- if (this.evaluation.isPair === undefined) this.isPair();
779
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
780
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
781
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
782
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
750
+ if (this.evaluation.isPair === undefined) this.#isPair();
751
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
752
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
753
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
754
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
783
755
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind) return this.evaluation.isStraight = false;
784
- this.sort();
785
- const distance = this.getDistance();
756
+ this.#sort();
757
+ const distance = this.#getDistance();
786
758
  if (distance === 4) return this.evaluation.isStraight = true;
787
- if (this.isStraightWheel()) return this.evaluation.isStraight = true;
759
+ if (this.#isStraightWheel()) return this.evaluation.isStraight = true;
788
760
  return this.evaluation.isStraight = false;
789
761
  }
790
762
 
@@ -797,16 +769,16 @@ class HandEvaluator {
797
769
  * const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
798
770
  * evaluator.isStraightWheel(); // Returns true (wheel: A-2-3-4-5)
799
771
  */
800
- isStraightWheel() {
772
+ #isStraightWheel() {
801
773
  if (this.evaluation.isStraightWheel !== undefined) return this.evaluation.isStraightWheel;
802
- if (this.evaluation.isPair === undefined) this.isPair();
803
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
804
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
805
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
806
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
774
+ if (this.evaluation.isPair === undefined) this.#isPair();
775
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
776
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
777
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
778
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
807
779
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind) return this.evaluation.isStraightWheel = false;
808
- this.sortWheel();
809
- const distance = this.getDistanceWheel();
780
+ this.#sortWheel();
781
+ const distance = this.#getDistanceWheel();
810
782
  return this.evaluation.isStraightWheel = distance === 4;
811
783
  }
812
784
 
@@ -819,10 +791,10 @@ class HandEvaluator {
819
791
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
820
792
  * evaluator.isRoyalFlush(); // Returns true
821
793
  */
822
- isRoyalFlush() {
794
+ #isRoyalFlush() {
823
795
  if (this.evaluation.isRoyalFlush !== undefined) return this.evaluation.isRoyalFlush;
824
- if (this.evaluation.isFlush === undefined) this.isFlush();
825
- if (this.evaluation.isStraight === undefined) this.isStraight();
796
+ if (this.evaluation.isFlush === undefined) this.#isFlush();
797
+ if (this.evaluation.isStraight === undefined) this.#isStraight();
826
798
  if (!this.evaluation.isFlush || !this.evaluation.isStraight) return this.evaluation.isRoyalFlush = false;
827
799
  const ranks = new Set(this.cards.map(c => c.slice(0, -1)));
828
800
  return this.evaluation.isRoyalFlush = ranks.has('A') && ranks.has('K') && ranks.has('Q') && ranks.has('J') && ranks.has('T');
@@ -837,11 +809,11 @@ class HandEvaluator {
837
809
  * const evaluator = new HandEvaluator(['9s', '8s', '7s', '6s', '5s']);
838
810
  * evaluator.isStraightFlush(); // Returns true
839
811
  */
840
- isStraightFlush() {
812
+ #isStraightFlush() {
841
813
  if (this.evaluation.isStraightFlush !== undefined) return this.evaluation.isStraightFlush;
842
- if (this.evaluation.isFlush === undefined) this.isFlush();
843
- if (this.evaluation.isStraight === undefined) this.isStraight();
844
- if (this.evaluation.isRoyalFlush === undefined) this.isRoyalFlush();
814
+ if (this.evaluation.isFlush === undefined) this.#isFlush();
815
+ if (this.evaluation.isStraight === undefined) this.#isStraight();
816
+ if (this.evaluation.isRoyalFlush === undefined) this.#isRoyalFlush();
845
817
  return this.evaluation.isStraightFlush = this.evaluation.isFlush && this.evaluation.isStraight && !this.evaluation.isRoyalFlush;
846
818
  }
847
819
 
@@ -857,24 +829,24 @@ class HandEvaluator {
857
829
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
858
830
  * evaluator.isOpenEndedStraightDraw(); // Returns true (needs T or 9 to complete straight)
859
831
  */
860
- isOpenEndedStraightDraw() {
832
+ #isOpenEndedStraightDraw() {
861
833
  if (this.evaluation.isOpenEndedStraightDraw !== undefined) return this.evaluation.isOpenEndedStraightDraw;
862
- if (this.evaluation.isPair === undefined) this.isPair();
863
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
864
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
865
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
866
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
867
- if (this.evaluation.isStraight === undefined) this.isStraight();
834
+ if (this.evaluation.isPair === undefined) this.#isPair();
835
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
836
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
837
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
838
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
839
+ if (this.evaluation.isStraight === undefined) this.#isStraight();
868
840
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
869
841
  // Check wheel equivalent before returning false
870
- if (this.isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
842
+ if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
871
843
  return this.evaluation.isOpenEndedStraightDraw = false;
872
844
  }
873
- this.sort();
874
- const result = this.getDistance(this.cards.slice(0,4)) === 3 || this.getDistance(this.cards.slice(1,5)) === 3;
845
+ this.#sort();
846
+ const result = this.#getDistance(this.cards.slice(0,4)) === 3 || this.#getDistance(this.cards.slice(1,5)) === 3;
875
847
  if (result) return this.evaluation.isOpenEndedStraightDraw = true;
876
848
  // Check wheel equivalent
877
- if (this.isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
849
+ if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
878
850
  return this.evaluation.isOpenEndedStraightDraw = false;
879
851
  }
880
852
 
@@ -885,17 +857,17 @@ class HandEvaluator {
885
857
  *
886
858
  * @returns {boolean} True if the hand is a wheel-equivalent open-ended straight draw, false otherwise
887
859
  */
888
- isOpenEndedStraightDrawWheel() {
860
+ #isOpenEndedStraightDrawWheel() {
889
861
  if (this.evaluation.isOpenEndedStraightDrawWheel !== undefined) return this.evaluation.isOpenEndedStraightDrawWheel;
890
- if (this.evaluation.isPair === undefined) this.isPair();
891
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
892
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
893
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
894
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
895
- if (this.evaluation.isStraightWheel === undefined) this.isStraightWheel();
862
+ if (this.evaluation.isPair === undefined) this.#isPair();
863
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
864
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
865
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
866
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
867
+ if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
896
868
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isOpenEndedStraightDrawWheel = false;
897
- this.sortWheel();
898
- return this.evaluation.isOpenEndedStraightDrawWheel = this.getDistanceWheel(this.cards.slice(0,4)) === 3 || this.getDistanceWheel(this.cards.slice(1,5)) === 3;
869
+ this.#sortWheel();
870
+ return this.evaluation.isOpenEndedStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 3 || this.#getDistanceWheel(this.cards.slice(1,5)) === 3;
899
871
  }
900
872
 
901
873
  /**
@@ -908,25 +880,25 @@ class HandEvaluator {
908
880
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
909
881
  * evaluator.isInsideStraightDraw(); // Returns true (needs T in the middle)
910
882
  */
911
- isInsideStraightDraw() {
883
+ #isInsideStraightDraw() {
912
884
  if (this.evaluation.isInsideStraightDraw !== undefined) return this.evaluation.isInsideStraightDraw;
913
- if (this.evaluation.isPair === undefined) this.isPair();
914
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
915
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
916
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
917
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
918
- if (this.evaluation.isStraight === undefined) this.isStraight();
885
+ if (this.evaluation.isPair === undefined) this.#isPair();
886
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
887
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
888
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
889
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
890
+ if (this.evaluation.isStraight === undefined) this.#isStraight();
919
891
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
920
892
  // Check wheel equivalent before returning false
921
- if (this.isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
893
+ if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
922
894
  return this.evaluation.isInsideStraightDraw = false;
923
895
  }
924
896
 
925
- this.sort();
926
- const result = this.getDistance(this.cards.slice(0,4)) === 4 || this.getDistance(this.cards.slice(1,5)) === 4;
897
+ this.#sort();
898
+ const result = this.#getDistance(this.cards.slice(0,4)) === 4 || this.#getDistance(this.cards.slice(1,5)) === 4;
927
899
  if (result) return this.evaluation.isInsideStraightDraw = true;
928
900
  // Check wheel equivalent
929
- if (this.isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
901
+ if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
930
902
  return this.evaluation.isInsideStraightDraw = false;
931
903
  }
932
904
 
@@ -937,18 +909,18 @@ class HandEvaluator {
937
909
  *
938
910
  * @returns {boolean} True if the hand is a wheel-equivalent inside straight draw, false otherwise
939
911
  */
940
- isInsideStraightDrawWheel() {
912
+ #isInsideStraightDrawWheel() {
941
913
  if (this.evaluation.isInsideStraightDrawWheel !== undefined) return this.evaluation.isInsideStraightDrawWheel;
942
- if (this.evaluation.isPair === undefined) this.isPair();
943
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
944
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
945
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
946
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
947
- if (this.evaluation.isStraightWheel === undefined) this.isStraightWheel();
914
+ if (this.evaluation.isPair === undefined) this.#isPair();
915
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
916
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
917
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
918
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
919
+ if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
948
920
  if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isInsideStraightDrawWheel = false;
949
921
 
950
- this.sortWheel();
951
- return this.evaluation.isInsideStraightDrawWheel = this.getDistanceWheel(this.cards.slice(0,4)) === 4 || this.getDistanceWheel(this.cards.slice(1,5)) === 4;
922
+ this.#sortWheel();
923
+ return this.evaluation.isInsideStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 4 || this.#getDistanceWheel(this.cards.slice(1,5)) === 4;
952
924
  }
953
925
 
954
926
  /**
@@ -964,10 +936,10 @@ class HandEvaluator {
964
936
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '8h']);
965
937
  * evaluator.isStraightDraw(); // Returns true (inside straight draw)
966
938
  */
967
- isStraightDraw() {
939
+ #isStraightDraw() {
968
940
  if (this.evaluation.isStraightDraw !== undefined) return this.evaluation.isStraightDraw;
969
- if (this.evaluation.isOpenEndedStraightDraw === undefined) this.isOpenEndedStraightDraw();
970
- if (this.evaluation.isInsideStraightDraw === undefined) this.isInsideStraightDraw();
941
+ if (this.evaluation.isOpenEndedStraightDraw === undefined) this.#isOpenEndedStraightDraw();
942
+ if (this.evaluation.isInsideStraightDraw === undefined) this.#isInsideStraightDraw();
971
943
  return this.evaluation.isStraightDraw = this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw;
972
944
  }
973
945
 
@@ -982,14 +954,14 @@ class HandEvaluator {
982
954
  */
983
955
  #getStraightCards() {
984
956
  // Try standard straight first
985
- this.sort();
957
+ this.#sort();
986
958
  const gaps = this.#getGaps();
987
959
  if (gaps.every(g => g === 1)) {
988
960
  return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
989
961
  }
990
962
 
991
963
  // Try wheel straight
992
- this.sortWheel();
964
+ this.#sortWheel();
993
965
  const gapsWheel = this.#getGapsWheel();
994
966
  if (gapsWheel.every(g => g === 1)) {
995
967
  // Convert back to standard sorting for return
@@ -999,6 +971,390 @@ class HandEvaluator {
999
971
  return [];
1000
972
  }
1001
973
 
974
+ // ==================== KEY CARDS EXTRACTION BY HAND TYPE ====================
975
+
976
+ /**
977
+ * Gets key cards for royal flush (all 5 cards).
978
+ *
979
+ * @private
980
+ * @returns {string[]} Array of 5 cards if royal flush, empty array otherwise
981
+ */
982
+ #getKeyCardsForRoyalFlush() {
983
+ if (!this.evaluation.isRoyalFlush) return [];
984
+ return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
985
+ }
986
+
987
+ /**
988
+ * Gets key cards for straight flush (all 5 cards).
989
+ *
990
+ * @private
991
+ * @returns {string[]} Array of 5 cards if straight flush, empty array otherwise
992
+ */
993
+ #getKeyCardsForStraightFlush() {
994
+ if (!this.evaluation.isStraightFlush) return [];
995
+ return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
996
+ }
997
+
998
+ /**
999
+ * Gets key cards for four of a kind (4 cards of same rank).
1000
+ *
1001
+ * @private
1002
+ * @returns {string[]} Array of 4 cards if four of a kind, empty array otherwise
1003
+ */
1004
+ #getKeyCardsForFourOfAKind() {
1005
+ if (!this.evaluation.isFourOfAKind) return [];
1006
+ const rankCounts = this.#getRankCounts();
1007
+ const fourRank = Object.keys(rankCounts).find(r => rankCounts[r] === 4);
1008
+ return this.cards.filter(c => c.slice(0, -1) === fourRank)
1009
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1010
+ }
1011
+
1012
+ /**
1013
+ * Gets key cards for full house (all 5 cards).
1014
+ *
1015
+ * @private
1016
+ * @returns {string[]} Array of 5 cards if full house, empty array otherwise
1017
+ */
1018
+ #getKeyCardsForFullHouse() {
1019
+ if (!this.evaluation.isFullHouse) return [];
1020
+ return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1021
+ }
1022
+
1023
+ /**
1024
+ * Gets key cards for flush (all 5 cards of same suit).
1025
+ *
1026
+ * @private
1027
+ * @returns {string[]} Array of 5 cards if flush, empty array otherwise
1028
+ */
1029
+ #getKeyCardsForFlush() {
1030
+ if (!this.evaluation.isFlush) return [];
1031
+ const suits = this.#countSuits();
1032
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 5);
1033
+ return this.cards.filter(c => c.slice(-1) === flushSuit)
1034
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1035
+ }
1036
+
1037
+ /**
1038
+ * Gets key cards for straight (5 cards forming straight).
1039
+ *
1040
+ * @private
1041
+ * @returns {string[]} Array of 5 cards if straight, empty array otherwise
1042
+ */
1043
+ #getKeyCardsForStraight() {
1044
+ if (!this.evaluation.isStraight) return [];
1045
+ return this.#getStraightCards();
1046
+ }
1047
+
1048
+ /**
1049
+ * Gets key cards for three of a kind (3 cards of same rank).
1050
+ *
1051
+ * @private
1052
+ * @returns {string[]} Array of 3 cards if three of a kind, empty array otherwise
1053
+ */
1054
+ #getKeyCardsForThreeOfAKind() {
1055
+ if (!this.evaluation.isThreeOfAKind) return [];
1056
+ const rankCounts = this.#getRankCounts();
1057
+ const threeRank = Object.keys(rankCounts).find(r => rankCounts[r] === 3);
1058
+ return this.cards.filter(c => c.slice(0, -1) === threeRank)
1059
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1060
+ }
1061
+
1062
+ /**
1063
+ * Gets key cards for two pair (4 cards forming 2 pairs).
1064
+ *
1065
+ * @private
1066
+ * @returns {string[]} Array of 4 cards if two pair, empty array otherwise
1067
+ */
1068
+ #getKeyCardsForTwoPair() {
1069
+ if (!this.evaluation.isTwoPair) return [];
1070
+ const rankCounts = this.#getRankCounts();
1071
+ const pairRanks = Object.keys(rankCounts).filter(r => rankCounts[r] === 2);
1072
+ return this.cards.filter(c => pairRanks.includes(c.slice(0, -1)))
1073
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1074
+ }
1075
+
1076
+ /**
1077
+ * Gets key cards for pair (2 cards of same rank).
1078
+ *
1079
+ * @private
1080
+ * @returns {string[]} Array of 2 cards if pair, empty array otherwise
1081
+ */
1082
+ #getKeyCardsForPair() {
1083
+ if (!this.evaluation.isPair) return [];
1084
+ const rankCounts = this.#getRankCounts();
1085
+ const pairRank = Object.keys(rankCounts).find(r => rankCounts[r] === 2);
1086
+ return this.cards.filter(c => c.slice(0, -1) === pairRank)
1087
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1088
+ }
1089
+
1090
+ /**
1091
+ * Gets key cards for top pair (2 cards if top pair).
1092
+ *
1093
+ * @private
1094
+ * @returns {string[]} Array of 2 cards if top pair, empty array otherwise
1095
+ */
1096
+ #getKeyCardsForTopPair() {
1097
+ if (!this.evaluation.isTopPair) return [];
1098
+ return this.#getKeyCardsForPair();
1099
+ }
1100
+
1101
+ /**
1102
+ * Gets key cards for middle pair (2 cards if middle pair).
1103
+ *
1104
+ * @private
1105
+ * @returns {string[]} Array of 2 cards if middle pair, empty array otherwise
1106
+ */
1107
+ #getKeyCardsForMiddlePair() {
1108
+ if (!this.evaluation.isMiddlePair) return [];
1109
+ return this.#getKeyCardsForPair();
1110
+ }
1111
+
1112
+ /**
1113
+ * Gets key cards for bottom pair (2 cards if bottom pair).
1114
+ *
1115
+ * @private
1116
+ * @returns {string[]} Array of 2 cards if bottom pair, empty array otherwise
1117
+ */
1118
+ #getKeyCardsForBottomPair() {
1119
+ if (!this.evaluation.isBottomPair) return [];
1120
+ return this.#getKeyCardsForPair();
1121
+ }
1122
+
1123
+ /**
1124
+ * Gets key cards for top and middle pair (4 cards if top and middle pair).
1125
+ *
1126
+ * @private
1127
+ * @returns {string[]} Array of 4 cards if top and middle pair, empty array otherwise
1128
+ */
1129
+ #getKeyCardsForTopAndMiddlePair() {
1130
+ if (!this.evaluation.isTopAndMiddlePair) return [];
1131
+ return this.#getKeyCardsForTwoPair();
1132
+ }
1133
+
1134
+ /**
1135
+ * Gets key cards for top and bottom pair (4 cards if top and bottom pair).
1136
+ *
1137
+ * @private
1138
+ * @returns {string[]} Array of 4 cards if top and bottom pair, empty array otherwise
1139
+ */
1140
+ #getKeyCardsForTopAndBottomPair() {
1141
+ if (!this.evaluation.isTopAndBottomPair) return [];
1142
+ return this.#getKeyCardsForTwoPair();
1143
+ }
1144
+
1145
+ /**
1146
+ * Gets key cards for middle and bottom pair (4 cards if middle and bottom pair).
1147
+ *
1148
+ * @private
1149
+ * @returns {string[]} Array of 4 cards if middle and bottom pair, empty array otherwise
1150
+ */
1151
+ #getKeyCardsForMiddleAndBottomPair() {
1152
+ if (!this.evaluation.isMiddleAndBottomPair) return [];
1153
+ return this.#getKeyCardsForTwoPair();
1154
+ }
1155
+
1156
+ /**
1157
+ * Gets key cards for top three of a kind (3 cards if top three).
1158
+ *
1159
+ * @private
1160
+ * @returns {string[]} Array of 3 cards if top three of a kind, empty array otherwise
1161
+ */
1162
+ #getKeyCardsForTopThreeOfAKind() {
1163
+ if (!this.evaluation.isTopThreeOfAKind) return [];
1164
+ return this.#getKeyCardsForThreeOfAKind();
1165
+ }
1166
+
1167
+ /**
1168
+ * Gets key cards for middle three of a kind (3 cards if middle three).
1169
+ *
1170
+ * @private
1171
+ * @returns {string[]} Array of 3 cards if middle three of a kind, empty array otherwise
1172
+ */
1173
+ #getKeyCardsForMiddleThreeOfAKind() {
1174
+ if (!this.evaluation.isMiddleThreeOfAKind) return [];
1175
+ return this.#getKeyCardsForThreeOfAKind();
1176
+ }
1177
+
1178
+ /**
1179
+ * Gets key cards for bottom three of a kind (3 cards if bottom three).
1180
+ *
1181
+ * @private
1182
+ * @returns {string[]} Array of 3 cards if bottom three of a kind, empty array otherwise
1183
+ */
1184
+ #getKeyCardsForBottomThreeOfAKind() {
1185
+ if (!this.evaluation.isBottomThreeOfAKind) return [];
1186
+ return this.#getKeyCardsForThreeOfAKind();
1187
+ }
1188
+
1189
+ /**
1190
+ * Gets key cards for flush draw (4 cards of same suit).
1191
+ *
1192
+ * @private
1193
+ * @returns {string[]} Array of 4 cards if flush draw, empty array otherwise
1194
+ */
1195
+ #getKeyCardsForFlushDraw() {
1196
+ if (!this.evaluation.isFlushDraw) return [];
1197
+ const suits = this.#countSuits();
1198
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
1199
+ return this.cards.filter(c => c.slice(-1) === flushSuit)
1200
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1201
+ }
1202
+
1203
+ /**
1204
+ * Gets key cards for backdoor flush draw (3 cards of same suit).
1205
+ *
1206
+ * @private
1207
+ * @returns {string[]} Array of 3 cards if backdoor flush draw, empty array otherwise
1208
+ */
1209
+ #getKeyCardsForBackdoorFlushDraw() {
1210
+ if (!this.evaluation.isBackdoorFlushDraw) return [];
1211
+ const suits = this.#countSuits();
1212
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
1213
+ return this.cards.filter(c => c.slice(-1) === flushSuit)
1214
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1215
+ }
1216
+
1217
+ /**
1218
+ * Gets key cards for open-ended straight draw (4 cards forming draw).
1219
+ *
1220
+ * @private
1221
+ * @returns {string[]} Array of 4 cards if open-ended straight draw, empty array otherwise
1222
+ */
1223
+ #getKeyCardsForOpenEndedStraightDraw() {
1224
+ if (!this.evaluation.isOpenEndedStraightDraw) return [];
1225
+ return this.#getStraightDrawCards();
1226
+ }
1227
+
1228
+ /**
1229
+ * Gets key cards for inside straight draw (4 cards forming draw).
1230
+ *
1231
+ * @private
1232
+ * @returns {string[]} Array of 4 cards if inside straight draw, empty array otherwise
1233
+ */
1234
+ #getKeyCardsForInsideStraightDraw() {
1235
+ if (!this.evaluation.isInsideStraightDraw) return [];
1236
+ return this.#getStraightDrawCards();
1237
+ }
1238
+
1239
+ /**
1240
+ * Gets key cards for straight draw (4 cards forming any straight draw).
1241
+ *
1242
+ * @private
1243
+ * @returns {string[]} Array of 4 cards if straight draw, empty array otherwise
1244
+ */
1245
+ #getKeyCardsForStraightDraw() {
1246
+ if (!this.evaluation.isStraightDraw) return [];
1247
+ return this.#getStraightDrawCards();
1248
+ }
1249
+
1250
+ /**
1251
+ * Gets key cards for wheel straight (5 cards forming wheel).
1252
+ *
1253
+ * @private
1254
+ * @returns {string[]} Array of 5 cards if wheel straight, empty array otherwise
1255
+ */
1256
+ #getKeyCardsForStraightWheel() {
1257
+ if (!this.evaluation.isStraightWheel) return [];
1258
+ return this.#getStraightCards();
1259
+ }
1260
+
1261
+ /**
1262
+ * Gets key cards for open-ended straight draw wheel (4 cards forming wheel draw).
1263
+ *
1264
+ * @private
1265
+ * @returns {string[]} Array of 4 cards if wheel open-ended draw, empty array otherwise
1266
+ */
1267
+ #getKeyCardsForOpenEndedStraightDrawWheel() {
1268
+ if (!this.evaluation.isOpenEndedStraightDrawWheel) return [];
1269
+ return this.#getStraightDrawCards();
1270
+ }
1271
+
1272
+ /**
1273
+ * Gets key cards for inside straight draw wheel (4 cards forming wheel draw).
1274
+ *
1275
+ * @private
1276
+ * @returns {string[]} Array of 4 cards if wheel inside draw, empty array otherwise
1277
+ */
1278
+ #getKeyCardsForInsideStraightDrawWheel() {
1279
+ if (!this.evaluation.isInsideStraightDrawWheel) return [];
1280
+ return this.#getStraightDrawCards();
1281
+ }
1282
+
1283
+ /**
1284
+ * Gets key cards for high card (empty array).
1285
+ *
1286
+ * @private
1287
+ * @returns {string[]} Always returns empty array
1288
+ */
1289
+ #getKeyCardsForHighCard() {
1290
+ return [];
1291
+ }
1292
+
1293
+ /**
1294
+ * Builds the keyCards object with entries for all hand types.
1295
+ *
1296
+ * @private
1297
+ * @returns {Object} Object with keys for all hand types, values are arrays of key cards
1298
+ */
1299
+ #buildKeyCardsObject() {
1300
+ return {
1301
+ isRoyalFlush: this.#getKeyCardsForRoyalFlush(),
1302
+ isStraightFlush: this.#getKeyCardsForStraightFlush(),
1303
+ isFourOfAKind: this.#getKeyCardsForFourOfAKind(),
1304
+ isFullHouse: this.#getKeyCardsForFullHouse(),
1305
+ isFlush: this.#getKeyCardsForFlush(),
1306
+ isStraight: this.#getKeyCardsForStraight(),
1307
+ isThreeOfAKind: this.#getKeyCardsForThreeOfAKind(),
1308
+ isTwoPair: this.#getKeyCardsForTwoPair(),
1309
+ isPair: this.#getKeyCardsForPair(),
1310
+ isTopPair: this.#getKeyCardsForTopPair(),
1311
+ isMiddlePair: this.#getKeyCardsForMiddlePair(),
1312
+ isBottomPair: this.#getKeyCardsForBottomPair(),
1313
+ isTopAndMiddlePair: this.#getKeyCardsForTopAndMiddlePair(),
1314
+ isTopAndBottomPair: this.#getKeyCardsForTopAndBottomPair(),
1315
+ isMiddleAndBottomPair: this.#getKeyCardsForMiddleAndBottomPair(),
1316
+ isTopThreeOfAKind: this.#getKeyCardsForTopThreeOfAKind(),
1317
+ isMiddleThreeOfAKind: this.#getKeyCardsForMiddleThreeOfAKind(),
1318
+ isBottomThreeOfAKind: this.#getKeyCardsForBottomThreeOfAKind(),
1319
+ isFlushDraw: this.#getKeyCardsForFlushDraw(),
1320
+ isBackdoorFlushDraw: this.#getKeyCardsForBackdoorFlushDraw(),
1321
+ isOpenEndedStraightDraw: this.#getKeyCardsForOpenEndedStraightDraw(),
1322
+ isInsideStraightDraw: this.#getKeyCardsForInsideStraightDraw(),
1323
+ isStraightDraw: this.#getKeyCardsForStraightDraw(),
1324
+ isStraightWheel: this.#getKeyCardsForStraightWheel(),
1325
+ isOpenEndedStraightDrawWheel: this.#getKeyCardsForOpenEndedStraightDrawWheel(),
1326
+ isInsideStraightDrawWheel: this.#getKeyCardsForInsideStraightDrawWheel(),
1327
+ isHighCard: this.#getKeyCardsForHighCard()
1328
+ };
1329
+ }
1330
+
1331
+ /**
1332
+ * Builds the kickerCards object with entries for all hand types.
1333
+ *
1334
+ * @private
1335
+ * @param {Object} keyCardsObj - The keyCards object
1336
+ * @returns {Object} Object with keys for all hand types, values are arrays of kicker cards
1337
+ */
1338
+ #buildKickerCardsObject(keyCardsObj) {
1339
+ const sortedCards = [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1340
+ const result = {};
1341
+
1342
+ for (const [handType, keyCards] of Object.entries(keyCardsObj)) {
1343
+ if (handType === 'isHighCard') {
1344
+ result[handType] = sortedCards;
1345
+ } else if (keyCards.length === 0) {
1346
+ result[handType] = [];
1347
+ } else {
1348
+ const keyCardsSet = new Set(keyCards);
1349
+ result[handType] = this.cards
1350
+ .filter(c => !keyCardsSet.has(c))
1351
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1352
+ }
1353
+ }
1354
+
1355
+ return result;
1356
+ }
1357
+
1002
1358
  /**
1003
1359
  * Helper method to extract the 4 cards that form a straight draw.
1004
1360
  * Handles both standard and wheel straight draws.
@@ -1008,29 +1364,29 @@ class HandEvaluator {
1008
1364
  */
1009
1365
  #getStraightDrawCards() {
1010
1366
  // Try standard straight draw
1011
- this.sort();
1367
+ this.#sort();
1012
1368
  const firstFour = this.cards.slice(0, 4);
1013
1369
  const lastFour = this.cards.slice(1, 5);
1014
1370
 
1015
- const firstFourDist = this.getDistance(firstFour);
1371
+ const firstFourDist = this.#getDistance(firstFour);
1016
1372
  if (firstFourDist === 3 || firstFourDist === 4) {
1017
1373
  return [...firstFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1018
1374
  }
1019
- const lastFourDist = this.getDistance(lastFour);
1375
+ const lastFourDist = this.#getDistance(lastFour);
1020
1376
  if (lastFourDist === 3 || lastFourDist === 4) {
1021
1377
  return [...lastFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1022
1378
  }
1023
1379
 
1024
1380
  // Try wheel straight draw
1025
- this.sortWheel();
1381
+ this.#sortWheel();
1026
1382
  const firstFourWheel = this.cards.slice(0, 4);
1027
1383
  const lastFourWheel = this.cards.slice(1, 5);
1028
1384
 
1029
- const firstFourDistWheel = this.getDistanceWheel(firstFourWheel);
1385
+ const firstFourDistWheel = this.#getDistanceWheel(firstFourWheel);
1030
1386
  if (firstFourDistWheel === 3 || firstFourDistWheel === 4) {
1031
1387
  return [...firstFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
1032
1388
  }
1033
- const lastFourDistWheel = this.getDistanceWheel(lastFourWheel);
1389
+ const lastFourDistWheel = this.#getDistanceWheel(lastFourWheel);
1034
1390
  if (lastFourDistWheel === 3 || lastFourDistWheel === 4) {
1035
1391
  return [...lastFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
1036
1392
  }
@@ -1038,107 +1394,6 @@ class HandEvaluator {
1038
1394
  return [];
1039
1395
  }
1040
1396
 
1041
- /**
1042
- * Determines the primary made hand or draw and extracts the key cards.
1043
- * Assumes cards are already sorted and evaluation is complete.
1044
- *
1045
- * @private
1046
- * @returns {string[]} Array of key cards, sorted from highest to lowest rank
1047
- */
1048
- #getKeyCards() {
1049
- // Ensure cards are sorted once (they likely already are from evaluation)
1050
- const sortedCards = [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1051
-
1052
- // Calculate these once and reuse
1053
- const rankCounts = this.#getRankCounts();
1054
- const suits = this.countSuits();
1055
-
1056
- // Use evaluation results directly - no re-checking
1057
- // Check made hands in priority order
1058
- if (this.evaluation.isRoyalFlush || this.evaluation.isStraightFlush ||
1059
- this.evaluation.isFullHouse) {
1060
- return sortedCards; // All 5 cards are key cards
1061
- }
1062
-
1063
- if (this.evaluation.isStraight) {
1064
- return this.#getStraightCards();
1065
- }
1066
-
1067
- if (this.evaluation.isFourOfAKind) {
1068
- const fourRank = Object.keys(rankCounts).find(r => rankCounts[r] === 4);
1069
- return sortedCards.filter(c => c.slice(0, -1) === fourRank)
1070
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1071
- }
1072
-
1073
- if (this.evaluation.isFlush) {
1074
- const flushSuit = Object.keys(suits).find(s => suits[s] === 5);
1075
- return sortedCards.filter(c => c.slice(-1) === flushSuit)
1076
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1077
- }
1078
-
1079
- if (this.evaluation.isThreeOfAKind) {
1080
- const threeRank = Object.keys(rankCounts).find(r => rankCounts[r] === 3);
1081
- return sortedCards.filter(c => c.slice(0, -1) === threeRank)
1082
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1083
- }
1084
-
1085
- if (this.evaluation.isTwoPair) {
1086
- const pairRanks = Object.keys(rankCounts).filter(r => rankCounts[r] === 2);
1087
- return sortedCards.filter(c => pairRanks.includes(c.slice(0, -1)))
1088
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1089
- }
1090
-
1091
- if (this.evaluation.isPair) {
1092
- const pairRank = Object.keys(rankCounts).find(r => rankCounts[r] === 2);
1093
- return sortedCards.filter(c => c.slice(0, -1) === pairRank)
1094
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1095
- }
1096
-
1097
- // Check draws (only if no made hand)
1098
- if (this.evaluation.isFlushDraw) {
1099
- const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
1100
- return sortedCards.filter(c => c.slice(-1) === flushSuit)
1101
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1102
- }
1103
-
1104
- if (this.evaluation.isBackdoorFlushDraw) {
1105
- const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
1106
- return sortedCards.filter(c => c.slice(-1) === flushSuit)
1107
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1108
- }
1109
-
1110
- if (this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw) {
1111
- const drawCards = this.#getStraightDrawCards();
1112
- // Only return if we actually found draw cards (safety check)
1113
- if (drawCards.length === 4) {
1114
- return drawCards;
1115
- }
1116
- }
1117
-
1118
- // High card or no hand/draw
1119
- return [];
1120
- }
1121
-
1122
- /**
1123
- * Gets the kicker cards (cards not in the made hand or draw).
1124
- *
1125
- * @private
1126
- * @returns {string[]} Array of kicker cards, sorted from highest to lowest rank
1127
- */
1128
- #getKickerCards() {
1129
- const keyCards = this.#getKeyCards();
1130
- if (keyCards.length === 0) {
1131
- // All cards are kickers (high card)
1132
- return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1133
- }
1134
-
1135
- // Use Set for O(1) lookup
1136
- const keyCardsSet = new Set(keyCards);
1137
- return this.cards
1138
- .filter(c => !keyCardsSet.has(c))
1139
- .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1140
- }
1141
-
1142
1397
  // ==================== HIGH CARD ====================
1143
1398
 
1144
1399
  /**
@@ -1150,15 +1405,15 @@ class HandEvaluator {
1150
1405
  * const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', 'Jc', '9s']);
1151
1406
  * evaluator.isHighCard(); // Returns true (no made hand)
1152
1407
  */
1153
- isHighCard() {
1408
+ #isHighCard() {
1154
1409
  if (this.evaluation.isHighCard !== undefined) return this.evaluation.isHighCard;
1155
- if (this.evaluation.isPair === undefined) this.isPair();
1156
- if (this.evaluation.isTwoPair === undefined) this.isTwoPair();
1157
- if (this.evaluation.isThreeOfAKind === undefined) this.isThreeOfAKind();
1158
- if (this.evaluation.isFullHouse === undefined) this.isFullHouse();
1159
- if (this.evaluation.isFourOfAKind === undefined) this.isFourOfAKind();
1160
- if (this.evaluation.isFlush === undefined) this.isFlush();
1161
- if (this.evaluation.isStraight === undefined) this.isStraight();
1410
+ if (this.evaluation.isPair === undefined) this.#isPair();
1411
+ if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
1412
+ if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
1413
+ if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
1414
+ if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
1415
+ if (this.evaluation.isFlush === undefined) this.#isFlush();
1416
+ if (this.evaluation.isStraight === undefined) this.#isStraight();
1162
1417
  return this.evaluation.isHighCard = !this.evaluation.isPair && !this.evaluation.isTwoPair && !this.evaluation.isThreeOfAKind && !this.evaluation.isFullHouse && !this.evaluation.isFourOfAKind && !this.evaluation.isFlush && !this.evaluation.isStraight;
1163
1418
  }
1164
1419
  }