poker-extval 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -16
- package/evaluator.js +463 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,10 @@ A comprehensive poker hand evaluation library for Node.js that evaluates poker h
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
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
|
+
- **Strict Mode (Default)**: Only marks the strongest made hand and stronger draws, preventing weaker hands from being marked when stronger ones exist
|
|
8
9
|
- **Detailed Categorization**: Categorizes pairs (Top, Middle, Bottom), two pairs, and three of a kind by position
|
|
9
10
|
- **Draw Detection**: Identifies flush draws, backdoor flush draws, open-ended straight draws, and inside straight draws
|
|
11
|
+
- **Key Cards and Kicker Cards**: Provides detailed card arrays showing which cards form each hand type and which are kickers
|
|
10
12
|
- **Wheel Straight Support**: Handles Ace-low straights (A-2-3-4-5) correctly
|
|
11
13
|
- **Multiple Card Support**: Accepts 5 or more cards and evaluates all possible 5-card combinations
|
|
12
14
|
- **Extensive Documentation**: Full JSDoc comments for all methods
|
|
@@ -24,15 +26,15 @@ npm install poker-extval
|
|
|
24
26
|
```javascript
|
|
25
27
|
const { evaluateHand } = require('poker-extval');
|
|
26
28
|
|
|
27
|
-
// Evaluate a 5-card hand
|
|
29
|
+
// Evaluate a 5-card hand (strict mode enabled by default)
|
|
28
30
|
const result = evaluateHand(['As', 'Ks', 'Qs', 'Js', 'Ts']);
|
|
29
31
|
console.log(result);
|
|
30
32
|
// {
|
|
31
33
|
// "As, Ks, Qs, Js, Ts": {
|
|
32
34
|
// "isRoyalFlush": true,
|
|
33
35
|
// "isStraightFlush": false,
|
|
34
|
-
// "isFlush":
|
|
35
|
-
// "isStraight":
|
|
36
|
+
// "isFlush": false, // Filtered out in strict mode (royal flush is stronger)
|
|
37
|
+
// "isStraight": false, // Filtered out in strict mode (royal flush is stronger)
|
|
36
38
|
// ...
|
|
37
39
|
// }
|
|
38
40
|
// }
|
|
@@ -43,14 +45,43 @@ console.log(result);
|
|
|
43
45
|
```javascript
|
|
44
46
|
const { HandEvaluator } = require('poker-extval');
|
|
45
47
|
|
|
46
|
-
// Create an evaluator instance
|
|
48
|
+
// Create an evaluator instance (strict mode enabled by default)
|
|
47
49
|
const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts', '9s']);
|
|
48
50
|
|
|
49
51
|
// Access evaluation results
|
|
50
52
|
const evaluation = evaluator.evaluation;
|
|
51
53
|
// Object with keys for each 5-card combination
|
|
54
|
+
|
|
55
|
+
// Non-strict mode (all applicable hands marked)
|
|
56
|
+
const nonStrictEvaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs'], false);
|
|
52
57
|
```
|
|
53
58
|
|
|
59
|
+
### Strict Mode
|
|
60
|
+
|
|
61
|
+
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.
|
|
62
|
+
|
|
63
|
+
**Example with strict mode (default):**
|
|
64
|
+
```javascript
|
|
65
|
+
const result = evaluateHand(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
|
|
66
|
+
// isTwoPair: true
|
|
67
|
+
// isPair: false (filtered out because two pair is stronger)
|
|
68
|
+
// isTopAndMiddlePair: true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Example with non-strict mode:**
|
|
72
|
+
```javascript
|
|
73
|
+
const result = evaluateHand(['As', 'Ah', 'Kd', 'Kc', 'Qs'], false);
|
|
74
|
+
// isTwoPair: true
|
|
75
|
+
// isPair: false (evaluation logic prevents this, not strict mode)
|
|
76
|
+
// isTopAndMiddlePair: true
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Strict Mode Rules:**
|
|
80
|
+
- Only the strongest made hand is marked as true
|
|
81
|
+
- Descriptive labels (isTopPair, etc.) are only true if they match the strongest hand
|
|
82
|
+
- Made flush or stronger cannot have straight draws (flush > straight)
|
|
83
|
+
- Draws can coexist when no made hand exists (flush draw + straight draw is valid)
|
|
84
|
+
|
|
54
85
|
### Evaluating Multiple Card Hands
|
|
55
86
|
|
|
56
87
|
When you provide more than 5 cards, the evaluator generates all possible 5-card combinations:
|
|
@@ -100,17 +131,17 @@ The `evaluateHand()` function returns an object where:
|
|
|
100
131
|
- `isPair` - Exactly one pair
|
|
101
132
|
- `isHighCard` - No made hand
|
|
102
133
|
|
|
103
|
-
#### Pair Categorization (only true if `isPair` is true)
|
|
134
|
+
#### Pair Categorization (only true if `isPair` is true in strict mode)
|
|
104
135
|
- `isTopPair` - Pair is the highest card
|
|
105
136
|
- `isMiddlePair` - Pair is neither highest nor lowest
|
|
106
137
|
- `isBottomPair` - Pair is the lowest card
|
|
107
138
|
|
|
108
|
-
#### Two Pair Categorization (only true if `isTwoPair` is true)
|
|
139
|
+
#### Two Pair Categorization (only true if `isTwoPair` is true in strict mode)
|
|
109
140
|
- `isTopAndMiddlePair` - Both pairs are in top two ranks
|
|
110
141
|
- `isTopAndBottomPair` - One pair is highest, other is lowest
|
|
111
142
|
- `isMiddleAndBottomPair` - Both pairs are in middle and bottom ranks
|
|
112
143
|
|
|
113
|
-
#### Three of a Kind Categorization (only true if `isThreeOfAKind` is true)
|
|
144
|
+
#### Three of a Kind Categorization (only true if `isThreeOfAKind` is true in strict mode)
|
|
114
145
|
- `isTopThreeOfAKind` - Three of a kind is the highest rank
|
|
115
146
|
- `isMiddleThreeOfAKind` - Three of a kind is neither highest nor lowest
|
|
116
147
|
- `isBottomThreeOfAKind` - Three of a kind is the lowest rank
|
|
@@ -128,11 +159,15 @@ The `evaluateHand()` function returns an object where:
|
|
|
128
159
|
- `isInsideStraightDrawWheel` - Inside draw using wheel ranking
|
|
129
160
|
|
|
130
161
|
#### Card Arrays
|
|
131
|
-
- `keyCards` -
|
|
132
|
-
- `kickerCards` -
|
|
162
|
+
- `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
|
+
- `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
|
+
|
|
165
|
+
**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`.
|
|
133
166
|
|
|
134
167
|
### Example Response
|
|
135
168
|
|
|
169
|
+
**Example 1: Royal Flush (strict mode - default)**
|
|
170
|
+
|
|
136
171
|
```javascript
|
|
137
172
|
{
|
|
138
173
|
"As, Ks, Qs, Js, Ts": {
|
|
@@ -141,10 +176,10 @@ The `evaluateHand()` function returns an object where:
|
|
|
141
176
|
"isThreeOfAKind": false,
|
|
142
177
|
"isTwoPair": false,
|
|
143
178
|
"isPair": false,
|
|
144
|
-
"isFlush":
|
|
145
|
-
"isStraight":
|
|
179
|
+
"isFlush": false, // Filtered out in strict mode (royal flush is stronger)
|
|
180
|
+
"isStraight": false, // Filtered out in strict mode (royal flush is stronger)
|
|
146
181
|
"isRoyalFlush": true,
|
|
147
|
-
"isStraightFlush":
|
|
182
|
+
"isStraightFlush": false, // Filtered out in strict mode (royal flush is stronger)
|
|
148
183
|
"isTopPair": false,
|
|
149
184
|
"isMiddlePair": false,
|
|
150
185
|
"isBottomPair": false,
|
|
@@ -160,8 +195,109 @@ The `evaluateHand()` function returns an object where:
|
|
|
160
195
|
"isInsideStraightDraw": false,
|
|
161
196
|
"isStraightDraw": false,
|
|
162
197
|
"isHighCard": false,
|
|
163
|
-
"keyCards":
|
|
164
|
-
|
|
198
|
+
"keyCards": {
|
|
199
|
+
"isRoyalFlush": ["As", "Ks", "Qs", "Js", "Ts"],
|
|
200
|
+
"isStraightFlush": [],
|
|
201
|
+
"isFourOfAKind": [],
|
|
202
|
+
"isFullHouse": [],
|
|
203
|
+
"isFlush": [], // Empty in strict mode (royal flush is stronger)
|
|
204
|
+
"isStraight": [], // Empty in strict mode (royal flush is stronger)
|
|
205
|
+
"isThreeOfAKind": [],
|
|
206
|
+
"isTwoPair": [],
|
|
207
|
+
"isPair": [],
|
|
208
|
+
"isTopPair": [],
|
|
209
|
+
"isMiddlePair": [],
|
|
210
|
+
"isBottomPair": [],
|
|
211
|
+
"isTopAndMiddlePair": [],
|
|
212
|
+
"isTopAndBottomPair": [],
|
|
213
|
+
"isMiddleAndBottomPair": [],
|
|
214
|
+
"isTopThreeOfAKind": [],
|
|
215
|
+
"isMiddleThreeOfAKind": [],
|
|
216
|
+
"isBottomThreeOfAKind": [],
|
|
217
|
+
"isFlushDraw": [],
|
|
218
|
+
"isBackdoorFlushDraw": [],
|
|
219
|
+
"isOpenEndedStraightDraw": [],
|
|
220
|
+
"isInsideStraightDraw": [],
|
|
221
|
+
"isStraightDraw": [],
|
|
222
|
+
"isStraightWheel": [],
|
|
223
|
+
"isOpenEndedStraightDrawWheel": [],
|
|
224
|
+
"isInsideStraightDrawWheel": [],
|
|
225
|
+
"isHighCard": []
|
|
226
|
+
},
|
|
227
|
+
"kickerCards": {
|
|
228
|
+
"isRoyalFlush": [],
|
|
229
|
+
"isStraightFlush": [],
|
|
230
|
+
"isFourOfAKind": [],
|
|
231
|
+
"isFullHouse": [],
|
|
232
|
+
"isFlush": [],
|
|
233
|
+
"isStraight": [],
|
|
234
|
+
"isThreeOfAKind": [],
|
|
235
|
+
"isTwoPair": [],
|
|
236
|
+
"isPair": [],
|
|
237
|
+
"isTopPair": [],
|
|
238
|
+
"isMiddlePair": [],
|
|
239
|
+
"isBottomPair": [],
|
|
240
|
+
"isTopAndMiddlePair": [],
|
|
241
|
+
"isTopAndBottomPair": [],
|
|
242
|
+
"isMiddleAndBottomPair": [],
|
|
243
|
+
"isTopThreeOfAKind": [],
|
|
244
|
+
"isMiddleThreeOfAKind": [],
|
|
245
|
+
"isBottomThreeOfAKind": [],
|
|
246
|
+
"isFlushDraw": [],
|
|
247
|
+
"isBackdoorFlushDraw": [],
|
|
248
|
+
"isOpenEndedStraightDraw": [],
|
|
249
|
+
"isInsideStraightDraw": [],
|
|
250
|
+
"isStraightDraw": [],
|
|
251
|
+
"isStraightWheel": [],
|
|
252
|
+
"isOpenEndedStraightDrawWheel": [],
|
|
253
|
+
"isInsideStraightDrawWheel": [],
|
|
254
|
+
"isHighCard": []
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Example 2: Pair with Flush Draw (strict mode - default)**
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
{
|
|
264
|
+
"As, Ah, Ks, Qs, Js": {
|
|
265
|
+
"isPair": true,
|
|
266
|
+
"isFlushDraw": true, // Draws can coexist with made hands in strict mode
|
|
267
|
+
// ... other boolean properties ...
|
|
268
|
+
"keyCards": {
|
|
269
|
+
"isPair": ["As", "Ah"],
|
|
270
|
+
"isFlushDraw": ["As", "Ks", "Qs", "Js"],
|
|
271
|
+
// ... other hand types with empty arrays ...
|
|
272
|
+
},
|
|
273
|
+
"kickerCards": {
|
|
274
|
+
"isPair": ["Ks", "Qs", "Js"],
|
|
275
|
+
"isFlushDraw": ["Ah"],
|
|
276
|
+
// ... other hand types with empty arrays ...
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Example 3: Two Pair (strict mode - default)**
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
{
|
|
286
|
+
"As, Ah, Kd, Kc, Qs": {
|
|
287
|
+
"isTwoPair": true,
|
|
288
|
+
"isPair": false, // Filtered out in strict mode (two pair is stronger)
|
|
289
|
+
"isTopAndMiddlePair": true,
|
|
290
|
+
// ... other boolean properties ...
|
|
291
|
+
"keyCards": {
|
|
292
|
+
"isTwoPair": ["As", "Ah", "Kd", "Kc"],
|
|
293
|
+
"isPair": [], // Empty in strict mode
|
|
294
|
+
// ... other hand types with empty arrays ...
|
|
295
|
+
},
|
|
296
|
+
"kickerCards": {
|
|
297
|
+
"isTwoPair": ["Qs"],
|
|
298
|
+
"isPair": [], // Empty in strict mode
|
|
299
|
+
// ... other hand types with empty arrays ...
|
|
300
|
+
}
|
|
165
301
|
}
|
|
166
302
|
}
|
|
167
303
|
```
|
|
@@ -244,12 +380,13 @@ The test suite includes 572+ tests covering:
|
|
|
244
380
|
|
|
245
381
|
## API Reference
|
|
246
382
|
|
|
247
|
-
### `evaluateHand(cards)`
|
|
383
|
+
### `evaluateHand(cards, strict)`
|
|
248
384
|
|
|
249
385
|
Main function to evaluate poker hands.
|
|
250
386
|
|
|
251
387
|
**Parameters:**
|
|
252
388
|
- `cards` (string[]): Array of 5 or more card strings
|
|
389
|
+
- `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`).
|
|
253
390
|
|
|
254
391
|
**Returns:**
|
|
255
392
|
- Object mapping sorted hand strings to evaluation objects
|
|
@@ -262,17 +399,19 @@ Main function to evaluate poker hands.
|
|
|
262
399
|
#### Constructor
|
|
263
400
|
|
|
264
401
|
```javascript
|
|
265
|
-
new HandEvaluator(cards)
|
|
402
|
+
new HandEvaluator(cards, strict)
|
|
266
403
|
```
|
|
267
404
|
|
|
268
405
|
Creates a new HandEvaluator instance.
|
|
269
406
|
|
|
270
407
|
**Parameters:**
|
|
271
408
|
- `cards` (string[]): Array of 5 or more card strings
|
|
409
|
+
- `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`).
|
|
272
410
|
|
|
273
411
|
**Properties:**
|
|
274
412
|
- `evaluation` - Object mapping sorted hand strings to evaluation objects (same format as `evaluateHand()` return value)
|
|
275
413
|
- `cards` - Array of input cards
|
|
414
|
+
- `strict` - Boolean indicating whether strict mode is enabled
|
|
276
415
|
|
|
277
416
|
**Note**: All internal methods are private. Use the `evaluation` property to access evaluation results.
|
|
278
417
|
|
package/evaluator.js
CHANGED
|
@@ -26,14 +26,23 @@ class HandEvaluator {
|
|
|
26
26
|
* @param {string[]} cards - Array of 5 or more card strings in format "Rs" where:
|
|
27
27
|
* - R is rank: A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2
|
|
28
28
|
* - s is suit: s (spades), h (hearts), d (diamonds), c (clubs)
|
|
29
|
+
* @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
|
|
30
|
+
* If false, all applicable hand types are marked (current behavior).
|
|
29
31
|
* @throws {Error} If cards is not an array, has fewer than 5 cards, contains invalid format,
|
|
30
32
|
* invalid rank, invalid suit, or duplicate cards
|
|
31
33
|
* @example
|
|
32
34
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
|
|
35
|
+
* @example
|
|
36
|
+
* // Strict mode (default) - only strongest hand marked
|
|
37
|
+
* const strictEvaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs'], true);
|
|
38
|
+
* @example
|
|
39
|
+
* // Non-strict mode - all applicable hands marked
|
|
40
|
+
* const nonStrictEvaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs'], false);
|
|
33
41
|
*/
|
|
34
|
-
constructor(cards) {
|
|
42
|
+
constructor(cards, strict = true) {
|
|
35
43
|
this.#validateCards(cards);
|
|
36
44
|
this.cards = cards;
|
|
45
|
+
this.strict = strict;
|
|
37
46
|
this.evaluation = this.#evaluateAllCombinations();
|
|
38
47
|
}
|
|
39
48
|
|
|
@@ -194,8 +203,13 @@ class HandEvaluator {
|
|
|
194
203
|
// Add keyCards and kickerCards before restoring original evaluation
|
|
195
204
|
// (these methods need access to this.evaluation)
|
|
196
205
|
const evaluation = this.evaluation;
|
|
197
|
-
evaluation.keyCards = this.#
|
|
198
|
-
evaluation.kickerCards = this.#
|
|
206
|
+
evaluation.keyCards = this.#buildKeyCardsObject();
|
|
207
|
+
evaluation.kickerCards = this.#buildKickerCardsObject(evaluation.keyCards);
|
|
208
|
+
|
|
209
|
+
// Apply strict mode filtering if enabled
|
|
210
|
+
if (this.strict) {
|
|
211
|
+
this.#applyStrictMode(evaluation);
|
|
212
|
+
}
|
|
199
213
|
|
|
200
214
|
// Restore original evaluation
|
|
201
215
|
this.evaluation = originalEvaluation;
|
|
@@ -203,6 +217,64 @@ class HandEvaluator {
|
|
|
203
217
|
return evaluation;
|
|
204
218
|
}
|
|
205
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Applies strict mode filtering to evaluation results.
|
|
222
|
+
* Only the strongest made hand and stronger draws are kept as true.
|
|
223
|
+
*
|
|
224
|
+
* @private
|
|
225
|
+
* @param {Object} evaluation - The evaluation object to filter
|
|
226
|
+
*/
|
|
227
|
+
#applyStrictMode(evaluation) {
|
|
228
|
+
// Hand strength lookup (strongest = highest number)
|
|
229
|
+
const handStrength = {
|
|
230
|
+
isRoyalFlush: 10,
|
|
231
|
+
isStraightFlush: 9,
|
|
232
|
+
isFourOfAKind: 8,
|
|
233
|
+
isFullHouse: 7,
|
|
234
|
+
isFlush: 6,
|
|
235
|
+
isStraight: 5,
|
|
236
|
+
isThreeOfAKind: 4,
|
|
237
|
+
isTwoPair: 3,
|
|
238
|
+
isPair: 2,
|
|
239
|
+
isHighCard: 1
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Find strongest made hand
|
|
243
|
+
let strongest = 0;
|
|
244
|
+
for (const [hand, strength] of Object.entries(handStrength)) {
|
|
245
|
+
if (evaluation[hand]) strongest = Math.max(strongest, strength);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Disable weaker made hands in single pass
|
|
249
|
+
for (const [hand, strength] of Object.entries(handStrength)) {
|
|
250
|
+
if (strength < strongest) evaluation[hand] = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Disable descriptive labels if not matching strongest
|
|
254
|
+
if (strongest !== 2) {
|
|
255
|
+
evaluation.isTopPair = false;
|
|
256
|
+
evaluation.isMiddlePair = false;
|
|
257
|
+
evaluation.isBottomPair = false;
|
|
258
|
+
}
|
|
259
|
+
if (strongest !== 3) {
|
|
260
|
+
evaluation.isTopAndMiddlePair = false;
|
|
261
|
+
evaluation.isTopAndBottomPair = false;
|
|
262
|
+
evaluation.isMiddleAndBottomPair = false;
|
|
263
|
+
}
|
|
264
|
+
if (strongest !== 4) {
|
|
265
|
+
evaluation.isTopThreeOfAKind = false;
|
|
266
|
+
evaluation.isMiddleThreeOfAKind = false;
|
|
267
|
+
evaluation.isBottomThreeOfAKind = false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Filter draws: made flush or stronger cannot have straight draws
|
|
271
|
+
if (strongest >= 6) {
|
|
272
|
+
evaluation.isStraightDraw = false;
|
|
273
|
+
evaluation.isOpenEndedStraightDraw = false;
|
|
274
|
+
evaluation.isInsideStraightDraw = false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
206
278
|
/**
|
|
207
279
|
* Evaluates all possible 5-card combinations from the input cards.
|
|
208
280
|
* For exactly 5 cards, evaluates directly. For more cards, generates all combinations
|
|
@@ -971,6 +1043,390 @@ class HandEvaluator {
|
|
|
971
1043
|
return [];
|
|
972
1044
|
}
|
|
973
1045
|
|
|
1046
|
+
// ==================== KEY CARDS EXTRACTION BY HAND TYPE ====================
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Gets key cards for royal flush (all 5 cards).
|
|
1050
|
+
*
|
|
1051
|
+
* @private
|
|
1052
|
+
* @returns {string[]} Array of 5 cards if royal flush, empty array otherwise
|
|
1053
|
+
*/
|
|
1054
|
+
#getKeyCardsForRoyalFlush() {
|
|
1055
|
+
if (!this.evaluation.isRoyalFlush) return [];
|
|
1056
|
+
return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Gets key cards for straight flush (all 5 cards).
|
|
1061
|
+
*
|
|
1062
|
+
* @private
|
|
1063
|
+
* @returns {string[]} Array of 5 cards if straight flush, empty array otherwise
|
|
1064
|
+
*/
|
|
1065
|
+
#getKeyCardsForStraightFlush() {
|
|
1066
|
+
if (!this.evaluation.isStraightFlush) return [];
|
|
1067
|
+
return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Gets key cards for four of a kind (4 cards of same rank).
|
|
1072
|
+
*
|
|
1073
|
+
* @private
|
|
1074
|
+
* @returns {string[]} Array of 4 cards if four of a kind, empty array otherwise
|
|
1075
|
+
*/
|
|
1076
|
+
#getKeyCardsForFourOfAKind() {
|
|
1077
|
+
if (!this.evaluation.isFourOfAKind) return [];
|
|
1078
|
+
const rankCounts = this.#getRankCounts();
|
|
1079
|
+
const fourRank = Object.keys(rankCounts).find(r => rankCounts[r] === 4);
|
|
1080
|
+
return this.cards.filter(c => c.slice(0, -1) === fourRank)
|
|
1081
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Gets key cards for full house (all 5 cards).
|
|
1086
|
+
*
|
|
1087
|
+
* @private
|
|
1088
|
+
* @returns {string[]} Array of 5 cards if full house, empty array otherwise
|
|
1089
|
+
*/
|
|
1090
|
+
#getKeyCardsForFullHouse() {
|
|
1091
|
+
if (!this.evaluation.isFullHouse) return [];
|
|
1092
|
+
return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Gets key cards for flush (all 5 cards of same suit).
|
|
1097
|
+
*
|
|
1098
|
+
* @private
|
|
1099
|
+
* @returns {string[]} Array of 5 cards if flush, empty array otherwise
|
|
1100
|
+
*/
|
|
1101
|
+
#getKeyCardsForFlush() {
|
|
1102
|
+
if (!this.evaluation.isFlush) return [];
|
|
1103
|
+
const suits = this.#countSuits();
|
|
1104
|
+
const flushSuit = Object.keys(suits).find(s => suits[s] === 5);
|
|
1105
|
+
return this.cards.filter(c => c.slice(-1) === flushSuit)
|
|
1106
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Gets key cards for straight (5 cards forming straight).
|
|
1111
|
+
*
|
|
1112
|
+
* @private
|
|
1113
|
+
* @returns {string[]} Array of 5 cards if straight, empty array otherwise
|
|
1114
|
+
*/
|
|
1115
|
+
#getKeyCardsForStraight() {
|
|
1116
|
+
if (!this.evaluation.isStraight) return [];
|
|
1117
|
+
return this.#getStraightCards();
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Gets key cards for three of a kind (3 cards of same rank).
|
|
1122
|
+
*
|
|
1123
|
+
* @private
|
|
1124
|
+
* @returns {string[]} Array of 3 cards if three of a kind, empty array otherwise
|
|
1125
|
+
*/
|
|
1126
|
+
#getKeyCardsForThreeOfAKind() {
|
|
1127
|
+
if (!this.evaluation.isThreeOfAKind) return [];
|
|
1128
|
+
const rankCounts = this.#getRankCounts();
|
|
1129
|
+
const threeRank = Object.keys(rankCounts).find(r => rankCounts[r] === 3);
|
|
1130
|
+
return this.cards.filter(c => c.slice(0, -1) === threeRank)
|
|
1131
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Gets key cards for two pair (4 cards forming 2 pairs).
|
|
1136
|
+
*
|
|
1137
|
+
* @private
|
|
1138
|
+
* @returns {string[]} Array of 4 cards if two pair, empty array otherwise
|
|
1139
|
+
*/
|
|
1140
|
+
#getKeyCardsForTwoPair() {
|
|
1141
|
+
if (!this.evaluation.isTwoPair) return [];
|
|
1142
|
+
const rankCounts = this.#getRankCounts();
|
|
1143
|
+
const pairRanks = Object.keys(rankCounts).filter(r => rankCounts[r] === 2);
|
|
1144
|
+
return this.cards.filter(c => pairRanks.includes(c.slice(0, -1)))
|
|
1145
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Gets key cards for pair (2 cards of same rank).
|
|
1150
|
+
*
|
|
1151
|
+
* @private
|
|
1152
|
+
* @returns {string[]} Array of 2 cards if pair, empty array otherwise
|
|
1153
|
+
*/
|
|
1154
|
+
#getKeyCardsForPair() {
|
|
1155
|
+
if (!this.evaluation.isPair) return [];
|
|
1156
|
+
const rankCounts = this.#getRankCounts();
|
|
1157
|
+
const pairRank = Object.keys(rankCounts).find(r => rankCounts[r] === 2);
|
|
1158
|
+
return this.cards.filter(c => c.slice(0, -1) === pairRank)
|
|
1159
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Gets key cards for top pair (2 cards if top pair).
|
|
1164
|
+
*
|
|
1165
|
+
* @private
|
|
1166
|
+
* @returns {string[]} Array of 2 cards if top pair, empty array otherwise
|
|
1167
|
+
*/
|
|
1168
|
+
#getKeyCardsForTopPair() {
|
|
1169
|
+
if (!this.evaluation.isTopPair) return [];
|
|
1170
|
+
return this.#getKeyCardsForPair();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Gets key cards for middle pair (2 cards if middle pair).
|
|
1175
|
+
*
|
|
1176
|
+
* @private
|
|
1177
|
+
* @returns {string[]} Array of 2 cards if middle pair, empty array otherwise
|
|
1178
|
+
*/
|
|
1179
|
+
#getKeyCardsForMiddlePair() {
|
|
1180
|
+
if (!this.evaluation.isMiddlePair) return [];
|
|
1181
|
+
return this.#getKeyCardsForPair();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Gets key cards for bottom pair (2 cards if bottom pair).
|
|
1186
|
+
*
|
|
1187
|
+
* @private
|
|
1188
|
+
* @returns {string[]} Array of 2 cards if bottom pair, empty array otherwise
|
|
1189
|
+
*/
|
|
1190
|
+
#getKeyCardsForBottomPair() {
|
|
1191
|
+
if (!this.evaluation.isBottomPair) return [];
|
|
1192
|
+
return this.#getKeyCardsForPair();
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Gets key cards for top and middle pair (4 cards if top and middle pair).
|
|
1197
|
+
*
|
|
1198
|
+
* @private
|
|
1199
|
+
* @returns {string[]} Array of 4 cards if top and middle pair, empty array otherwise
|
|
1200
|
+
*/
|
|
1201
|
+
#getKeyCardsForTopAndMiddlePair() {
|
|
1202
|
+
if (!this.evaluation.isTopAndMiddlePair) return [];
|
|
1203
|
+
return this.#getKeyCardsForTwoPair();
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Gets key cards for top and bottom pair (4 cards if top and bottom pair).
|
|
1208
|
+
*
|
|
1209
|
+
* @private
|
|
1210
|
+
* @returns {string[]} Array of 4 cards if top and bottom pair, empty array otherwise
|
|
1211
|
+
*/
|
|
1212
|
+
#getKeyCardsForTopAndBottomPair() {
|
|
1213
|
+
if (!this.evaluation.isTopAndBottomPair) return [];
|
|
1214
|
+
return this.#getKeyCardsForTwoPair();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Gets key cards for middle and bottom pair (4 cards if middle and bottom pair).
|
|
1219
|
+
*
|
|
1220
|
+
* @private
|
|
1221
|
+
* @returns {string[]} Array of 4 cards if middle and bottom pair, empty array otherwise
|
|
1222
|
+
*/
|
|
1223
|
+
#getKeyCardsForMiddleAndBottomPair() {
|
|
1224
|
+
if (!this.evaluation.isMiddleAndBottomPair) return [];
|
|
1225
|
+
return this.#getKeyCardsForTwoPair();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Gets key cards for top three of a kind (3 cards if top three).
|
|
1230
|
+
*
|
|
1231
|
+
* @private
|
|
1232
|
+
* @returns {string[]} Array of 3 cards if top three of a kind, empty array otherwise
|
|
1233
|
+
*/
|
|
1234
|
+
#getKeyCardsForTopThreeOfAKind() {
|
|
1235
|
+
if (!this.evaluation.isTopThreeOfAKind) return [];
|
|
1236
|
+
return this.#getKeyCardsForThreeOfAKind();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Gets key cards for middle three of a kind (3 cards if middle three).
|
|
1241
|
+
*
|
|
1242
|
+
* @private
|
|
1243
|
+
* @returns {string[]} Array of 3 cards if middle three of a kind, empty array otherwise
|
|
1244
|
+
*/
|
|
1245
|
+
#getKeyCardsForMiddleThreeOfAKind() {
|
|
1246
|
+
if (!this.evaluation.isMiddleThreeOfAKind) return [];
|
|
1247
|
+
return this.#getKeyCardsForThreeOfAKind();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Gets key cards for bottom three of a kind (3 cards if bottom three).
|
|
1252
|
+
*
|
|
1253
|
+
* @private
|
|
1254
|
+
* @returns {string[]} Array of 3 cards if bottom three of a kind, empty array otherwise
|
|
1255
|
+
*/
|
|
1256
|
+
#getKeyCardsForBottomThreeOfAKind() {
|
|
1257
|
+
if (!this.evaluation.isBottomThreeOfAKind) return [];
|
|
1258
|
+
return this.#getKeyCardsForThreeOfAKind();
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Gets key cards for flush draw (4 cards of same suit).
|
|
1263
|
+
*
|
|
1264
|
+
* @private
|
|
1265
|
+
* @returns {string[]} Array of 4 cards if flush draw, empty array otherwise
|
|
1266
|
+
*/
|
|
1267
|
+
#getKeyCardsForFlushDraw() {
|
|
1268
|
+
if (!this.evaluation.isFlushDraw) return [];
|
|
1269
|
+
const suits = this.#countSuits();
|
|
1270
|
+
const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
|
|
1271
|
+
return this.cards.filter(c => c.slice(-1) === flushSuit)
|
|
1272
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Gets key cards for backdoor flush draw (3 cards of same suit).
|
|
1277
|
+
*
|
|
1278
|
+
* @private
|
|
1279
|
+
* @returns {string[]} Array of 3 cards if backdoor flush draw, empty array otherwise
|
|
1280
|
+
*/
|
|
1281
|
+
#getKeyCardsForBackdoorFlushDraw() {
|
|
1282
|
+
if (!this.evaluation.isBackdoorFlushDraw) return [];
|
|
1283
|
+
const suits = this.#countSuits();
|
|
1284
|
+
const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
|
|
1285
|
+
return this.cards.filter(c => c.slice(-1) === flushSuit)
|
|
1286
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Gets key cards for open-ended straight draw (4 cards forming draw).
|
|
1291
|
+
*
|
|
1292
|
+
* @private
|
|
1293
|
+
* @returns {string[]} Array of 4 cards if open-ended straight draw, empty array otherwise
|
|
1294
|
+
*/
|
|
1295
|
+
#getKeyCardsForOpenEndedStraightDraw() {
|
|
1296
|
+
if (!this.evaluation.isOpenEndedStraightDraw) return [];
|
|
1297
|
+
return this.#getStraightDrawCards();
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Gets key cards for inside straight draw (4 cards forming draw).
|
|
1302
|
+
*
|
|
1303
|
+
* @private
|
|
1304
|
+
* @returns {string[]} Array of 4 cards if inside straight draw, empty array otherwise
|
|
1305
|
+
*/
|
|
1306
|
+
#getKeyCardsForInsideStraightDraw() {
|
|
1307
|
+
if (!this.evaluation.isInsideStraightDraw) return [];
|
|
1308
|
+
return this.#getStraightDrawCards();
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Gets key cards for straight draw (4 cards forming any straight draw).
|
|
1313
|
+
*
|
|
1314
|
+
* @private
|
|
1315
|
+
* @returns {string[]} Array of 4 cards if straight draw, empty array otherwise
|
|
1316
|
+
*/
|
|
1317
|
+
#getKeyCardsForStraightDraw() {
|
|
1318
|
+
if (!this.evaluation.isStraightDraw) return [];
|
|
1319
|
+
return this.#getStraightDrawCards();
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Gets key cards for wheel straight (5 cards forming wheel).
|
|
1324
|
+
*
|
|
1325
|
+
* @private
|
|
1326
|
+
* @returns {string[]} Array of 5 cards if wheel straight, empty array otherwise
|
|
1327
|
+
*/
|
|
1328
|
+
#getKeyCardsForStraightWheel() {
|
|
1329
|
+
if (!this.evaluation.isStraightWheel) return [];
|
|
1330
|
+
return this.#getStraightCards();
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Gets key cards for open-ended straight draw wheel (4 cards forming wheel draw).
|
|
1335
|
+
*
|
|
1336
|
+
* @private
|
|
1337
|
+
* @returns {string[]} Array of 4 cards if wheel open-ended draw, empty array otherwise
|
|
1338
|
+
*/
|
|
1339
|
+
#getKeyCardsForOpenEndedStraightDrawWheel() {
|
|
1340
|
+
if (!this.evaluation.isOpenEndedStraightDrawWheel) return [];
|
|
1341
|
+
return this.#getStraightDrawCards();
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* Gets key cards for inside straight draw wheel (4 cards forming wheel draw).
|
|
1346
|
+
*
|
|
1347
|
+
* @private
|
|
1348
|
+
* @returns {string[]} Array of 4 cards if wheel inside draw, empty array otherwise
|
|
1349
|
+
*/
|
|
1350
|
+
#getKeyCardsForInsideStraightDrawWheel() {
|
|
1351
|
+
if (!this.evaluation.isInsideStraightDrawWheel) return [];
|
|
1352
|
+
return this.#getStraightDrawCards();
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Gets key cards for high card (empty array).
|
|
1357
|
+
*
|
|
1358
|
+
* @private
|
|
1359
|
+
* @returns {string[]} Always returns empty array
|
|
1360
|
+
*/
|
|
1361
|
+
#getKeyCardsForHighCard() {
|
|
1362
|
+
return [];
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Builds the keyCards object with entries for all hand types.
|
|
1367
|
+
*
|
|
1368
|
+
* @private
|
|
1369
|
+
* @returns {Object} Object with keys for all hand types, values are arrays of key cards
|
|
1370
|
+
*/
|
|
1371
|
+
#buildKeyCardsObject() {
|
|
1372
|
+
return {
|
|
1373
|
+
isRoyalFlush: this.#getKeyCardsForRoyalFlush(),
|
|
1374
|
+
isStraightFlush: this.#getKeyCardsForStraightFlush(),
|
|
1375
|
+
isFourOfAKind: this.#getKeyCardsForFourOfAKind(),
|
|
1376
|
+
isFullHouse: this.#getKeyCardsForFullHouse(),
|
|
1377
|
+
isFlush: this.#getKeyCardsForFlush(),
|
|
1378
|
+
isStraight: this.#getKeyCardsForStraight(),
|
|
1379
|
+
isThreeOfAKind: this.#getKeyCardsForThreeOfAKind(),
|
|
1380
|
+
isTwoPair: this.#getKeyCardsForTwoPair(),
|
|
1381
|
+
isPair: this.#getKeyCardsForPair(),
|
|
1382
|
+
isTopPair: this.#getKeyCardsForTopPair(),
|
|
1383
|
+
isMiddlePair: this.#getKeyCardsForMiddlePair(),
|
|
1384
|
+
isBottomPair: this.#getKeyCardsForBottomPair(),
|
|
1385
|
+
isTopAndMiddlePair: this.#getKeyCardsForTopAndMiddlePair(),
|
|
1386
|
+
isTopAndBottomPair: this.#getKeyCardsForTopAndBottomPair(),
|
|
1387
|
+
isMiddleAndBottomPair: this.#getKeyCardsForMiddleAndBottomPair(),
|
|
1388
|
+
isTopThreeOfAKind: this.#getKeyCardsForTopThreeOfAKind(),
|
|
1389
|
+
isMiddleThreeOfAKind: this.#getKeyCardsForMiddleThreeOfAKind(),
|
|
1390
|
+
isBottomThreeOfAKind: this.#getKeyCardsForBottomThreeOfAKind(),
|
|
1391
|
+
isFlushDraw: this.#getKeyCardsForFlushDraw(),
|
|
1392
|
+
isBackdoorFlushDraw: this.#getKeyCardsForBackdoorFlushDraw(),
|
|
1393
|
+
isOpenEndedStraightDraw: this.#getKeyCardsForOpenEndedStraightDraw(),
|
|
1394
|
+
isInsideStraightDraw: this.#getKeyCardsForInsideStraightDraw(),
|
|
1395
|
+
isStraightDraw: this.#getKeyCardsForStraightDraw(),
|
|
1396
|
+
isStraightWheel: this.#getKeyCardsForStraightWheel(),
|
|
1397
|
+
isOpenEndedStraightDrawWheel: this.#getKeyCardsForOpenEndedStraightDrawWheel(),
|
|
1398
|
+
isInsideStraightDrawWheel: this.#getKeyCardsForInsideStraightDrawWheel(),
|
|
1399
|
+
isHighCard: this.#getKeyCardsForHighCard()
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Builds the kickerCards object with entries for all hand types.
|
|
1405
|
+
*
|
|
1406
|
+
* @private
|
|
1407
|
+
* @param {Object} keyCardsObj - The keyCards object
|
|
1408
|
+
* @returns {Object} Object with keys for all hand types, values are arrays of kicker cards
|
|
1409
|
+
*/
|
|
1410
|
+
#buildKickerCardsObject(keyCardsObj) {
|
|
1411
|
+
const sortedCards = [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1412
|
+
const result = {};
|
|
1413
|
+
|
|
1414
|
+
for (const [handType, keyCards] of Object.entries(keyCardsObj)) {
|
|
1415
|
+
if (handType === 'isHighCard') {
|
|
1416
|
+
result[handType] = sortedCards;
|
|
1417
|
+
} else if (keyCards.length === 0) {
|
|
1418
|
+
result[handType] = [];
|
|
1419
|
+
} else {
|
|
1420
|
+
const keyCardsSet = new Set(keyCards);
|
|
1421
|
+
result[handType] = this.cards
|
|
1422
|
+
.filter(c => !keyCardsSet.has(c))
|
|
1423
|
+
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
return result;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
974
1430
|
/**
|
|
975
1431
|
* Helper method to extract the 4 cards that form a straight draw.
|
|
976
1432
|
* Handles both standard and wheel straight draws.
|
|
@@ -1010,107 +1466,6 @@ class HandEvaluator {
|
|
|
1010
1466
|
return [];
|
|
1011
1467
|
}
|
|
1012
1468
|
|
|
1013
|
-
/**
|
|
1014
|
-
* Determines the primary made hand or draw and extracts the key cards.
|
|
1015
|
-
* Assumes cards are already sorted and evaluation is complete.
|
|
1016
|
-
*
|
|
1017
|
-
* @private
|
|
1018
|
-
* @returns {string[]} Array of key cards, sorted from highest to lowest rank
|
|
1019
|
-
*/
|
|
1020
|
-
#getKeyCards() {
|
|
1021
|
-
// Ensure cards are sorted once (they likely already are from evaluation)
|
|
1022
|
-
const sortedCards = [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1023
|
-
|
|
1024
|
-
// Calculate these once and reuse
|
|
1025
|
-
const rankCounts = this.#getRankCounts();
|
|
1026
|
-
const suits = this.#countSuits();
|
|
1027
|
-
|
|
1028
|
-
// Use evaluation results directly - no re-checking
|
|
1029
|
-
// Check made hands in priority order
|
|
1030
|
-
if (this.evaluation.isRoyalFlush || this.evaluation.isStraightFlush ||
|
|
1031
|
-
this.evaluation.isFullHouse) {
|
|
1032
|
-
return sortedCards; // All 5 cards are key cards
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
if (this.evaluation.isStraight) {
|
|
1036
|
-
return this.#getStraightCards();
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (this.evaluation.isFourOfAKind) {
|
|
1040
|
-
const fourRank = Object.keys(rankCounts).find(r => rankCounts[r] === 4);
|
|
1041
|
-
return sortedCards.filter(c => c.slice(0, -1) === fourRank)
|
|
1042
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
if (this.evaluation.isFlush) {
|
|
1046
|
-
const flushSuit = Object.keys(suits).find(s => suits[s] === 5);
|
|
1047
|
-
return sortedCards.filter(c => c.slice(-1) === flushSuit)
|
|
1048
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
if (this.evaluation.isThreeOfAKind) {
|
|
1052
|
-
const threeRank = Object.keys(rankCounts).find(r => rankCounts[r] === 3);
|
|
1053
|
-
return sortedCards.filter(c => c.slice(0, -1) === threeRank)
|
|
1054
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
if (this.evaluation.isTwoPair) {
|
|
1058
|
-
const pairRanks = Object.keys(rankCounts).filter(r => rankCounts[r] === 2);
|
|
1059
|
-
return sortedCards.filter(c => pairRanks.includes(c.slice(0, -1)))
|
|
1060
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
if (this.evaluation.isPair) {
|
|
1064
|
-
const pairRank = Object.keys(rankCounts).find(r => rankCounts[r] === 2);
|
|
1065
|
-
return sortedCards.filter(c => c.slice(0, -1) === pairRank)
|
|
1066
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// Check draws (only if no made hand)
|
|
1070
|
-
if (this.evaluation.isFlushDraw) {
|
|
1071
|
-
const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
|
|
1072
|
-
return sortedCards.filter(c => c.slice(-1) === flushSuit)
|
|
1073
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
if (this.evaluation.isBackdoorFlushDraw) {
|
|
1077
|
-
const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
|
|
1078
|
-
return sortedCards.filter(c => c.slice(-1) === flushSuit)
|
|
1079
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
if (this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw) {
|
|
1083
|
-
const drawCards = this.#getStraightDrawCards();
|
|
1084
|
-
// Only return if we actually found draw cards (safety check)
|
|
1085
|
-
if (drawCards.length === 4) {
|
|
1086
|
-
return drawCards;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// High card or no hand/draw
|
|
1091
|
-
return [];
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* Gets the kicker cards (cards not in the made hand or draw).
|
|
1096
|
-
*
|
|
1097
|
-
* @private
|
|
1098
|
-
* @returns {string[]} Array of kicker cards, sorted from highest to lowest rank
|
|
1099
|
-
*/
|
|
1100
|
-
#getKickerCards() {
|
|
1101
|
-
const keyCards = this.#getKeyCards();
|
|
1102
|
-
if (keyCards.length === 0) {
|
|
1103
|
-
// All cards are kickers (high card)
|
|
1104
|
-
return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Use Set for O(1) lookup
|
|
1108
|
-
const keyCardsSet = new Set(keyCards);
|
|
1109
|
-
return this.cards
|
|
1110
|
-
.filter(c => !keyCardsSet.has(c))
|
|
1111
|
-
.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
1469
|
// ==================== HIGH CARD ====================
|
|
1115
1470
|
|
|
1116
1471
|
/**
|
|
@@ -1140,10 +1495,12 @@ class HandEvaluator {
|
|
|
1140
1495
|
* Generates all 5-card combinations and evaluates each one.
|
|
1141
1496
|
*
|
|
1142
1497
|
* @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)
|
|
1498
|
+
* @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
|
|
1499
|
+
* If false, all applicable hand types are marked.
|
|
1143
1500
|
* @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
|
|
1144
1501
|
*/
|
|
1145
|
-
function evaluateHand(cards) {
|
|
1146
|
-
const evaluator = new HandEvaluator(cards);
|
|
1502
|
+
function evaluateHand(cards, strict = true) {
|
|
1503
|
+
const evaluator = new HandEvaluator(cards, strict);
|
|
1147
1504
|
return evaluator.evaluation;
|
|
1148
1505
|
}
|
|
1149
1506
|
|