poker-extval 3.0.0 → 3.1.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 +75 -16
- package/evaluator.js +195 -10
- 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
|
|
@@ -135,6 +166,8 @@ The `evaluateHand()` function returns an object where:
|
|
|
135
166
|
|
|
136
167
|
### Example Response
|
|
137
168
|
|
|
169
|
+
**Example 1: Royal Flush (strict mode - default)**
|
|
170
|
+
|
|
138
171
|
```javascript
|
|
139
172
|
{
|
|
140
173
|
"As, Ks, Qs, Js, Ts": {
|
|
@@ -143,10 +176,10 @@ The `evaluateHand()` function returns an object where:
|
|
|
143
176
|
"isThreeOfAKind": false,
|
|
144
177
|
"isTwoPair": false,
|
|
145
178
|
"isPair": false,
|
|
146
|
-
"isFlush":
|
|
147
|
-
"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)
|
|
148
181
|
"isRoyalFlush": true,
|
|
149
|
-
"isStraightFlush":
|
|
182
|
+
"isStraightFlush": false, // Filtered out in strict mode (royal flush is stronger)
|
|
150
183
|
"isTopPair": false,
|
|
151
184
|
"isMiddlePair": false,
|
|
152
185
|
"isBottomPair": false,
|
|
@@ -167,8 +200,8 @@ The `evaluateHand()` function returns an object where:
|
|
|
167
200
|
"isStraightFlush": [],
|
|
168
201
|
"isFourOfAKind": [],
|
|
169
202
|
"isFullHouse": [],
|
|
170
|
-
"isFlush": [
|
|
171
|
-
"isStraight": [
|
|
203
|
+
"isFlush": [], // Empty in strict mode (royal flush is stronger)
|
|
204
|
+
"isStraight": [], // Empty in strict mode (royal flush is stronger)
|
|
172
205
|
"isThreeOfAKind": [],
|
|
173
206
|
"isTwoPair": [],
|
|
174
207
|
"isPair": [],
|
|
@@ -224,13 +257,13 @@ The `evaluateHand()` function returns an object where:
|
|
|
224
257
|
}
|
|
225
258
|
```
|
|
226
259
|
|
|
227
|
-
**Example 2: Pair with Flush Draw**
|
|
260
|
+
**Example 2: Pair with Flush Draw (strict mode - default)**
|
|
228
261
|
|
|
229
262
|
```javascript
|
|
230
263
|
{
|
|
231
264
|
"As, Ah, Ks, Qs, Js": {
|
|
232
265
|
"isPair": true,
|
|
233
|
-
"isFlushDraw": true,
|
|
266
|
+
"isFlushDraw": true, // Draws can coexist with made hands in strict mode
|
|
234
267
|
// ... other boolean properties ...
|
|
235
268
|
"keyCards": {
|
|
236
269
|
"isPair": ["As", "Ah"],
|
|
@@ -246,6 +279,29 @@ The `evaluateHand()` function returns an object where:
|
|
|
246
279
|
}
|
|
247
280
|
```
|
|
248
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
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
249
305
|
## Performance Benchmarks
|
|
250
306
|
|
|
251
307
|
This library prioritizes **extensiveness and accuracy over raw speed**. The evaluation system is designed to provide comprehensive analysis of all possible hand combinations, which means:
|
|
@@ -324,12 +380,13 @@ The test suite includes 572+ tests covering:
|
|
|
324
380
|
|
|
325
381
|
## API Reference
|
|
326
382
|
|
|
327
|
-
### `evaluateHand(cards)`
|
|
383
|
+
### `evaluateHand(cards, strict)`
|
|
328
384
|
|
|
329
385
|
Main function to evaluate poker hands.
|
|
330
386
|
|
|
331
387
|
**Parameters:**
|
|
332
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`).
|
|
333
390
|
|
|
334
391
|
**Returns:**
|
|
335
392
|
- Object mapping sorted hand strings to evaluation objects
|
|
@@ -342,17 +399,19 @@ Main function to evaluate poker hands.
|
|
|
342
399
|
#### Constructor
|
|
343
400
|
|
|
344
401
|
```javascript
|
|
345
|
-
new HandEvaluator(cards)
|
|
402
|
+
new HandEvaluator(cards, strict)
|
|
346
403
|
```
|
|
347
404
|
|
|
348
405
|
Creates a new HandEvaluator instance.
|
|
349
406
|
|
|
350
407
|
**Parameters:**
|
|
351
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`).
|
|
352
410
|
|
|
353
411
|
**Properties:**
|
|
354
412
|
- `evaluation` - Object mapping sorted hand strings to evaluation objects (same format as `evaluateHand()` return value)
|
|
355
413
|
- `cards` - Array of input cards
|
|
414
|
+
- `strict` - Boolean indicating whether strict mode is enabled
|
|
356
415
|
|
|
357
416
|
**Note**: All internal methods are private. Use the `evaluation` property to access evaluation results.
|
|
358
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
|
|
|
@@ -197,12 +206,190 @@ class HandEvaluator {
|
|
|
197
206
|
evaluation.keyCards = this.#buildKeyCardsObject();
|
|
198
207
|
evaluation.kickerCards = this.#buildKickerCardsObject(evaluation.keyCards);
|
|
199
208
|
|
|
209
|
+
// Apply strict mode filtering if enabled
|
|
210
|
+
if (this.strict) {
|
|
211
|
+
this.#applyStrictMode(evaluation);
|
|
212
|
+
}
|
|
213
|
+
|
|
200
214
|
// Restore original evaluation
|
|
201
215
|
this.evaluation = originalEvaluation;
|
|
202
216
|
|
|
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
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Gets the hand strength value for an evaluation.
|
|
280
|
+
*
|
|
281
|
+
* @private
|
|
282
|
+
* @param {Object} evaluation - The evaluation object
|
|
283
|
+
* @returns {number} Hand strength (10 = Royal Flush, 1 = High Card, 0 = no made hand)
|
|
284
|
+
*/
|
|
285
|
+
#getHandStrength(evaluation) {
|
|
286
|
+
const handStrength = {
|
|
287
|
+
isRoyalFlush: 10, isStraightFlush: 9, isFourOfAKind: 8,
|
|
288
|
+
isFullHouse: 7, isFlush: 6, isStraight: 5, isThreeOfAKind: 4,
|
|
289
|
+
isTwoPair: 3, isPair: 2, isHighCard: 1
|
|
290
|
+
};
|
|
291
|
+
for (const [hand, strength] of Object.entries(handStrength)) {
|
|
292
|
+
if (evaluation[hand]) return strength;
|
|
293
|
+
}
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Gets sorted key card ranks for an evaluation.
|
|
299
|
+
*
|
|
300
|
+
* @private
|
|
301
|
+
* @param {Object} evaluation - The evaluation object
|
|
302
|
+
* @returns {number[]} Array of rank values sorted descending
|
|
303
|
+
*/
|
|
304
|
+
#getKeyCardRanks(evaluation) {
|
|
305
|
+
const handStrength = {
|
|
306
|
+
isRoyalFlush: 10, isStraightFlush: 9, isFourOfAKind: 8,
|
|
307
|
+
isFullHouse: 7, isFlush: 6, isStraight: 5, isThreeOfAKind: 4,
|
|
308
|
+
isTwoPair: 3, isPair: 2, isHighCard: 1
|
|
309
|
+
};
|
|
310
|
+
for (const [hand] of Object.entries(handStrength)) {
|
|
311
|
+
if (evaluation[hand]) {
|
|
312
|
+
const keyCards = evaluation.keyCards[hand] || [];
|
|
313
|
+
return keyCards.map(c => this.#getRankValue(c)).sort((a, b) => b - a);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Gets sorted kicker ranks for an evaluation.
|
|
321
|
+
*
|
|
322
|
+
* @private
|
|
323
|
+
* @param {Object} evaluation - The evaluation object
|
|
324
|
+
* @returns {number[]} Array of rank values sorted descending
|
|
325
|
+
*/
|
|
326
|
+
#getKickerRanks(evaluation) {
|
|
327
|
+
const handStrength = {
|
|
328
|
+
isRoyalFlush: 10, isStraightFlush: 9, isFourOfAKind: 8,
|
|
329
|
+
isFullHouse: 7, isFlush: 6, isStraight: 5, isThreeOfAKind: 4,
|
|
330
|
+
isTwoPair: 3, isPair: 2, isHighCard: 1
|
|
331
|
+
};
|
|
332
|
+
for (const [hand] of Object.entries(handStrength)) {
|
|
333
|
+
if (evaluation[hand]) {
|
|
334
|
+
const kickers = evaluation.kickerCards[hand] || [];
|
|
335
|
+
return kickers.map(c => this.#getRankValue(c)).sort((a, b) => b - a);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Compares two evaluations to determine which is better.
|
|
343
|
+
*
|
|
344
|
+
* @private
|
|
345
|
+
* @param {Object} eval1 - First evaluation object
|
|
346
|
+
* @param {Object} eval2 - Second evaluation object
|
|
347
|
+
* @returns {number} Positive if eval2 is better, negative if eval1 is better, 0 if equal
|
|
348
|
+
*/
|
|
349
|
+
#compareEvaluations(eval1, eval2) {
|
|
350
|
+
const strength1 = this.#getHandStrength(eval1);
|
|
351
|
+
const strength2 = this.#getHandStrength(eval2);
|
|
352
|
+
if (strength1 !== strength2) return strength2 - strength1;
|
|
353
|
+
|
|
354
|
+
const keyRanks1 = this.#getKeyCardRanks(eval1);
|
|
355
|
+
const keyRanks2 = this.#getKeyCardRanks(eval2);
|
|
356
|
+
for (let i = 0; i < Math.max(keyRanks1.length, keyRanks2.length); i++) {
|
|
357
|
+
const rank1 = keyRanks1[i] || 0;
|
|
358
|
+
const rank2 = keyRanks2[i] || 0;
|
|
359
|
+
if (rank1 !== rank2) return rank2 - rank1;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const kickerRanks1 = this.#getKickerRanks(eval1);
|
|
363
|
+
const kickerRanks2 = this.#getKickerRanks(eval2);
|
|
364
|
+
for (let i = 0; i < Math.max(kickerRanks1.length, kickerRanks2.length); i++) {
|
|
365
|
+
const rank1 = kickerRanks1[i] || 0;
|
|
366
|
+
const rank2 = kickerRanks2[i] || 0;
|
|
367
|
+
if (rank1 !== rank2) return rank2 - rank1;
|
|
368
|
+
}
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Finds the best combination from a result object.
|
|
374
|
+
*
|
|
375
|
+
* @private
|
|
376
|
+
* @param {Object} result - Object mapping hand keys to evaluation objects
|
|
377
|
+
* @returns {Object} Object with only the best combination
|
|
378
|
+
*/
|
|
379
|
+
#findBestCombination(result) {
|
|
380
|
+
let bestKey = null;
|
|
381
|
+
let bestEval = null;
|
|
382
|
+
for (const [key, evaluation] of Object.entries(result)) {
|
|
383
|
+
const strength = this.#getHandStrength(evaluation);
|
|
384
|
+
const bestStrength = bestEval ? this.#getHandStrength(bestEval) : 0;
|
|
385
|
+
if (!bestEval || strength > bestStrength || (strength === bestStrength && this.#compareEvaluations(evaluation, bestEval) > 0)) {
|
|
386
|
+
bestKey = key;
|
|
387
|
+
bestEval = evaluation;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return bestKey ? { [bestKey]: bestEval } : result;
|
|
391
|
+
}
|
|
392
|
+
|
|
206
393
|
/**
|
|
207
394
|
* Evaluates all possible 5-card combinations from the input cards.
|
|
208
395
|
* For exactly 5 cards, evaluates directly. For more cards, generates all combinations
|
|
@@ -218,26 +405,22 @@ class HandEvaluator {
|
|
|
218
405
|
* // With 7 cards: returns object with 21 key-value pairs (C(7,5) = 21)
|
|
219
406
|
*/
|
|
220
407
|
#evaluateAllCombinations() {
|
|
221
|
-
// If exactly 5 cards, evaluate directly and wrap in the new format
|
|
222
408
|
if (this.cards.length === 5) {
|
|
223
409
|
const key = this.#formatHandKey(this.cards);
|
|
224
410
|
const evaluation = this.#evaluateSingleHand();
|
|
225
411
|
return { [key]: evaluation };
|
|
226
412
|
}
|
|
227
413
|
|
|
228
|
-
// For more than 5 cards, generate all combinations
|
|
229
414
|
const combinations = this.#generateCombinations(this.cards, 5);
|
|
230
415
|
const result = {};
|
|
231
416
|
for (const combination of combinations) {
|
|
232
417
|
const key = this.#formatHandKey(combination);
|
|
233
|
-
|
|
234
|
-
// This will evaluate directly since it's exactly 5 cards
|
|
235
|
-
const tempEvaluator = new HandEvaluator(combination);
|
|
236
|
-
// Extract the evaluation object from the single key-value pair
|
|
418
|
+
const tempEvaluator = new HandEvaluator(combination, this.strict);
|
|
237
419
|
const evaluationKey = Object.keys(tempEvaluator.evaluation)[0];
|
|
238
420
|
result[key] = tempEvaluator.evaluation[evaluationKey];
|
|
239
421
|
}
|
|
240
|
-
|
|
422
|
+
|
|
423
|
+
return this.strict ? this.#findBestCombination(result) : result;
|
|
241
424
|
}
|
|
242
425
|
|
|
243
426
|
/**
|
|
@@ -1423,10 +1606,12 @@ class HandEvaluator {
|
|
|
1423
1606
|
* Generates all 5-card combinations and evaluates each one.
|
|
1424
1607
|
*
|
|
1425
1608
|
* @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)
|
|
1609
|
+
* @param {boolean} [strict=true] - If true, only the strongest made hand and stronger draws are marked as true.
|
|
1610
|
+
* If false, all applicable hand types are marked.
|
|
1426
1611
|
* @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
|
|
1427
1612
|
*/
|
|
1428
|
-
function evaluateHand(cards) {
|
|
1429
|
-
const evaluator = new HandEvaluator(cards);
|
|
1613
|
+
function evaluateHand(cards, strict = true) {
|
|
1614
|
+
const evaluator = new HandEvaluator(cards, strict);
|
|
1430
1615
|
return evaluator.evaluation;
|
|
1431
1616
|
}
|
|
1432
1617
|
|