poker-extval 3.2.1 → 3.3.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 +67 -3
  2. package/evaluator.js +210 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,6 +7,7 @@ A comprehensive poker hand evaluation library for Node.js that evaluates poker h
7
7
  - **Comprehensive Hand Evaluation**: Evaluates all standard poker hand types (Royal Flush, Straight Flush, Four of a Kind, Full House, Flush, Straight, Three of a Kind, Two Pair, Pair, High Card)
8
8
  - **Strict Mode (Default)**: Only marks the strongest made hand and stronger draws, preventing weaker hands from being marked when stronger ones exist
9
9
  - **Detailed Categorization**: Categorizes pairs (Top, Middle, Bottom), two pairs, and three of a kind by position
10
+ - **Overcard Categorization**: With hole cards supplied, classifies them against the board (two overcards, overcard and middle card, overcard and undercard, two middle cards, middle and undercard, two undercards)
10
11
  - **Draw Detection**: Identifies flush draws, backdoor flush draws, open-ended straight draws, and inside straight draws
11
12
  - **Key Cards and Kicker Cards**: Provides detailed card arrays showing which cards form each hand type and which are kickers
12
13
  - **Wheel Straight Support**: Handles Ace-low straights (A-2-3-4-5) correctly
@@ -56,6 +57,49 @@ const evaluation = evaluator.evaluation;
56
57
  const nonStrictEvaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs'], false);
57
58
  ```
58
59
 
60
+ ### Overcards
61
+
62
+ The overcard categorizations describe your **hole cards** relative to the **board**, so the evaluator needs to know which two of the cards are yours. Pass them as the third argument:
63
+
64
+ ```javascript
65
+ const { evaluateHand } = require('poker-extval');
66
+
67
+ const board = ['Ks', 'Qh', '8d', '3c'];
68
+ const holeCards = ['Ad', 'Jh'];
69
+
70
+ const result = evaluateHand([...board, ...holeCards], true, holeCards);
71
+ const evaluation = result[Object.keys(result)[0]];
72
+
73
+ evaluation.isOvercard; // true (the ace beats every board card)
74
+ evaluation.isOvercardAndMiddleCard; // true (ace is an overcard, jack sits between 3 and K)
75
+ evaluation.isTwoOvercards; // false
76
+ evaluation.keyCards.isOvercard; // ['Ad']
77
+ ```
78
+
79
+ Each hole card is classified against the board:
80
+
81
+ | Classification | Meaning |
82
+ | --- | --- |
83
+ | Overcard | Rank is higher than every board card |
84
+ | Middle card | Rank sits strictly between the highest and lowest board ranks |
85
+ | Undercard | Rank is lower than every board card |
86
+ | Paired | Rank matches a board card, so it belongs to none of the above |
87
+
88
+ The two classifications combine into one (and only one) of the six categories below. If either hole card pairs the board, none of the six is true — though `isOvercard` can still be true on its own.
89
+
90
+ ```javascript
91
+ // Board: K Q 8 3
92
+ evaluateHand([...board, 'Ad', 'Kc'], true, ['Ad', 'Kc']); // isOvercard only (the king pairs the board)
93
+ evaluateHand([...board, 'Jd', 'Th'], true, ['Jd', 'Th']); // isTwoMiddleCards
94
+ evaluateHand([...board, '4d', '2h'], true, ['4d', '2h']); // isMiddleAndUndercard (the 4 beats the 3)
95
+ evaluateHand([...board, '2d', '2h'], true, ['2d', '2h']); // isTwoUndercards
96
+ ```
97
+
98
+ **Notes:**
99
+ - Hole cards are optional. Without them every overcard property is `false`.
100
+ - Both hole cards must appear in the `cards` array, but they do **not** have to appear in the 5-card combination being evaluated — the classification is a property of the whole hand and is identical across every combination.
101
+ - These categorizations describe the hole cards, not the made hand, so **strict mode does not filter them**. A hand that flops a straight with A-J on a K-Q-T-3 board reports both `isStraight` and `isOvercardAndMiddleCard`.
102
+
59
103
  ### Strict Mode
60
104
 
61
105
  By default, the evaluator uses **strict mode** which only marks the strongest made hand and stronger draws as true. This prevents weaker hands from being marked when a stronger hand exists.
@@ -146,6 +190,19 @@ The `evaluateHand()` function returns an object where:
146
190
  - `isMiddleThreeOfAKind` - Three of a kind is neither highest nor lowest
147
191
  - `isBottomThreeOfAKind` - Three of a kind is the lowest rank
148
192
 
193
+ #### Overcard Categorization (requires `holeCards`; not filtered by strict mode)
194
+ - `isOvercard` - At least one hole card ranks above every board card
195
+ - `isTwoOvercards` - Both hole cards rank above every board card
196
+ - `isOvercardAndMiddleCard` - One overcard, one middle card
197
+ - `isOvercardAndUndercard` - One overcard, one undercard
198
+ - `isTwoMiddleCards` - Both hole cards sit strictly between the highest and lowest board ranks
199
+ - `isMiddleAndUndercard` - One middle card, one undercard
200
+ - `isTwoUndercards` - Both hole cards rank below every board card
201
+
202
+ #### Other Categorization
203
+ - `isUnderPair` - Pocket pair ranked below every other card in the hand (requires 6+ cards)
204
+ - `isComboDraw` - Both a flush draw (not backdoor) and a straight draw
205
+
149
206
  #### Draws
150
207
  - `isFlushDraw` - Four cards of the same suit (one away from flush)
151
208
  - `isBackdoorFlushDraw` - Three cards of the same suit (two away from flush)
@@ -162,6 +219,8 @@ The `evaluateHand()` function returns an object where:
162
219
  - `keyCards` - Object with keys for each hand type/draw, values are arrays of cards (sorted) that constitute that specific hand type or draw. False hand types have empty arrays `[]`.
163
220
  - `kickerCards` - Object with keys for each hand type/draw, values are arrays of cards (sorted) that don't constitute that specific hand type or draw. False hand types have empty arrays `[]`, except `isHighCard` which contains all 5 cards when true.
164
221
 
222
+ **Note**: For the overcard categorizations, `keyCards` holds the qualifying hole cards and `kickerCards` holds the remaining cards of the **full hand** (the board, plus any hole card that is not a key card) rather than the remaining cards of the 5-card combination.
223
+
165
224
  **Note**: Each hand type has its own `keyCards` and `kickerCards` entries. For example, a hand with both a pair and a flush draw will have separate entries in `keyCards.isPair`, `keyCards.isFlushDraw`, `kickerCards.isPair`, and `kickerCards.isFlushDraw`.
166
225
 
167
226
  ### Example Response
@@ -352,6 +411,7 @@ The evaluator validates input and throws descriptive errors for:
352
411
  - Invalid rank (not A, K, Q, J, T, 9-2)
353
412
  - Invalid suit (not s, h, d, c)
354
413
  - Duplicate cards
414
+ - Hole cards that are not an array of exactly 2 distinct cards drawn from `cards`
355
415
 
356
416
  Example:
357
417
  ```javascript
