poker-extval 3.2.1 → 3.3.1
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 +74 -3
- package/evaluator.js +281 -54
- 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,50 @@ 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
|
+
- Supplying hole cards also restricts draws to those that use at least one hole card (see [Draws](#draws)).
|
|
101
|
+
- 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.
|
|
102
|
+
- 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`.
|
|
103
|
+
|
|
59
104
|
### Strict Mode
|
|
60
105
|
|
|
61
106
|
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 +191,19 @@ The `evaluateHand()` function returns an object where:
|
|
|
146
191
|
- `isMiddleThreeOfAKind` - Three of a kind is neither highest nor lowest
|
|
147
192
|
- `isBottomThreeOfAKind` - Three of a kind is the lowest rank
|
|
148
193
|
|
|
194
|
+
#### Overcard Categorization (requires `holeCards`; not filtered by strict mode)
|
|
195
|
+
- `isOvercard` - At least one hole card ranks above every board card
|
|
196
|
+
- `isTwoOvercards` - Both hole cards rank above every board card
|
|
197
|
+
- `isOvercardAndMiddleCard` - One overcard, one middle card
|
|
198
|
+
- `isOvercardAndUndercard` - One overcard, one undercard
|
|
199
|
+
- `isTwoMiddleCards` - Both hole cards sit strictly between the highest and lowest board ranks
|
|
200
|
+
- `isMiddleAndUndercard` - One middle card, one undercard
|
|
201
|
+
- `isTwoUndercards` - Both hole cards rank below every board card
|
|
202
|
+
|
|
203
|
+
#### Other Categorization
|
|
204
|
+
- `isUnderPair` - Pocket pair ranked below every other card in the hand (requires 6+ cards)
|
|
205
|
+
- `isComboDraw` - Both a flush draw (not backdoor) and a straight draw
|
|
206
|
+
|
|
149
207
|
#### Draws
|
|
150
208
|
- `isFlushDraw` - Four cards of the same suit (one away from flush)
|
|
151
209
|
- `isBackdoorFlushDraw` - Three cards of the same suit (two away from flush)
|
|
@@ -153,6 +211,8 @@ The `evaluateHand()` function returns an object where:
|
|
|
153
211
|
- `isInsideStraightDraw` - One card away from straight (needed card in middle)
|
|
154
212
|
- `isStraightDraw` - Any type of straight draw (open-ended or inside, standard or wheel)
|
|
155
213
|
|
|
214
|
+
**Note**: When `holeCards` are supplied, every draw (including `isComboDraw` and the wheel draws) must use at least one hole card. A draw made entirely of board cards is shared by every player, so it is not reported: 2-2 on a 9-8-7-6 board is not an open-ended straight draw, and A♦-Q♦ on a K♠-7♠-2♠ flop is not a backdoor flush draw. Without `holeCards` the evaluator cannot tell the board from the hand, so draws are detected from all cards.
|
|
215
|
+
|
|
156
216
|
#### Wheel Straight Support
|
|
157
217
|
- `isStraightWheel` - Wheel straight (A-2-3-4-5)
|
|
158
218
|
- `isOpenEndedStraightDrawWheel` - Open-ended draw using wheel ranking
|
|
@@ -162,6 +222,8 @@ The `evaluateHand()` function returns an object where:
|
|
|
162
222
|
- `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
223
|
- `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
224
|
|
|
225
|
+
**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.
|
|
226
|
+
|
|
165
227
|
**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
228
|
|
|
167
229
|
### Example Response
|
|
@@ -352,6 +414,7 @@ The evaluator validates input and throws descriptive errors for:
|
|
|
352
414
|
- Invalid rank (not A, K, Q, J, T, 9-2)
|
|
353
415
|
- Invalid suit (not s, h, d, c)
|
|
354
416
|
- Duplicate cards
|
|
417
|
+
- Hole cards that are not an array of exactly 2 distinct cards drawn from `cards`
|
|
355
418
|
|
|
356
419
|
Example:
|
|
357
420
|
```javascript
|
|
@@ -370,23 +433,25 @@ Run the test suite:
|
|
|
370
433
|
npm test
|
|
371
434
|
```
|
|
372
435
|
|
|
373
|
-
The test suite includes
|
|
436
|
+
The test suite includes 700+ tests covering:
|
|
374
437
|
- All hand types
|
|
375
438
|
- Edge cases
|
|
376
439
|
- Invalid input handling
|
|
377
440
|
- Wheel straights
|
|
378
441
|
- Draw detection
|
|
379
442
|
- Categorization logic
|
|
443
|
+
- Overcard categorization and hole card validation
|
|
380
444
|
|
|
381
445
|
## API Reference
|
|
382
446
|
|
|
383
|
-
### `evaluateHand(cards, strict)`
|
|
447
|
+
### `evaluateHand(cards, strict, holeCards)`
|
|
384
448
|
|
|
385
449
|
Main function to evaluate poker hands.
|
|
386
450
|
|
|
387
451
|
**Parameters:**
|
|
388
452
|
- `cards` (string[]): Array of 5 or more card strings
|
|
389
453
|
- `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`).
|
|
454
|
+
- `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
455
|
|
|
391
456
|
**Returns:**
|
|
392
457
|
- Object mapping sorted hand strings to evaluation objects
|
|
@@ -399,7 +464,7 @@ Main function to evaluate poker hands.
|
|
|
399
464
|
#### Constructor
|
|
400
465
|
|
|
401
466
|
```javascript
|
|
402
|
-
new HandEvaluator(cards, strict)
|
|
467
|
+
new HandEvaluator(cards, strict, holeCards)
|
|
403
468
|
```
|
|
404
469
|
|
|
405
470
|
Creates a new HandEvaluator instance.
|
|
@@ -407,14 +472,20 @@ Creates a new HandEvaluator instance.
|
|
|
407
472
|
**Parameters:**
|
|
408
473
|
- `cards` (string[]): Array of 5 or more card strings
|
|
409
474
|
- `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`).
|
|
475
|
+
- `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
476
|
|
|
411
477
|
**Properties:**
|
|
412
478
|
- `evaluation` - Object mapping sorted hand strings to evaluation objects (same format as `evaluateHand()` return value)
|
|
413
479
|
- `cards` - Array of input cards
|
|
414
480
|
- `strict` - Boolean indicating whether strict mode is enabled
|
|
481
|
+
- `holeCards` - Array of the 2 hole cards, or `null` if none were supplied
|
|
415
482
|
|
|
416
483
|
**Note**: All internal methods are private. Use the `evaluation` property to access evaluation results.
|
|
417
484
|
|
|
485
|
+
## Releasing
|
|
486
|
+
|
|
487
|
+
Pushing to `main` publishes to npm via GitHub Actions (`.github/workflows/publish.yml`), using npm trusted publishing so no token is stored. Every push to `main` is released: the workflow runs the tests, bumps the patch version, commits the bump back to `main`, tags it `v<version>` and publishes. There is no need to bump the version by hand for patch releases. For a minor or major release, bump it in your change (`npm version minor|major --no-git-tag-version`); a version that is not on npm yet is published as is, without an extra bump.
|
|
488
|
+
|
|
418
489
|
## License
|
|
419
490
|
|
|
420
491
|
MIT License
|
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
|
}
|
|
@@ -882,7 +954,8 @@ class HandEvaluator {
|
|
|
882
954
|
|
|
883
955
|
/**
|
|
884
956
|
* Determines if the hand has four cards of the same suit (one card away from a flush).
|
|
885
|
-
* Returns false if the hand is already a flush.
|
|
957
|
+
* Returns false if the hand is already a flush. When hole cards are supplied, at least one
|
|
958
|
+
* of the four suited cards must be a hole card.
|
|
886
959
|
*
|
|
887
960
|
* @returns {boolean} True if the hand is a flush draw, false otherwise
|
|
888
961
|
* @example
|
|
@@ -894,12 +967,13 @@ class HandEvaluator {
|
|
|
894
967
|
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
895
968
|
if (this.evaluation.isFlush) return this.evaluation.isFlushDraw = false;
|
|
896
969
|
const suits = this.#countSuits();
|
|
897
|
-
return this.evaluation.isFlushDraw = Object.
|
|
970
|
+
return this.evaluation.isFlushDraw = Object.keys(suits).some(s => suits[s] === 4 && this.#usesHoleCard(this.#getCardsOfSuit(s)));
|
|
898
971
|
}
|
|
899
972
|
|
|
900
973
|
/**
|
|
901
974
|
* Determines if the hand has three cards of the same suit (two cards away from a flush).
|
|
902
|
-
* Returns false if the hand is already a flush or flush draw.
|
|
975
|
+
* Returns false if the hand is already a flush or flush draw. When hole cards are supplied,
|
|
976
|
+
* at least one of the three suited cards must be a hole card.
|
|
903
977
|
*
|
|
904
978
|
* @returns {boolean} True if the hand is a backdoor flush draw, false otherwise
|
|
905
979
|
* @example
|
|
@@ -912,7 +986,7 @@ class HandEvaluator {
|
|
|
912
986
|
if (this.evaluation.isFlushDraw === undefined) this.#isFlushDraw();
|
|
913
987
|
if (this.evaluation.isFlush || this.evaluation.isFlushDraw) return this.evaluation.isBackdoorFlushDraw = false;
|
|
914
988
|
const suits = this.#countSuits();
|
|
915
|
-
return this.evaluation.isBackdoorFlushDraw = Object.
|
|
989
|
+
return this.evaluation.isBackdoorFlushDraw = Object.keys(suits).some(s => suits[s] === 3 && this.#usesHoleCard(this.#getCardsOfSuit(s)));
|
|
916
990
|
}
|
|
917
991
|
|
|
918
992
|
// ==================== STRAIGHT EVALUATION ====================
|
|
@@ -1008,6 +1082,7 @@ class HandEvaluator {
|
|
|
1008
1082
|
/**
|
|
1009
1083
|
* Determines if the hand is an open-ended straight draw (one card away from a straight on either end).
|
|
1010
1084
|
* Checks both standard and wheel-equivalent open-ended draws.
|
|
1085
|
+
* When hole cards are supplied, the four drawing cards must include at least one hole card.
|
|
1011
1086
|
* Returns false if the hand already contains pairs, two pair, three of a kind, full house, four of a kind, or a straight.
|
|
1012
1087
|
*
|
|
1013
1088
|
* @returns {boolean} True if the hand is an open-ended straight draw, false otherwise
|
|
@@ -1028,8 +1103,7 @@ class HandEvaluator {
|
|
|
1028
1103
|
if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
1029
1104
|
return this.evaluation.isOpenEndedStraightDraw = false;
|
|
1030
1105
|
}
|
|
1031
|
-
this.#
|
|
1032
|
-
const result = this.#getDistance(this.cards.slice(0,4)) === 3 || this.#getDistance(this.cards.slice(1,5)) === 3;
|
|
1106
|
+
const result = this.#findStraightDrawWindow([3], false) !== null;
|
|
1033
1107
|
if (result) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
1034
1108
|
// Check wheel equivalent
|
|
1035
1109
|
if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
@@ -1052,13 +1126,13 @@ class HandEvaluator {
|
|
|
1052
1126
|
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
1053
1127
|
if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
|
|
1054
1128
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isOpenEndedStraightDrawWheel = false;
|
|
1055
|
-
this.#
|
|
1056
|
-
return this.evaluation.isOpenEndedStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 3 || this.#getDistanceWheel(this.cards.slice(1,5)) === 3;
|
|
1129
|
+
return this.evaluation.isOpenEndedStraightDrawWheel = this.#findStraightDrawWindow([3], true) !== null;
|
|
1057
1130
|
}
|
|
1058
1131
|
|
|
1059
1132
|
/**
|
|
1060
1133
|
* Determines if the hand is an inside straight draw (one card away from a straight, but the needed card is in the middle).
|
|
1061
1134
|
* Checks both standard and wheel-equivalent inside draws.
|
|
1135
|
+
* When hole cards are supplied, the four drawing cards must include at least one hole card.
|
|
1062
1136
|
* Returns false if the hand already contains pairs, two pair, three of a kind, full house, four of a kind, or a straight.
|
|
1063
1137
|
*
|
|
1064
1138
|
* @returns {boolean} True if the hand is an inside straight draw, false otherwise
|
|
@@ -1080,8 +1154,7 @@ class HandEvaluator {
|
|
|
1080
1154
|
return this.evaluation.isInsideStraightDraw = false;
|
|
1081
1155
|
}
|
|
1082
1156
|
|
|
1083
|
-
this.#
|
|
1084
|
-
const result = this.#getDistance(this.cards.slice(0,4)) === 4 || this.#getDistance(this.cards.slice(1,5)) === 4;
|
|
1157
|
+
const result = this.#findStraightDrawWindow([4], false) !== null;
|
|
1085
1158
|
if (result) return this.evaluation.isInsideStraightDraw = true;
|
|
1086
1159
|
// Check wheel equivalent
|
|
1087
1160
|
if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
|
|
@@ -1105,8 +1178,7 @@ class HandEvaluator {
|
|
|
1105
1178
|
if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
|
|
1106
1179
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isInsideStraightDrawWheel = false;
|
|
1107
1180
|
|
|
1108
|
-
this.#
|
|
1109
|
-
return this.evaluation.isInsideStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 4 || this.#getDistanceWheel(this.cards.slice(1,5)) === 4;
|
|
1181
|
+
return this.evaluation.isInsideStraightDrawWheel = this.#findStraightDrawWindow([4], true) !== null;
|
|
1110
1182
|
}
|
|
1111
1183
|
|
|
1112
1184
|
/**
|
|
@@ -1382,8 +1454,7 @@ class HandEvaluator {
|
|
|
1382
1454
|
if (!this.evaluation.isFlushDraw) return [];
|
|
1383
1455
|
const suits = this.#countSuits();
|
|
1384
1456
|
const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
|
|
1385
|
-
return this
|
|
1386
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1457
|
+
return this.#getCardsOfSuit(flushSuit);
|
|
1387
1458
|
}
|
|
1388
1459
|
|
|
1389
1460
|
/**
|
|
@@ -1396,8 +1467,7 @@ class HandEvaluator {
|
|
|
1396
1467
|
if (!this.evaluation.isBackdoorFlushDraw) return [];
|
|
1397
1468
|
const suits = this.#countSuits();
|
|
1398
1469
|
const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
|
|
1399
|
-
return this
|
|
1400
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1470
|
+
return this.#getCardsOfSuit(flushSuit);
|
|
1401
1471
|
}
|
|
1402
1472
|
|
|
1403
1473
|
/**
|
|
@@ -1408,7 +1478,7 @@ class HandEvaluator {
|
|
|
1408
1478
|
*/
|
|
1409
1479
|
#getKeyCardsForOpenEndedStraightDraw() {
|
|
1410
1480
|
if (!this.evaluation.isOpenEndedStraightDraw) return [];
|
|
1411
|
-
return this.#getStraightDrawCards();
|
|
1481
|
+
return this.#getStraightDrawCards([3]);
|
|
1412
1482
|
}
|
|
1413
1483
|
|
|
1414
1484
|
/**
|
|
@@ -1419,7 +1489,7 @@ class HandEvaluator {
|
|
|
1419
1489
|
*/
|
|
1420
1490
|
#getKeyCardsForInsideStraightDraw() {
|
|
1421
1491
|
if (!this.evaluation.isInsideStraightDraw) return [];
|
|
1422
|
-
return this.#getStraightDrawCards();
|
|
1492
|
+
return this.#getStraightDrawCards([4]);
|
|
1423
1493
|
}
|
|
1424
1494
|
|
|
1425
1495
|
/**
|
|
@@ -1460,6 +1530,32 @@ class HandEvaluator {
|
|
|
1460
1530
|
return this.#getKeyCardsForPair();
|
|
1461
1531
|
}
|
|
1462
1532
|
|
|
1533
|
+
/**
|
|
1534
|
+
* Gets key cards for the general overcard property (the hole cards that beat the whole board).
|
|
1535
|
+
*
|
|
1536
|
+
* @private
|
|
1537
|
+
* @returns {string[]} Array of 1 or 2 overcards if isOvercard, empty array otherwise
|
|
1538
|
+
*/
|
|
1539
|
+
#getKeyCardsForOvercard() {
|
|
1540
|
+
if (!this.evaluation.isOvercard) return [];
|
|
1541
|
+
const classifications = this.#classifyHoleCards();
|
|
1542
|
+
return this.holeCards
|
|
1543
|
+
.filter((card, i) => classifications[i] === 'over')
|
|
1544
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Gets key cards for a two-card hole card categorization (both hole cards, sorted high to low).
|
|
1549
|
+
*
|
|
1550
|
+
* @private
|
|
1551
|
+
* @param {string} category - The evaluation property name (e.g. 'isTwoOvercards')
|
|
1552
|
+
* @returns {string[]} Array of both hole cards if the category is true, empty array otherwise
|
|
1553
|
+
*/
|
|
1554
|
+
#getKeyCardsForHoleCardCategory(category) {
|
|
1555
|
+
if (!this.evaluation[category]) return [];
|
|
1556
|
+
return [...this.holeCards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1463
1559
|
/**
|
|
1464
1560
|
* Gets key cards for wheel straight (5 cards forming wheel).
|
|
1465
1561
|
*
|
|
@@ -1479,7 +1575,7 @@ class HandEvaluator {
|
|
|
1479
1575
|
*/
|
|
1480
1576
|
#getKeyCardsForOpenEndedStraightDrawWheel() {
|
|
1481
1577
|
if (!this.evaluation.isOpenEndedStraightDrawWheel) return [];
|
|
1482
|
-
return this.#getStraightDrawCards();
|
|
1578
|
+
return this.#getStraightDrawCards([3]);
|
|
1483
1579
|
}
|
|
1484
1580
|
|
|
1485
1581
|
/**
|
|
@@ -1490,7 +1586,7 @@ class HandEvaluator {
|
|
|
1490
1586
|
*/
|
|
1491
1587
|
#getKeyCardsForInsideStraightDrawWheel() {
|
|
1492
1588
|
if (!this.evaluation.isInsideStraightDrawWheel) return [];
|
|
1493
|
-
return this.#getStraightDrawCards();
|
|
1589
|
+
return this.#getStraightDrawCards([4]);
|
|
1494
1590
|
}
|
|
1495
1591
|
|
|
1496
1592
|
/**
|
|
@@ -1539,6 +1635,13 @@ class HandEvaluator {
|
|
|
1539
1635
|
isInsideStraightDrawWheel: this.#getKeyCardsForInsideStraightDrawWheel(),
|
|
1540
1636
|
isComboDraw: this.#getKeyCardsForComboDraw(),
|
|
1541
1637
|
isUnderPair: this.#getKeyCardsForUnderPair(),
|
|
1638
|
+
isOvercard: this.#getKeyCardsForOvercard(),
|
|
1639
|
+
isTwoOvercards: this.#getKeyCardsForHoleCardCategory('isTwoOvercards'),
|
|
1640
|
+
isOvercardAndMiddleCard: this.#getKeyCardsForHoleCardCategory('isOvercardAndMiddleCard'),
|
|
1641
|
+
isOvercardAndUndercard: this.#getKeyCardsForHoleCardCategory('isOvercardAndUndercard'),
|
|
1642
|
+
isTwoMiddleCards: this.#getKeyCardsForHoleCardCategory('isTwoMiddleCards'),
|
|
1643
|
+
isMiddleAndUndercard: this.#getKeyCardsForHoleCardCategory('isMiddleAndUndercard'),
|
|
1644
|
+
isTwoUndercards: this.#getKeyCardsForHoleCardCategory('isTwoUndercards'),
|
|
1542
1645
|
isHighCard: this.#getKeyCardsForHighCard()
|
|
1543
1646
|
};
|
|
1544
1647
|
}
|
|
@@ -1559,6 +1662,14 @@ class HandEvaluator {
|
|
|
1559
1662
|
result[handType] = sortedCards;
|
|
1560
1663
|
} else if (keyCards.length === 0) {
|
|
1561
1664
|
result[handType] = [];
|
|
1665
|
+
} else if (HOLE_CARD_CATEGORIES.has(handType)) {
|
|
1666
|
+
// Hole card categorizations are evaluated against the board, which may include cards
|
|
1667
|
+
// outside the current 5-card combination, so kickers come from the full hand.
|
|
1668
|
+
const keyCardsSet = new Set(keyCards);
|
|
1669
|
+
const fullHand = this.originalCards || this.cards;
|
|
1670
|
+
result[handType] = fullHand
|
|
1671
|
+
.filter(c => !keyCardsSet.has(c))
|
|
1672
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1562
1673
|
} else {
|
|
1563
1674
|
const keyCardsSet = new Set(keyCards);
|
|
1564
1675
|
result[handType] = this.cards
|
|
@@ -1572,41 +1683,64 @@ class HandEvaluator {
|
|
|
1572
1683
|
|
|
1573
1684
|
/**
|
|
1574
1685
|
* Helper method to extract the 4 cards that form a straight draw.
|
|
1575
|
-
* Handles both standard and wheel straight draws.
|
|
1686
|
+
* Handles both standard and wheel straight draws, preferring a standard draw.
|
|
1687
|
+
* When hole cards are supplied, only draws that use at least one hole card are returned.
|
|
1576
1688
|
*
|
|
1577
1689
|
* @private
|
|
1690
|
+
* @param {number[]} [distances=[3, 4]] - Accepted rank spans for the 4 drawing cards
|
|
1691
|
+
* (3 = open-ended, 4 = inside)
|
|
1578
1692
|
* @returns {string[]} Array of 4 cards forming the draw, sorted from highest to lowest
|
|
1579
1693
|
*/
|
|
1580
|
-
#getStraightDrawCards() {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
this.#sortWheel();
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
return [...firstFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
1603
|
-
}
|
|
1604
|
-
const lastFourDistWheel = this.#getDistanceWheel(lastFourWheel);
|
|
1605
|
-
if (lastFourDistWheel === 3 || lastFourDistWheel === 4) {
|
|
1606
|
-
return [...lastFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
1694
|
+
#getStraightDrawCards(distances = [3, 4]) {
|
|
1695
|
+
return this.#findStraightDrawWindow(distances, false) || this.#findStraightDrawWindow(distances, true) || [];
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Finds 4 rank-adjacent cards (the highest four or the lowest four) whose rank span is one of
|
|
1700
|
+
* the given distances. When hole cards are supplied, a window made only of board cards is
|
|
1701
|
+
* skipped, since a draw the board gives every player is not the player's draw.
|
|
1702
|
+
*
|
|
1703
|
+
* @private
|
|
1704
|
+
* @param {number[]} distances - Accepted rank spans for the 4 cards (3 = open-ended, 4 = inside)
|
|
1705
|
+
* @param {boolean} wheel - If true, ranks the Ace low (for wheel draws)
|
|
1706
|
+
* @returns {string[]|null} Array of 4 cards sorted from highest to lowest, or null if none match
|
|
1707
|
+
*/
|
|
1708
|
+
#findStraightDrawWindow(distances, wheel) {
|
|
1709
|
+
const rankValue = wheel ? c => this.#getRankValueWheel(c) : c => this.#getRankValue(c);
|
|
1710
|
+
if (wheel) this.#sortWheel(); else this.#sort();
|
|
1711
|
+
for (const window of [this.cards.slice(0, 4), this.cards.slice(1, 5)]) {
|
|
1712
|
+
const distance = wheel ? this.#getDistanceWheel(window) : this.#getDistance(window);
|
|
1713
|
+
if (distances.includes(distance) && this.#usesHoleCard(window)) {
|
|
1714
|
+
return [...window].sort((a, b) => rankValue(b) - rankValue(a));
|
|
1715
|
+
}
|
|
1607
1716
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* Gets the cards of a given suit in the hand, sorted from highest to lowest rank.
|
|
1722
|
+
*
|
|
1723
|
+
* @private
|
|
1724
|
+
* @param {string} suit - Suit character (s, h, d, c)
|
|
1725
|
+
* @returns {string[]} Cards of that suit
|
|
1726
|
+
*/
|
|
1727
|
+
#getCardsOfSuit(suit) {
|
|
1728
|
+
return this.cards.filter(c => c.slice(-1) === suit)
|
|
1729
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/**
|
|
1733
|
+
* Determines whether a draw's cards include at least one of the player's hole cards.
|
|
1734
|
+
* A draw made entirely of board cards is shared by every player, so when hole cards are
|
|
1735
|
+
* supplied it does not count as the player's draw. Without hole cards every draw qualifies.
|
|
1736
|
+
*
|
|
1737
|
+
* @private
|
|
1738
|
+
* @param {string[]} cards - The cards forming the draw
|
|
1739
|
+
* @returns {boolean} True if no hole cards were supplied or a hole card is among the cards
|
|
1740
|
+
*/
|
|
1741
|
+
#usesHoleCard(cards) {
|
|
1742
|
+
if (!this.holeCards) return true;
|
|
1743
|
+
return cards.some(c => this.holeCards.includes(c));
|
|
1610
1744
|
}
|
|
1611
1745
|
|
|
1612
1746
|
// ==================== HIGH CARD ====================
|
|
@@ -1667,6 +1801,95 @@ class HandEvaluator {
|
|
|
1667
1801
|
return null;
|
|
1668
1802
|
}
|
|
1669
1803
|
|
|
1804
|
+
// ==================== OVERCARDS ====================
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* Classifies each hole card relative to the board (every card in the full hand that is not a
|
|
1808
|
+
* hole card). A card is an "overcard" when its rank beats every board card, an "undercard" when
|
|
1809
|
+
* every board card beats it, and a "middle card" when it falls strictly between the highest and
|
|
1810
|
+
* lowest board ranks. A hole card whose rank matches any board rank pairs the board and is
|
|
1811
|
+
* classified as "paired", which belongs to none of the three categories.
|
|
1812
|
+
*
|
|
1813
|
+
* @private
|
|
1814
|
+
* @returns {string[]|null} Array of 2 classifications ('over', 'middle', 'under' or 'paired')
|
|
1815
|
+
* parallel to this.holeCards, or null if hole cards were not supplied
|
|
1816
|
+
* @example
|
|
1817
|
+
* // Board: K Q 8 3, hole cards: A J
|
|
1818
|
+
* this.#classifyHoleCards(); // Returns ['over', 'middle']
|
|
1819
|
+
*/
|
|
1820
|
+
#classifyHoleCards() {
|
|
1821
|
+
if (!this.holeCards || this.holeCards.length !== 2) return null;
|
|
1822
|
+
|
|
1823
|
+
const fullHand = this.originalCards || this.cards;
|
|
1824
|
+
const holeCardSet = new Set(this.holeCards);
|
|
1825
|
+
const board = fullHand.filter(c => !holeCardSet.has(c));
|
|
1826
|
+
if (board.length < 3) return null;
|
|
1827
|
+
|
|
1828
|
+
const boardRanks = new Set(board.map(c => c.slice(0, -1)));
|
|
1829
|
+
const boardValues = board.map(c => this.#getRankValue(c));
|
|
1830
|
+
const highestBoard = Math.max(...boardValues);
|
|
1831
|
+
const lowestBoard = Math.min(...boardValues);
|
|
1832
|
+
|
|
1833
|
+
return this.holeCards.map(card => {
|
|
1834
|
+
if (boardRanks.has(card.slice(0, -1))) return 'paired';
|
|
1835
|
+
const value = this.#getRankValue(card);
|
|
1836
|
+
if (value > highestBoard) return 'over';
|
|
1837
|
+
if (value < lowestBoard) return 'under';
|
|
1838
|
+
return 'middle';
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
* Counts how many hole cards fall into each classification.
|
|
1844
|
+
*
|
|
1845
|
+
* @private
|
|
1846
|
+
* @returns {Object} Object with 'over', 'middle', 'under' and 'paired' counts
|
|
1847
|
+
*/
|
|
1848
|
+
#getHoleCardCounts() {
|
|
1849
|
+
const counts = { over: 0, middle: 0, under: 0, paired: 0 };
|
|
1850
|
+
const classifications = this.#classifyHoleCards();
|
|
1851
|
+
if (classifications) {
|
|
1852
|
+
for (const classification of classifications) counts[classification]++;
|
|
1853
|
+
}
|
|
1854
|
+
return counts;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/**
|
|
1858
|
+
* Determines how the player's hole cards rank against the board and sets every overcard
|
|
1859
|
+
* categorization property. Requires hole cards to have been supplied; without them all
|
|
1860
|
+
* overcard properties are false.
|
|
1861
|
+
*
|
|
1862
|
+
* These categorizations describe the hole cards themselves, not the made hand, so they are
|
|
1863
|
+
* identical across every 5-card combination of a hand and are not filtered by strict mode.
|
|
1864
|
+
*
|
|
1865
|
+
* Sets: isOvercard, isTwoOvercards, isOvercardAndMiddleCard, isOvercardAndUndercard,
|
|
1866
|
+
* isTwoMiddleCards, isMiddleAndUndercard, isTwoUndercards.
|
|
1867
|
+
*
|
|
1868
|
+
* @returns {boolean} True if at least one hole card is an overcard, false otherwise
|
|
1869
|
+
* @example
|
|
1870
|
+
* // Board: K Q 8 3, Hand: AJ
|
|
1871
|
+
* const evaluator = new HandEvaluator(['Ks', 'Qh', '8d', '3c', 'Ad', 'Jh'], true, ['Ad', 'Jh']);
|
|
1872
|
+
* // isOvercard: true, isOvercardAndMiddleCard: true
|
|
1873
|
+
* @example
|
|
1874
|
+
* // Board: 9 7 4 2, Hand: AK (both hole cards beat every board card)
|
|
1875
|
+
* const twoOvercards = new HandEvaluator(['9s', '7h', '4d', '2c', 'Ad', 'Kh'], true, ['Ad', 'Kh']);
|
|
1876
|
+
* // isTwoOvercards: true
|
|
1877
|
+
*/
|
|
1878
|
+
#isOvercards() {
|
|
1879
|
+
if (this.evaluation.isOvercard !== undefined) return this.evaluation.isOvercard;
|
|
1880
|
+
|
|
1881
|
+
const counts = this.#getHoleCardCounts();
|
|
1882
|
+
|
|
1883
|
+
this.evaluation.isTwoOvercards = counts.over === 2;
|
|
1884
|
+
this.evaluation.isOvercardAndMiddleCard = counts.over === 1 && counts.middle === 1;
|
|
1885
|
+
this.evaluation.isOvercardAndUndercard = counts.over === 1 && counts.under === 1;
|
|
1886
|
+
this.evaluation.isTwoMiddleCards = counts.middle === 2;
|
|
1887
|
+
this.evaluation.isMiddleAndUndercard = counts.middle === 1 && counts.under === 1;
|
|
1888
|
+
this.evaluation.isTwoUndercards = counts.under === 2;
|
|
1889
|
+
|
|
1890
|
+
return this.evaluation.isOvercard = counts.over > 0;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1670
1893
|
/**
|
|
1671
1894
|
* Determines if the hand is high card only (no pairs, two pair, three of a kind, full house,
|
|
1672
1895
|
* four of a kind, flush, or straight).
|
|
@@ -1696,10 +1919,14 @@ class HandEvaluator {
|
|
|
1696
1919
|
* @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
1920
|
* @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
|
|
1698
1921
|
* If false, all applicable hand types are marked.
|
|
1922
|
+
* @param {string[]|null} [holeCards=null] - Optional array of exactly 2 cards identifying the player's hole cards.
|
|
1923
|
+
* Required for the overcard categorizations (isOvercard, isTwoOvercards, isOvercardAndMiddleCard,
|
|
1924
|
+
* isOvercardAndUndercard, isTwoMiddleCards, isMiddleAndUndercard, isTwoUndercards); without it those
|
|
1925
|
+
* properties are always false. Both cards must be present in `cards`.
|
|
1699
1926
|
* @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
1927
|
*/
|
|
1701
|
-
function evaluateHand(cards, strict = true) {
|
|
1702
|
-
const evaluator = new HandEvaluator(cards, strict);
|
|
1928
|
+
function evaluateHand(cards, strict = true, holeCards = null) {
|
|
1929
|
+
const evaluator = new HandEvaluator(cards, strict, holeCards);
|
|
1703
1930
|
return evaluator.evaluation;
|
|
1704
1931
|
}
|
|
1705
1932
|
|