@@ -370,23 +430,25 @@ Run the test suite:
370
430
  npm test
371
431
  ```
372
432
 
373
- The test suite includes 572+ tests covering:
433
+ The test suite includes 700+ tests covering:
374
434
  - All hand types
375
435
  - Edge cases
376
436
  - Invalid input handling
377
437
  - Wheel straights
378
438
  - Draw detection
379
439
  - Categorization logic
440
+ - Overcard categorization and hole card validation
380
441
 
381
442
  ## API Reference
382
443
 
383
- ### `evaluateHand(cards, strict)`
444
+ ### `evaluateHand(cards, strict, holeCards)`
384
445
 
385
446
  Main function to evaluate poker hands.
386
447
 
387
448
  **Parameters:**
388
449
  - `cards` (string[]): Array of 5 or more card strings
389
450
  - `strict` (boolean, optional): If `true`, only the strongest made hand and stronger draws are marked. If `false`, all applicable hand types are marked (default: `true`).
451
+ - `holeCards` (string[] | null, optional): Exactly 2 cards identifying the player's hole cards, both of which must appear in `cards`. Required for the overcard categorizations; without it those properties are always `false` (default: `null`).
390
452
 
391
453
  **Returns:**
392
454
  - Object mapping sorted hand strings to evaluation objects
@@ -399,7 +461,7 @@ Main function to evaluate poker hands.
399
461
  #### Constructor
400
462
 
401
463
  ```javascript
402
- new HandEvaluator(cards, strict)
464
+ new HandEvaluator(cards, strict, holeCards)
403
465
  ```
404
466
 
405
467
  Creates a new HandEvaluator instance.
@@ -407,11 +469,13 @@ Creates a new HandEvaluator instance.
407
469
  **Parameters:**
408
470
  - `cards` (string[]): Array of 5 or more card strings
409
471
  - `strict` (boolean, optional): If `true`, only the strongest made hand and stronger draws are marked. If `false`, all applicable hand types are marked (default: `true`).
472
+ - `holeCards` (string[] | null, optional): Exactly 2 cards identifying the player's hole cards, both of which must appear in `cards`. Required for the overcard categorizations (default: `null`).
410
473
 
411
474
  **Properties:**
412
475
  - `evaluation` - Object mapping sorted hand strings to evaluation objects (same format as `evaluateHand()` return value)
413
476
  - `cards` - Array of input cards
414
477
  - `strict` - Boolean indicating whether strict mode is enabled
478
+ - `holeCards` - Array of the 2 hole cards, or `null` if none were supplied
415
479
 
416
480
  **Note**: All internal methods are private. Use the `evaluation` property to access evaluation results.
417
481
 
package/evaluator.js CHANGED
@@ -19,6 +19,23 @@
19
19
  * const result = evaluator.evaluation;
20
20
  * // result contains 6 key-value pairs, one for each 5-card combination
21
21
  */
22
+ /**
23
+ * Evaluation properties that categorize the player's hole cards relative to the board.
24
+ * These describe the two hole cards rather than a 5-card hand, so their kicker cards are
25
+ * drawn from the full hand (the board) rather than from the 5-card combination.
26
+ *
27
+ * @constant {Set<string>}
28
+ */
29
+ const HOLE_CARD_CATEGORIES = new Set([
30
+ 'isOvercard',
31
+ 'isTwoOvercards',
32
+ 'isOvercardAndMiddleCard',
33
+ 'isOvercardAndUndercard',
34
+ 'isTwoMiddleCards',
35
+ 'isMiddleAndUndercard',
36
+ 'isTwoUndercards'
37
+ ]);
38
+
22
39
  class HandEvaluator {
23
40
  /**
24
41
  * Creates an instance of HandEvaluator.
@@ -28,8 +45,12 @@ class HandEvaluator {
28
45
  * - s is suit: s (spades), h (hearts), d (diamonds), c (clubs)
29
46
  * @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
30
47
  * If false, all applicable hand types are marked (current behavior).
48
+ * @param {string[]|null} [holeCards=null] - Optional array of exactly 2 cards identifying the player's
49
+ * hole cards. Required for the overcard categorizations (isOvercard, isTwoOvercards, etc.); without it
50
+ * those properties are always false. Both cards must be present in `cards`.
31
51
  * @throws {Error} If cards is not an array, has fewer than 5 cards, contains invalid format,
32
52
  * invalid rank, invalid suit, or duplicate cards
53
+ * @throws {Error} If holeCards is provided but is not an array of exactly 2 distinct cards drawn from `cards`
33
54
  * @example
34
55
  * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
35
56
  * @example
@@ -38,15 +59,65 @@ class HandEvaluator {
38
59
  * @example
39
60
  * // Non-strict mode - all applicable hands marked
40
61
  * const nonStrictEvaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs'], false);
62
+ * @example
63
+ * // With hole cards - enables overcard categorization
64
+ * const withHoleCards = new HandEvaluator(['Ks', 'Qh', '8d', '3c', 'Ad', 'Jh'], true, ['Ad', 'Jh']);
41
65
  */
42
- constructor(cards, strict = true, originalCards = null) {
66
+ constructor(cards, strict = true, holeCards = null, originalCards = null) {
43
67
  this.#validateCards(cards);
44
68
  this.cards = cards;
45
69
  this.strict = strict;
46
70
  this.originalCards = originalCards || (cards.length > 5 ? cards : null);
71
+ if (holeCards === null || holeCards === undefined) {
72
+ this.holeCards = null;
73
+ } else {
74
+ this.#validateHoleCards(holeCards, this.originalCards || cards);
75
+ this.holeCards = holeCards;
76
+ }
47
77
  this.evaluation = this.#evaluateAllCombinations();
48
78
  }
49
79
 
80
+ /**
81
+ * Validates the optional hole cards array.
82
+ * Hole cards must be exactly 2 distinct cards that appear in the full hand.
83
+ *
84
+ * @private
85
+ * @param {string[]} holeCards - Array of hole card strings to validate
86
+ * @param {string[]} fullHand - The complete set of cards the hole cards must be drawn from
87
+ * @throws {Error} If holeCards is not an array
88
+ * @throws {Error} If holeCards does not contain exactly 2 cards
89
+ * @throws {Error} If any hole card is not a string
90
+ * @throws {Error} If duplicate hole cards are found
91
+ * @throws {Error} If a hole card is not present in the full hand
92
+ */
93
+ #validateHoleCards(holeCards, fullHand) {
94
+ if (!Array.isArray(holeCards)) {
95
+ throw new Error('Hole cards must be an array');
96
+ }
97
+ if (holeCards.length !== 2) {
98
+ throw new Error(`Expected exactly 2 hole cards, received ${holeCards.length}`);
99
+ }
100
+
101
+ const handSet = new Set(fullHand);
102
+ const seenHoleCards = new Set();
103
+
104
+ for (let i = 0; i < holeCards.length; i++) {
105
+ const card = holeCards[i];
106
+
107
+ if (typeof card !== 'string') {
108
+ throw new Error(`Hole card at index ${i} must be a string`);
109
+ }
110
+ if (seenHoleCards.has(card)) {
111
+ throw new Error(`Duplicate hole card found: "${card}"`);
112
+ }
113
+ seenHoleCards.add(card);
114
+
115
+ if (!handSet.has(card)) {
116
+ throw new Error(`Hole card not found in cards: "${card}"`);
117
+ }
118
+ }
119
+ }
120
+
50
121
  /**
51
122
  * Validates the input cards array for correct format, length, and uniqueness.
52
123
  *
@@ -201,6 +272,7 @@ class HandEvaluator {
201
272
  this.#isStraightDraw();
202
273
  this.#isComboDraw();
203
274
  this.#isUnderPair();
275
+ this.#isOvercards();
204
276
  this.#isHighCard();
205
277
 
206
278
  // Add keyCards and kickerCards before restoring original evaluation
@@ -418,7 +490,7 @@ class HandEvaluator {
418
490
  const result = {};
419
491
  for (const combination of combinations) {
420
492
  const key = this.#formatHandKey(combination);
421
- const tempEvaluator = new HandEvaluator(combination, this.strict, this.originalCards);
493
+ const tempEvaluator = new HandEvaluator(combination, this.strict, this.holeCards, this.originalCards);
422
494
  const evaluationKey = Object.keys(tempEvaluator.evaluation)[0];
423
495
  result[key] = tempEvaluator.evaluation[evaluationKey];
424
496
  }
@@ -1460,6 +1532,32 @@ class HandEvaluator {
1460
1532
  return this.#getKeyCardsForPair();
1461
1533
  }
1462
1534
 
1535
+ /**
1536
+ * Gets key cards for the general overcard property (the hole cards that beat the whole board).
1537
+ *
1538
+ * @private
1539
+ * @returns {string[]} Array of 1 or 2 overcards if isOvercard, empty array otherwise
1540
+ */
1541
+ #getKeyCardsForOvercard() {
1542
+ if (!this.evaluation.isOvercard) return [];
1543
+ const classifications = this.#classifyHoleCards();
1544
+ return this.holeCards
1545
+ .filter((card, i) => classifications[i] === 'over')
1546
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1547
+ }
1548
+
1549
+ /**
1550
+ * Gets key cards for a two-card hole card categorization (both hole cards, sorted high to low).
1551
+ *
1552
+ * @private
1553
+ * @param {string} category - The evaluation property name (e.g. 'isTwoOvercards')
1554
+ * @returns {string[]} Array of both hole cards if the category is true, empty array otherwise
1555
+ */
1556
+ #getKeyCardsForHoleCardCategory(category) {
1557
+ if (!this.evaluation[category]) return [];
1558
+ return [...this.holeCards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1559
+ }
1560
+
1463
1561
  /**
1464
1562
  * Gets key cards for wheel straight (5 cards forming wheel).
1465
1563
  *
@@ -1539,6 +1637,13 @@ class HandEvaluator {
1539
1637
  isInsideStraightDrawWheel: this.#getKeyCardsForInsideStraightDrawWheel(),
1540
1638
  isComboDraw: this.#getKeyCardsForComboDraw(),
1541
1639
  isUnderPair: this.#getKeyCardsForUnderPair(),
1640
+ isOvercard: this.#getKeyCardsForOvercard(),
1641
+ isTwoOvercards: this.#getKeyCardsForHoleCardCategory('isTwoOvercards'),
1642
+ isOvercardAndMiddleCard: this.#getKeyCardsForHoleCardCategory('isOvercardAndMiddleCard'),
1643
+ isOvercardAndUndercard: this.#getKeyCardsForHoleCardCategory('isOvercardAndUndercard'),
1644
+ isTwoMiddleCards: this.#getKeyCardsForHoleCardCategory('isTwoMiddleCards'),
1645
+ isMiddleAndUndercard: this.#getKeyCardsForHoleCardCategory('isMiddleAndUndercard'),
1646
+ isTwoUndercards: this.#getKeyCardsForHoleCardCategory('isTwoUndercards'),
1542
1647
  isHighCard: this.#getKeyCardsForHighCard()
1543
1648
  };
1544
1649
  }
@@ -1559,6 +1664,14 @@ class HandEvaluator {
1559
1664
  result[handType] = sortedCards;
1560
1665
  } else if (keyCards.length === 0) {
1561
1666
  result[handType] = [];
1667
+ } else if (HOLE_CARD_CATEGORIES.has(handType)) {
1668
+ // Hole card categorizations are evaluated against the board, which may include cards
1669
+ // outside the current 5-card combination, so kickers come from the full hand.
1670
+ const keyCardsSet = new Set(keyCards);
1671
+ const fullHand = this.originalCards || this.cards;
1672
+ result[handType] = fullHand
1673
+ .filter(c => !keyCardsSet.has(c))
1674
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1562
1675
  } else {
1563
1676
  const keyCardsSet = new Set(keyCards);
1564
1677
  result[handType] = this.cards
@@ -1667,6 +1780,95 @@ class HandEvaluator {
1667
1780
  return null;
1668
1781
  }
1669
1782
 
1783
+ // ==================== OVERCARDS ====================
1784
+
1785
+ /**
1786
+ * Classifies each hole card relative to the board (every card in the full hand that is not a
1787
+ * hole card). A card is an "overcard" when its rank beats every board card, an "undercard" when
1788
+ * every board card beats it, and a "middle card" when it falls strictly between the highest and
1789
+ * lowest board ranks. A hole card whose rank matches any board rank pairs the board and is
1790
+ * classified as "paired", which belongs to none of the three categories.
1791
+ *
1792
+ * @private
1793
+ * @returns {string[]|null} Array of 2 classifications ('over', 'middle', 'under' or 'paired')
1794
+ * parallel to this.holeCards, or null if hole cards were not supplied
1795
+ * @example
1796
+ * // Board: K Q 8 3, hole cards: A J
1797
+ * this.#classifyHoleCards(); // Returns ['over', 'middle']
1798
+ */
1799
+ #classifyHoleCards() {
1800
+ if (!this.holeCards || this.holeCards.length !== 2) return null;
1801
+
1802
+ const fullHand = this.originalCards || this.cards;
1803
+ const holeCardSet = new Set(this.holeCards);
1804
+ const board = fullHand.filter(c => !holeCardSet.has(c));
1805
+ if (board.length < 3) return null;
1806
+
1807
+ const boardRanks = new Set(board.map(c => c.slice(0, -1)));
1808
+ const boardValues = board.map(c => this.#getRankValue(c));
1809
+ const highestBoard = Math.max(...boardValues);
1810
+ const lowestBoard = Math.min(...boardValues);
1811
+
1812
+ return this.holeCards.map(card => {
1813
+ if (boardRanks.has(card.slice(0, -1))) return 'paired';
1814
+ const value = this.#getRankValue(card);
1815
+ if (value > highestBoard) return 'over';
1816
+ if (value < lowestBoard) return 'under';
1817
+ return 'middle';
1818
+ });
1819
+ }
1820
+
1821
+ /**
1822
+ * Counts how many hole cards fall into each classification.
1823
+ *
1824
+ * @private
1825
+ * @returns {Object} Object with 'over', 'middle', 'under' and 'paired' counts
1826
+ */
1827
+ #getHoleCardCounts() {
1828
+ const counts = { over: 0, middle: 0, under: 0, paired: 0 };
1829
+ const classifications = this.#classifyHoleCards();
1830
+ if (classifications) {
1831
+ for (const classification of classifications) counts[classification]++;
1832
+ }
1833
+ return counts;
1834
+ }
1835
+
1836
+ /**
1837
+ * Determines how the player's hole cards rank against the board and sets every overcard
1838
+ * categorization property. Requires hole cards to have been supplied; without them all
1839
+ * overcard properties are false.
1840
+ *
1841
+ * These categorizations describe the hole cards themselves, not the made hand, so they are
1842
+ * identical across every 5-card combination of a hand and are not filtered by strict mode.
1843
+ *
1844
+ * Sets: isOvercard, isTwoOvercards, isOvercardAndMiddleCard, isOvercardAndUndercard,
1845
+ * isTwoMiddleCards, isMiddleAndUndercard, isTwoUndercards.
1846
+ *
1847
+ * @returns {boolean} True if at least one hole card is an overcard, false otherwise
1848
+ * @example
1849
+ * // Board: K Q 8 3, Hand: AJ
1850
+ * const evaluator = new HandEvaluator(['Ks', 'Qh', '8d', '3c', 'Ad', 'Jh'], true, ['Ad', 'Jh']);
1851
+ * // isOvercard: true, isOvercardAndMiddleCard: true
1852
+ * @example
1853
+ * // Board: 9 7 4 2, Hand: AK (both hole cards beat every board card)
1854
+ * const twoOvercards = new HandEvaluator(['9s', '7h', '4d', '2c', 'Ad', 'Kh'], true, ['Ad', 'Kh']);
1855
+ * // isTwoOvercards: true
1856
+ */
1857
+ #isOvercards() {
1858
+ if (this.evaluation.isOvercard !== undefined) return this.evaluation.isOvercard;
1859
+
1860
+ const counts = this.#getHoleCardCounts();
1861
+
1862
+ this.evaluation.isTwoOvercards = counts.over === 2;
1863
+ this.evaluation.isOvercardAndMiddleCard = counts.over === 1 && counts.middle === 1;
1864
+ this.evaluation.isOvercardAndUndercard = counts.over === 1 && counts.under === 1;
1865
+ this.evaluation.isTwoMiddleCards = counts.middle === 2;
1866
+ this.evaluation.isMiddleAndUndercard = counts.middle === 1 && counts.under === 1;
1867
+ this.evaluation.isTwoUndercards = counts.under === 2;
1868
+
1869
+ return this.evaluation.isOvercard = counts.over > 0;
1870
+ }
1871
+
1670
1872
  /**
1671
1873
  * Determines if the hand is high card only (no pairs, two pair, three of a kind, full house,
1672
1874
  * four of a kind, flush, or straight).
@@ -1696,10 +1898,14 @@ class HandEvaluator {
1696
1898
  * @param {string[]} cards - Array of 5 or more card strings in format "Rs" where R is rank (A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2) and s is suit (s, h, d, c)
1697
1899
  * @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
1698
1900
  * If false, all applicable hand types are marked.
1901
+ * @param {string[]|null} [holeCards=null] - Optional array of exactly 2 cards identifying the player's hole cards.
1902
+ * Required for the overcard categorizations (isOvercard, isTwoOvercards, isOvercardAndMiddleCard,
1903
+ * isOvercardAndUndercard, isTwoMiddleCards, isMiddleAndUndercard, isTwoUndercards); without it those
1904
+ * properties are always false. Both cards must be present in `cards`.
1699
1905
  * @returns {Object} Object mapping sorted hand strings (e.g., "As, Ks, Qs, Js, Ts") to evaluation objects with boolean fields for all hand types and draws
1700
1906
  */
1701
- function evaluateHand(cards, strict = true) {
1702
- const evaluator = new HandEvaluator(cards, strict);
1907
+ function evaluateHand(cards, strict = true, holeCards = null) {
1908
+ const evaluator = new HandEvaluator(cards, strict, holeCards);
1703
1909
  return evaluator.evaluation;
1704
1910
  }
1705
1911
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-extval",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Poker hand evaluator with comprehensive test suite",
5
5
  "main": "evaluator.js",
6
6
  "files": [