poker-extval 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/evaluator.js +330 -184
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,12 +49,6 @@ const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts', '9s']);
|
|
|
49
49
|
// Access evaluation results
|
|
50
50
|
const evaluation = evaluator.evaluation;
|
|
51
51
|
// Object with keys for each 5-card combination
|
|
52
|
-
|
|
53
|
-
// Use utility methods
|
|
54
|
-
evaluator.sort(); // Sort cards by rank
|
|
55
|
-
evaluator.getDistance(); // Get distance between highest and lowest card
|
|
56
|
-
evaluator.countSuits(); // Count suits in hand
|
|
57
|
-
evaluator.countRanks(); // Count ranks in hand
|
|
58
52
|
```
|
|
59
53
|
|
|
60
54
|
### Evaluating Multiple Card Hands
|
|
@@ -126,12 +120,17 @@ The `evaluateHand()` function returns an object where:
|
|
|
126
120
|
- `isBackdoorFlushDraw` - Three cards of the same suit (two away from flush)
|
|
127
121
|
- `isOpenEndedStraightDraw` - One card away from straight on either end
|
|
128
122
|
- `isInsideStraightDraw` - One card away from straight (needed card in middle)
|
|
123
|
+
- `isStraightDraw` - Any type of straight draw (open-ended or inside, standard or wheel)
|
|
129
124
|
|
|
130
125
|
#### Wheel Straight Support
|
|
131
126
|
- `isStraightWheel` - Wheel straight (A-2-3-4-5)
|
|
132
127
|
- `isOpenEndedStraightDrawWheel` - Open-ended draw using wheel ranking
|
|
133
128
|
- `isInsideStraightDrawWheel` - Inside draw using wheel ranking
|
|
134
129
|
|
|
130
|
+
#### Card Arrays
|
|
131
|
+
- `keyCards` - Array of cards (sorted) that constitute the made hand or draw
|
|
132
|
+
- `kickerCards` - Array of cards (sorted) that don't constitute the made hand or draw
|
|
133
|
+
|
|
135
134
|
### Example Response
|
|
136
135
|
|
|
137
136
|
```javascript
|
|
@@ -159,7 +158,10 @@ The `evaluateHand()` function returns an object where:
|
|
|
159
158
|
"isBackdoorFlushDraw": false,
|
|
160
159
|
"isOpenEndedStraightDraw": false,
|
|
161
160
|
"isInsideStraightDraw": false,
|
|
162
|
-
"
|
|
161
|
+
"isStraightDraw": false,
|
|
162
|
+
"isHighCard": false,
|
|
163
|
+
"keyCards": ["As", "Ks", "Qs", "Js", "Ts"],
|
|
164
|
+
"kickerCards": []
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
```
|
|
@@ -265,16 +267,14 @@ new HandEvaluator(cards)
|
|
|
265
267
|
|
|
266
268
|
Creates a new HandEvaluator instance.
|
|
267
269
|
|
|
268
|
-
|
|
270
|
+
**Parameters:**
|
|
271
|
+
- `cards` (string[]): Array of 5 or more card strings
|
|
269
272
|
|
|
270
|
-
|
|
271
|
-
- `
|
|
272
|
-
- `
|
|
273
|
-
- `getDistanceWheel(cards?)` - Calculate distance using wheel ranking
|
|
274
|
-
- `countSuits()` - Count occurrences of each suit
|
|
275
|
-
- `countRanks()` - Count occurrences of each rank (sorted)
|
|
273
|
+
**Properties:**
|
|
274
|
+
- `evaluation` - Object mapping sorted hand strings to evaluation objects (same format as `evaluateHand()` return value)
|
|
275
|
+
- `cards` - Array of input cards
|
|
276
276
|
|
|
277
|
-
All
|
|
277
|
+
**Note**: All internal methods are private. Use the `evaluation` property to access evaluation results.
|
|
278
278
|
|
|
279
279
|
## License
|
|
280
280
|
|
package/evaluator.js
CHANGED
|
@@ -166,34 +166,40 @@ class HandEvaluator {
|
|
|
166
166
|
this.evaluation = {};
|
|
167
167
|
|
|
168
168
|
// Call all evaluation methods - they will populate this.evaluation
|
|
169
|
-
this
|
|
170
|
-
this
|
|
171
|
-
this
|
|
172
|
-
this
|
|
173
|
-
this
|
|
174
|
-
this
|
|
175
|
-
this
|
|
176
|
-
this
|
|
177
|
-
this
|
|
178
|
-
this
|
|
179
|
-
this
|
|
180
|
-
this
|
|
181
|
-
this
|
|
182
|
-
this
|
|
183
|
-
this
|
|
184
|
-
this
|
|
185
|
-
this
|
|
186
|
-
this
|
|
187
|
-
this
|
|
188
|
-
this
|
|
189
|
-
this
|
|
190
|
-
this
|
|
191
|
-
this
|
|
192
|
-
this
|
|
169
|
+
this.#isFourOfAKind();
|
|
170
|
+
this.#isFullHouse();
|
|
171
|
+
this.#isThreeOfAKind();
|
|
172
|
+
this.#isTwoPair();
|
|
173
|
+
this.#isPair();
|
|
174
|
+
this.#isFlush();
|
|
175
|
+
this.#isTopPair();
|
|
176
|
+
this.#isMiddlePair();
|
|
177
|
+
this.#isBottomPair();
|
|
178
|
+
this.#isTopAndMiddlePair();
|
|
179
|
+
this.#isTopAndBottomPair();
|
|
180
|
+
this.#isMiddleAndBottomPair();
|
|
181
|
+
this.#isTopThreeOfAKind();
|
|
182
|
+
this.#isMiddleThreeOfAKind();
|
|
183
|
+
this.#isBottomThreeOfAKind();
|
|
184
|
+
this.#isFlushDraw();
|
|
185
|
+
this.#isBackdoorFlushDraw();
|
|
186
|
+
this.#isStraight();
|
|
187
|
+
this.#isRoyalFlush();
|
|
188
|
+
this.#isStraightFlush();
|
|
189
|
+
this.#isOpenEndedStraightDraw();
|
|
190
|
+
this.#isInsideStraightDraw();
|
|
191
|
+
this.#isStraightDraw();
|
|
192
|
+
this.#isHighCard();
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// Add keyCards and kickerCards before restoring original evaluation
|
|
195
|
+
// (these methods need access to this.evaluation)
|
|
195
196
|
const evaluation = this.evaluation;
|
|
197
|
+
evaluation.keyCards = this.#getKeyCards();
|
|
198
|
+
evaluation.kickerCards = this.#getKickerCards();
|
|
199
|
+
|
|
200
|
+
// Restore original evaluation
|
|
196
201
|
this.evaluation = originalEvaluation;
|
|
202
|
+
|
|
197
203
|
return evaluation;
|
|
198
204
|
}
|
|
199
205
|
|
|
@@ -275,18 +281,12 @@ class HandEvaluator {
|
|
|
275
281
|
* Sorts cards from highest to lowest rank using standard poker ranking (Ace high).
|
|
276
282
|
* Modifies the array in place. If no cards parameter is provided, sorts this.cards.
|
|
277
283
|
*
|
|
284
|
+
* @private
|
|
278
285
|
* @param {string[]} [cards=null] - Optional array of card strings to sort.
|
|
279
286
|
* If null or undefined, sorts this.cards instead.
|
|
280
287
|
* @returns {void}
|
|
281
|
-
* @example
|
|
282
|
-
* const evaluator = new HandEvaluator(['2s', 'As', 'Ks', '5s', 'Ts']);
|
|
283
|
-
* evaluator.sort(); // Modifies evaluator.cards to ['As', 'Ks', 'Ts', '5s', '2s']
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* const cards = ['2s', 'As', 'Ks'];
|
|
287
|
-
* evaluator.sort(cards); // Modifies cards array to ['As', 'Ks', '2s']
|
|
288
288
|
*/
|
|
289
|
-
sort(cards = null) {
|
|
289
|
+
#sort(cards = null) {
|
|
290
290
|
const cardsToSort = cards || this.cards;
|
|
291
291
|
cardsToSort.sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
292
292
|
}
|
|
@@ -296,14 +296,12 @@ class HandEvaluator {
|
|
|
296
296
|
* Modifies the array in place. Used for evaluating wheel straights (A-2-3-4-5).
|
|
297
297
|
* If no cards parameter is provided, sorts this.cards.
|
|
298
298
|
*
|
|
299
|
+
* @private
|
|
299
300
|
* @param {string[]} [cards=null] - Optional array of card strings to sort.
|
|
300
301
|
* If null or undefined, sorts this.cards instead.
|
|
301
302
|
* @returns {void}
|
|
302
|
-
* @example
|
|
303
|
-
* const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
|
|
304
|
-
* evaluator.sortWheel(); // Modifies evaluator.cards to ['5s', '4s', '3s', '2s', 'As']
|
|
305
303
|
*/
|
|
306
|
-
sortWheel(cards = null) {
|
|
304
|
+
#sortWheel(cards = null) {
|
|
307
305
|
const cardsToSort = cards || this.cards;
|
|
308
306
|
cardsToSort.sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
309
307
|
}
|
|
@@ -312,18 +310,12 @@ class HandEvaluator {
|
|
|
312
310
|
* Calculates the distance (rank difference) between the highest and lowest card.
|
|
313
311
|
* Uses standard poker ranking (Ace high). Distance of 4 indicates a possible straight.
|
|
314
312
|
*
|
|
313
|
+
* @private
|
|
315
314
|
* @param {string[]} [cards=null] - Optional array of card strings to evaluate.
|
|
316
315
|
* If null or undefined, uses this.cards instead.
|
|
317
316
|
* @returns {number} Distance between highest and lowest card (0-12)
|
|
318
|
-
* @example
|
|
319
|
-
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
|
|
320
|
-
* evaluator.getDistance(); // Returns 4 (A=13, T=9, 13-9=4)
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* evaluator.getDistance(['As', '2s', '3s', '4s', '5s']);
|
|
324
|
-
* // Returns 12 (A=13, 2=1, 13-1=12)
|
|
325
317
|
*/
|
|
326
|
-
getDistance(cards = null) {
|
|
318
|
+
#getDistance(cards = null) {
|
|
327
319
|
if (cards) {
|
|
328
320
|
// If cards array provided, sort a copy and calculate distance
|
|
329
321
|
if (cards.length === 0) return 0;
|
|
@@ -332,7 +324,7 @@ class HandEvaluator {
|
|
|
332
324
|
}
|
|
333
325
|
// If no cards provided, use instance cards property
|
|
334
326
|
if (this.cards.length === 0) return 0;
|
|
335
|
-
this
|
|
327
|
+
this.#sort();
|
|
336
328
|
return this.#getRankValue(this.cards[0]) - this.#getRankValue(this.cards[this.cards.length - 1]);
|
|
337
329
|
}
|
|
338
330
|
|
|
@@ -340,14 +332,12 @@ class HandEvaluator {
|
|
|
340
332
|
* Calculates the distance (rank difference) between the highest and lowest card using wheel ranking.
|
|
341
333
|
* Uses wheel ranking (Ace low). Distance of 4 indicates a possible wheel straight (A-2-3-4-5).
|
|
342
334
|
*
|
|
335
|
+
* @private
|
|
343
336
|
* @param {string[]} [cards=null] - Optional array of card strings to evaluate.
|
|
344
337
|
* If null or undefined, uses this.cards instead.
|
|
345
338
|
* @returns {number} Distance between highest and lowest card using wheel ranking (0-12)
|
|
346
|
-
* @example
|
|
347
|
-
* const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
|
|
348
|
-
* evaluator.getDistanceWheel(); // Returns 4 (5=5, A=1, 5-1=4) - wheel straight
|
|
349
339
|
*/
|
|
350
|
-
getDistanceWheel(cards = null) {
|
|
340
|
+
#getDistanceWheel(cards = null) {
|
|
351
341
|
if (cards) {
|
|
352
342
|
// If cards array provided, sort a copy using wheel values and calculate distance
|
|
353
343
|
if (cards.length === 0) return 0;
|
|
@@ -356,24 +346,18 @@ class HandEvaluator {
|
|
|
356
346
|
}
|
|
357
347
|
// If no cards provided, use instance cards property
|
|
358
348
|
if (this.cards.length === 0) return 0;
|
|
359
|
-
this
|
|
349
|
+
this.#sortWheel();
|
|
360
350
|
return this.#getRankValueWheel(this.cards[0]) - this.#getRankValueWheel(this.cards[this.cards.length - 1]);
|
|
361
351
|
}
|
|
362
352
|
|
|
363
353
|
/**
|
|
364
354
|
* Counts the occurrences of each suit in the hand.
|
|
365
355
|
*
|
|
356
|
+
* @private
|
|
366
357
|
* @returns {Object<string, number>} Object mapping suit to count:
|
|
367
358
|
* { s: count, h: count, d: count, c: count }
|
|
368
|
-
* @example
|
|
369
|
-
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
|
|
370
|
-
* evaluator.countSuits(); // Returns { s: 5 }
|
|
371
|
-
*
|
|
372
|
-
* @example
|
|
373
|
-
* const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', 'Jc', 'Ts']);
|
|
374
|
-
* evaluator.countSuits(); // Returns { s: 2, h: 1, d: 1, c: 1 }
|
|
375
359
|
*/
|
|
376
|
-
countSuits() {
|
|
360
|
+
#countSuits() {
|
|
377
361
|
return this.cards.reduce((acc, card) => ({ ...acc, [card.slice(-1)]: (acc[card.slice(-1)] || 0) + 1 }), {});
|
|
378
362
|
}
|
|
379
363
|
|
|
@@ -381,17 +365,11 @@ class HandEvaluator {
|
|
|
381
365
|
* Counts the occurrences of each rank and returns sorted counts (descending).
|
|
382
366
|
* Used to identify pairs, three of a kind, four of a kind, etc.
|
|
383
367
|
*
|
|
368
|
+
* @private
|
|
384
369
|
* @returns {number[]} Array of rank counts sorted in descending order
|
|
385
|
-
* @example
|
|
386
|
-
* const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Ks']);
|
|
387
|
-
* evaluator.countRanks(); // Returns [3, 2] (three aces, two kings)
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
390
|
-
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
|
|
391
|
-
* evaluator.countRanks(); // Returns [2, 2, 1] (two aces, two kings, one queen)
|
|
392
370
|
*/
|
|
393
|
-
countRanks() {
|
|
394
|
-
this
|
|
371
|
+
#countRanks() {
|
|
372
|
+
this.#sort();
|
|
395
373
|
const rankCounts = this.cards.reduce((acc, card) => (acc[card.slice(0, -1)] = (acc[card.slice(0, -1)] || 0) + 1, acc), {});
|
|
396
374
|
return Object.values(rankCounts).sort((a, b) => b - a);
|
|
397
375
|
}
|
|
@@ -406,7 +384,7 @@ class HandEvaluator {
|
|
|
406
384
|
* // Returns { A: 3, K: 2 }
|
|
407
385
|
*/
|
|
408
386
|
#getRankCounts() {
|
|
409
|
-
this
|
|
387
|
+
this.#sort();
|
|
410
388
|
return this.cards.reduce((acc, card) => (acc[card.slice(0, -1)] = (acc[card.slice(0, -1)] || 0) + 1, acc), {});
|
|
411
389
|
}
|
|
412
390
|
|
|
@@ -440,7 +418,7 @@ class HandEvaluator {
|
|
|
440
418
|
* // Returns [1, 1, 1, 2] (gap of 2 between J and 9)
|
|
441
419
|
*/
|
|
442
420
|
#getGaps() {
|
|
443
|
-
this
|
|
421
|
+
this.#sort();
|
|
444
422
|
const gaps = [];
|
|
445
423
|
for (let i = 0; i < 4; i++) {
|
|
446
424
|
gaps.push(this.#getRankValue(this.cards[i]) - this.#getRankValue(this.cards[i + 1]));
|
|
@@ -460,7 +438,7 @@ class HandEvaluator {
|
|
|
460
438
|
* // Returns [1, 1, 1, 1] (all gaps are 1)
|
|
461
439
|
*/
|
|
462
440
|
#getGapsWheel() {
|
|
463
|
-
this
|
|
441
|
+
this.#sortWheel();
|
|
464
442
|
const gaps = [];
|
|
465
443
|
for (let i = 0; i < 4; i++) {
|
|
466
444
|
gaps.push(this.#getRankValueWheel(this.cards[i]) - this.#getRankValueWheel(this.cards[i + 1]));
|
|
@@ -478,9 +456,9 @@ class HandEvaluator {
|
|
|
478
456
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Ac', 'Ks']);
|
|
479
457
|
* evaluator.isFourOfAKind(); // Returns true
|
|
480
458
|
*/
|
|
481
|
-
isFourOfAKind() {
|
|
459
|
+
#isFourOfAKind() {
|
|
482
460
|
if (this.evaluation.isFourOfAKind !== undefined) return this.evaluation.isFourOfAKind;
|
|
483
|
-
const ranks = this
|
|
461
|
+
const ranks = this.#countRanks();
|
|
484
462
|
return this.evaluation.isFourOfAKind = ranks[0] === 4 && ranks[1] === 1;
|
|
485
463
|
}
|
|
486
464
|
|
|
@@ -492,9 +470,9 @@ class HandEvaluator {
|
|
|
492
470
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Ks']);
|
|
493
471
|
* evaluator.isFullHouse(); // Returns true
|
|
494
472
|
*/
|
|
495
|
-
isFullHouse() {
|
|
473
|
+
#isFullHouse() {
|
|
496
474
|
if (this.evaluation.isFullHouse !== undefined) return this.evaluation.isFullHouse;
|
|
497
|
-
const ranks = this
|
|
475
|
+
const ranks = this.#countRanks();
|
|
498
476
|
return this.evaluation.isFullHouse = ranks[0] === 3 && ranks[1] === 2;
|
|
499
477
|
}
|
|
500
478
|
|
|
@@ -506,9 +484,9 @@ class HandEvaluator {
|
|
|
506
484
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Qs']);
|
|
507
485
|
* evaluator.isThreeOfAKind(); // Returns true
|
|
508
486
|
*/
|
|
509
|
-
isThreeOfAKind() {
|
|
487
|
+
#isThreeOfAKind() {
|
|
510
488
|
if (this.evaluation.isThreeOfAKind !== undefined) return this.evaluation.isThreeOfAKind;
|
|
511
|
-
const ranks = this
|
|
489
|
+
const ranks = this.#countRanks();
|
|
512
490
|
return this.evaluation.isThreeOfAKind = ranks[0] === 3 && ranks[1] === 1 && ranks[2] === 1;
|
|
513
491
|
}
|
|
514
492
|
|
|
@@ -520,9 +498,9 @@ class HandEvaluator {
|
|
|
520
498
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
|
|
521
499
|
* evaluator.isTwoPair(); // Returns true
|
|
522
500
|
*/
|
|
523
|
-
isTwoPair() {
|
|
501
|
+
#isTwoPair() {
|
|
524
502
|
if (this.evaluation.isTwoPair !== undefined) return this.evaluation.isTwoPair;
|
|
525
|
-
const ranks = this
|
|
503
|
+
const ranks = this.#countRanks();
|
|
526
504
|
return this.evaluation.isTwoPair = ranks[0] === 2 && ranks[1] === 2 && ranks[2] === 1;
|
|
527
505
|
}
|
|
528
506
|
|
|
@@ -534,9 +512,9 @@ class HandEvaluator {
|
|
|
534
512
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', 'Js']);
|
|
535
513
|
* evaluator.isPair(); // Returns true
|
|
536
514
|
*/
|
|
537
|
-
isPair() {
|
|
515
|
+
#isPair() {
|
|
538
516
|
if (this.evaluation.isPair !== undefined) return this.evaluation.isPair;
|
|
539
|
-
const ranks = this
|
|
517
|
+
const ranks = this.#countRanks();
|
|
540
518
|
return this.evaluation.isPair = ranks[0] === 2 && ranks[1] === 1 && ranks[2] === 1 && ranks[3] === 1;
|
|
541
519
|
}
|
|
542
520
|
|
|
@@ -551,9 +529,9 @@ class HandEvaluator {
|
|
|
551
529
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', 'Js']);
|
|
552
530
|
* evaluator.isTopPair(); // Returns true (pair of aces, highest card)
|
|
553
531
|
*/
|
|
554
|
-
isTopPair() {
|
|
532
|
+
#isTopPair() {
|
|
555
533
|
if (this.evaluation.isTopPair !== undefined) return this.evaluation.isTopPair;
|
|
556
|
-
if (this.evaluation.isPair === undefined) this
|
|
534
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
557
535
|
if (!this.evaluation.isPair) return this.evaluation.isTopPair = false;
|
|
558
536
|
const rankCounts = this.#getRankCounts();
|
|
559
537
|
return this.evaluation.isTopPair = rankCounts[this.cards[0].slice(0, -1)] === 2;
|
|
@@ -568,10 +546,10 @@ class HandEvaluator {
|
|
|
568
546
|
* const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', 'Qc', 'Js']);
|
|
569
547
|
* evaluator.isMiddlePair(); // Returns true (pair of kings, not highest or lowest)
|
|
570
548
|
*/
|
|
571
|
-
isMiddlePair() {
|
|
549
|
+
#isMiddlePair() {
|
|
572
550
|
if (this.evaluation.isMiddlePair !== undefined) return this.evaluation.isMiddlePair;
|
|
573
|
-
if (this.evaluation.isPair === undefined) this
|
|
574
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
551
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
552
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
575
553
|
if (!this.evaluation.isPair || this.evaluation.isTwoPair) return this.evaluation.isMiddlePair = false;
|
|
576
554
|
const ranks = this.#getRankCountsSorted();
|
|
577
555
|
return this.evaluation.isMiddlePair = ranks.length === 4 && (ranks[1].count === 2 || ranks[2].count === 2);
|
|
@@ -586,9 +564,9 @@ class HandEvaluator {
|
|
|
586
564
|
* const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', '2c', '2s']);
|
|
587
565
|
* evaluator.isBottomPair(); // Returns true (pair of deuces, lowest card)
|
|
588
566
|
*/
|
|
589
|
-
isBottomPair() {
|
|
567
|
+
#isBottomPair() {
|
|
590
568
|
if (this.evaluation.isBottomPair !== undefined) return this.evaluation.isBottomPair;
|
|
591
|
-
if (this.evaluation.isPair === undefined) this
|
|
569
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
592
570
|
if (!this.evaluation.isPair) return this.evaluation.isBottomPair = false;
|
|
593
571
|
const rankCounts = this.#getRankCounts();
|
|
594
572
|
return this.evaluation.isBottomPair = rankCounts[this.cards[this.cards.length - 1].slice(0, -1)] === 2;
|
|
@@ -605,9 +583,9 @@ class HandEvaluator {
|
|
|
605
583
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Kc', 'Qs']);
|
|
606
584
|
* evaluator.isTopAndMiddlePair(); // Returns true (aces and kings)
|
|
607
585
|
*/
|
|
608
|
-
isTopAndMiddlePair() {
|
|
586
|
+
#isTopAndMiddlePair() {
|
|
609
587
|
if (this.evaluation.isTopAndMiddlePair !== undefined) return this.evaluation.isTopAndMiddlePair;
|
|
610
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
588
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
611
589
|
if (!this.evaluation.isTwoPair) return this.evaluation.isTopAndMiddlePair = false;
|
|
612
590
|
const ranks = this.#getRankCountsSorted();
|
|
613
591
|
return this.evaluation.isTopAndMiddlePair = ranks[0].count === 2 && ranks[1].count === 2;
|
|
@@ -622,9 +600,9 @@ class HandEvaluator {
|
|
|
622
600
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Kd', 'Qc', '2s', '2h']);
|
|
623
601
|
* evaluator.isTopAndBottomPair(); // Returns true (aces and deuces)
|
|
624
602
|
*/
|
|
625
|
-
isTopAndBottomPair() {
|
|
603
|
+
#isTopAndBottomPair() {
|
|
626
604
|
if (this.evaluation.isTopAndBottomPair !== undefined) return this.evaluation.isTopAndBottomPair;
|
|
627
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
605
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
628
606
|
if (!this.evaluation.isTwoPair) return this.evaluation.isTopAndBottomPair = false;
|
|
629
607
|
const ranks = this.#getRankCountsSorted();
|
|
630
608
|
return this.evaluation.isTopAndBottomPair = ranks[0].count === 2 && ranks[ranks.length - 1].count === 2;
|
|
@@ -639,9 +617,9 @@ class HandEvaluator {
|
|
|
639
617
|
* const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', '2c', '2s']);
|
|
640
618
|
* evaluator.isMiddleAndBottomPair(); // Returns true (kings and deuces)
|
|
641
619
|
*/
|
|
642
|
-
isMiddleAndBottomPair() {
|
|
620
|
+
#isMiddleAndBottomPair() {
|
|
643
621
|
if (this.evaluation.isMiddleAndBottomPair !== undefined) return this.evaluation.isMiddleAndBottomPair;
|
|
644
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
622
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
645
623
|
if (!this.evaluation.isTwoPair) return this.evaluation.isMiddleAndBottomPair = false;
|
|
646
624
|
const ranks = this.#getRankCountsSorted();
|
|
647
625
|
return this.evaluation.isMiddleAndBottomPair = ranks.length === 3 && ranks[1].count === 2 && ranks[2].count === 2;
|
|
@@ -658,9 +636,9 @@ class HandEvaluator {
|
|
|
658
636
|
* const evaluator = new HandEvaluator(['As', 'Ah', 'Ad', 'Kc', 'Qs']);
|
|
659
637
|
* evaluator.isTopThreeOfAKind(); // Returns true (three aces)
|
|
660
638
|
*/
|
|
661
|
-
isTopThreeOfAKind() {
|
|
639
|
+
#isTopThreeOfAKind() {
|
|
662
640
|
if (this.evaluation.isTopThreeOfAKind !== undefined) return this.evaluation.isTopThreeOfAKind;
|
|
663
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
641
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
664
642
|
if (!this.evaluation.isThreeOfAKind) return this.evaluation.isTopThreeOfAKind = false;
|
|
665
643
|
const rankCounts = this.#getRankCounts();
|
|
666
644
|
return this.evaluation.isTopThreeOfAKind = rankCounts[this.cards[0].slice(0, -1)] === 3;
|
|
@@ -675,9 +653,9 @@ class HandEvaluator {
|
|
|
675
653
|
* const evaluator = new HandEvaluator(['As', 'Kh', 'Kd', 'Kc', 'Qs']);
|
|
676
654
|
* evaluator.isMiddleThreeOfAKind(); // Returns true (three kings)
|
|
677
655
|
*/
|
|
678
|
-
isMiddleThreeOfAKind() {
|
|
656
|
+
#isMiddleThreeOfAKind() {
|
|
679
657
|
if (this.evaluation.isMiddleThreeOfAKind !== undefined) return this.evaluation.isMiddleThreeOfAKind;
|
|
680
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
658
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
681
659
|
if (!this.evaluation.isThreeOfAKind) return this.evaluation.isMiddleThreeOfAKind = false;
|
|
682
660
|
const ranks = this.#getRankCountsSorted();
|
|
683
661
|
return this.evaluation.isMiddleThreeOfAKind = ranks.length === 3 && ranks[1].count === 3;
|
|
@@ -692,9 +670,9 @@ class HandEvaluator {
|
|
|
692
670
|
* const evaluator = new HandEvaluator(['As', 'Kh', '2d', '2c', '2s']);
|
|
693
671
|
* evaluator.isBottomThreeOfAKind(); // Returns true (three deuces)
|
|
694
672
|
*/
|
|
695
|
-
isBottomThreeOfAKind() {
|
|
673
|
+
#isBottomThreeOfAKind() {
|
|
696
674
|
if (this.evaluation.isBottomThreeOfAKind !== undefined) return this.evaluation.isBottomThreeOfAKind;
|
|
697
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
675
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
698
676
|
if (!this.evaluation.isThreeOfAKind) return this.evaluation.isBottomThreeOfAKind = false;
|
|
699
677
|
const rankCounts = this.#getRankCounts();
|
|
700
678
|
return this.evaluation.isBottomThreeOfAKind = rankCounts[this.cards[this.cards.length - 1].slice(0, -1)] === 3;
|
|
@@ -710,9 +688,9 @@ class HandEvaluator {
|
|
|
710
688
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9s']);
|
|
711
689
|
* evaluator.isFlush(); // Returns true (all spades)
|
|
712
690
|
*/
|
|
713
|
-
isFlush() {
|
|
691
|
+
#isFlush() {
|
|
714
692
|
if (this.evaluation.isFlush !== undefined) return this.evaluation.isFlush;
|
|
715
|
-
const suits = this
|
|
693
|
+
const suits = this.#countSuits();
|
|
716
694
|
return this.evaluation.isFlush = Object.values(suits).some(count => count === 5);
|
|
717
695
|
}
|
|
718
696
|
|
|
@@ -725,11 +703,11 @@ class HandEvaluator {
|
|
|
725
703
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
|
|
726
704
|
* evaluator.isFlushDraw(); // Returns true (4 spades, 1 heart)
|
|
727
705
|
*/
|
|
728
|
-
isFlushDraw() {
|
|
706
|
+
#isFlushDraw() {
|
|
729
707
|
if (this.evaluation.isFlushDraw !== undefined) return this.evaluation.isFlushDraw;
|
|
730
|
-
if (this.evaluation.isFlush === undefined) this
|
|
708
|
+
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
731
709
|
if (this.evaluation.isFlush) return this.evaluation.isFlushDraw = false;
|
|
732
|
-
const suits = this
|
|
710
|
+
const suits = this.#countSuits();
|
|
733
711
|
return this.evaluation.isFlushDraw = Object.values(suits).some(count => count === 4);
|
|
734
712
|
}
|
|
735
713
|
|
|
@@ -742,12 +720,12 @@ class HandEvaluator {
|
|
|
742
720
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Jh', '9h']);
|
|
743
721
|
* evaluator.isBackdoorFlushDraw(); // Returns true (3 spades, 2 hearts)
|
|
744
722
|
*/
|
|
745
|
-
isBackdoorFlushDraw() {
|
|
723
|
+
#isBackdoorFlushDraw() {
|
|
746
724
|
if (this.evaluation.isBackdoorFlushDraw !== undefined) return this.evaluation.isBackdoorFlushDraw;
|
|
747
|
-
if (this.evaluation.isFlush === undefined) this
|
|
748
|
-
if (this.evaluation.isFlushDraw === undefined) this
|
|
725
|
+
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
726
|
+
if (this.evaluation.isFlushDraw === undefined) this.#isFlushDraw();
|
|
749
727
|
if (this.evaluation.isFlush || this.evaluation.isFlushDraw) return this.evaluation.isBackdoorFlushDraw = false;
|
|
750
|
-
const suits = this
|
|
728
|
+
const suits = this.#countSuits();
|
|
751
729
|
return this.evaluation.isBackdoorFlushDraw = Object.values(suits).some(count => count === 3);
|
|
752
730
|
}
|
|
753
731
|
|
|
@@ -767,18 +745,18 @@ class HandEvaluator {
|
|
|
767
745
|
* const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
|
|
768
746
|
* evaluator.isStraight(); // Returns true (wheel: A-2-3-4-5)
|
|
769
747
|
*/
|
|
770
|
-
isStraight() {
|
|
748
|
+
#isStraight() {
|
|
771
749
|
if (this.evaluation.isStraight !== undefined) return this.evaluation.isStraight;
|
|
772
|
-
if (this.evaluation.isPair === undefined) this
|
|
773
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
774
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
775
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
776
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
750
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
751
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
752
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
753
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
754
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
777
755
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind) return this.evaluation.isStraight = false;
|
|
778
|
-
this
|
|
779
|
-
const distance = this
|
|
756
|
+
this.#sort();
|
|
757
|
+
const distance = this.#getDistance();
|
|
780
758
|
if (distance === 4) return this.evaluation.isStraight = true;
|
|
781
|
-
|
|
759
|
+
if (this.#isStraightWheel()) return this.evaluation.isStraight = true;
|
|
782
760
|
return this.evaluation.isStraight = false;
|
|
783
761
|
}
|
|
784
762
|
|
|
@@ -791,16 +769,16 @@ class HandEvaluator {
|
|
|
791
769
|
* const evaluator = new HandEvaluator(['As', '2s', '3s', '4s', '5s']);
|
|
792
770
|
* evaluator.isStraightWheel(); // Returns true (wheel: A-2-3-4-5)
|
|
793
771
|
*/
|
|
794
|
-
isStraightWheel() {
|
|
772
|
+
#isStraightWheel() {
|
|
795
773
|
if (this.evaluation.isStraightWheel !== undefined) return this.evaluation.isStraightWheel;
|
|
796
|
-
if (this.evaluation.isPair === undefined) this
|
|
797
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
798
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
799
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
800
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
774
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
775
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
776
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
777
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
778
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
801
779
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind) return this.evaluation.isStraightWheel = false;
|
|
802
|
-
this
|
|
803
|
-
const distance = this
|
|
780
|
+
this.#sortWheel();
|
|
781
|
+
const distance = this.#getDistanceWheel();
|
|
804
782
|
return this.evaluation.isStraightWheel = distance === 4;
|
|
805
783
|
}
|
|
806
784
|
|
|
@@ -813,10 +791,10 @@ class HandEvaluator {
|
|
|
813
791
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', 'Ts']);
|
|
814
792
|
* evaluator.isRoyalFlush(); // Returns true
|
|
815
793
|
*/
|
|
816
|
-
isRoyalFlush() {
|
|
794
|
+
#isRoyalFlush() {
|
|
817
795
|
if (this.evaluation.isRoyalFlush !== undefined) return this.evaluation.isRoyalFlush;
|
|
818
|
-
if (this.evaluation.isFlush === undefined) this
|
|
819
|
-
if (this.evaluation.isStraight === undefined) this
|
|
796
|
+
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
797
|
+
if (this.evaluation.isStraight === undefined) this.#isStraight();
|
|
820
798
|
if (!this.evaluation.isFlush || !this.evaluation.isStraight) return this.evaluation.isRoyalFlush = false;
|
|
821
799
|
const ranks = new Set(this.cards.map(c => c.slice(0, -1)));
|
|
822
800
|
return this.evaluation.isRoyalFlush = ranks.has('A') && ranks.has('K') && ranks.has('Q') && ranks.has('J') && ranks.has('T');
|
|
@@ -831,11 +809,11 @@ class HandEvaluator {
|
|
|
831
809
|
* const evaluator = new HandEvaluator(['9s', '8s', '7s', '6s', '5s']);
|
|
832
810
|
* evaluator.isStraightFlush(); // Returns true
|
|
833
811
|
*/
|
|
834
|
-
isStraightFlush() {
|
|
812
|
+
#isStraightFlush() {
|
|
835
813
|
if (this.evaluation.isStraightFlush !== undefined) return this.evaluation.isStraightFlush;
|
|
836
|
-
if (this.evaluation.isFlush === undefined) this
|
|
837
|
-
if (this.evaluation.isStraight === undefined) this
|
|
838
|
-
if (this.evaluation.isRoyalFlush === undefined) this
|
|
814
|
+
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
815
|
+
if (this.evaluation.isStraight === undefined) this.#isStraight();
|
|
816
|
+
if (this.evaluation.isRoyalFlush === undefined) this.#isRoyalFlush();
|
|
839
817
|
return this.evaluation.isStraightFlush = this.evaluation.isFlush && this.evaluation.isStraight && !this.evaluation.isRoyalFlush;
|
|
840
818
|
}
|
|
841
819
|
|
|
@@ -851,24 +829,24 @@ class HandEvaluator {
|
|
|
851
829
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
|
|
852
830
|
* evaluator.isOpenEndedStraightDraw(); // Returns true (needs T or 9 to complete straight)
|
|
853
831
|
*/
|
|
854
|
-
isOpenEndedStraightDraw() {
|
|
832
|
+
#isOpenEndedStraightDraw() {
|
|
855
833
|
if (this.evaluation.isOpenEndedStraightDraw !== undefined) return this.evaluation.isOpenEndedStraightDraw;
|
|
856
|
-
if (this.evaluation.isPair === undefined) this
|
|
857
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
858
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
859
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
860
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
861
|
-
if (this.evaluation.isStraight === undefined) this
|
|
834
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
835
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
836
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
837
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
838
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
839
|
+
if (this.evaluation.isStraight === undefined) this.#isStraight();
|
|
862
840
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
|
|
863
841
|
// Check wheel equivalent before returning false
|
|
864
|
-
if (this
|
|
842
|
+
if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
865
843
|
return this.evaluation.isOpenEndedStraightDraw = false;
|
|
866
844
|
}
|
|
867
|
-
this
|
|
868
|
-
const result = this
|
|
845
|
+
this.#sort();
|
|
846
|
+
const result = this.#getDistance(this.cards.slice(0,4)) === 3 || this.#getDistance(this.cards.slice(1,5)) === 3;
|
|
869
847
|
if (result) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
870
848
|
// Check wheel equivalent
|
|
871
|
-
if (this
|
|
849
|
+
if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
|
|
872
850
|
return this.evaluation.isOpenEndedStraightDraw = false;
|
|
873
851
|
}
|
|
874
852
|
|
|
@@ -879,17 +857,17 @@ class HandEvaluator {
|
|
|
879
857
|
*
|
|
880
858
|
* @returns {boolean} True if the hand is a wheel-equivalent open-ended straight draw, false otherwise
|
|
881
859
|
*/
|
|
882
|
-
isOpenEndedStraightDrawWheel() {
|
|
860
|
+
#isOpenEndedStraightDrawWheel() {
|
|
883
861
|
if (this.evaluation.isOpenEndedStraightDrawWheel !== undefined) return this.evaluation.isOpenEndedStraightDrawWheel;
|
|
884
|
-
if (this.evaluation.isPair === undefined) this
|
|
885
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
886
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
887
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
888
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
889
|
-
if (this.evaluation.isStraightWheel === undefined) this
|
|
862
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
863
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
864
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
865
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
866
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
867
|
+
if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
|
|
890
868
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isOpenEndedStraightDrawWheel = false;
|
|
891
|
-
this
|
|
892
|
-
return this.evaluation.isOpenEndedStraightDrawWheel = this
|
|
869
|
+
this.#sortWheel();
|
|
870
|
+
return this.evaluation.isOpenEndedStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 3 || this.#getDistanceWheel(this.cards.slice(1,5)) === 3;
|
|
893
871
|
}
|
|
894
872
|
|
|
895
873
|
/**
|
|
@@ -902,25 +880,25 @@ class HandEvaluator {
|
|
|
902
880
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
|
|
903
881
|
* evaluator.isInsideStraightDraw(); // Returns true (needs T in the middle)
|
|
904
882
|
*/
|
|
905
|
-
isInsideStraightDraw() {
|
|
883
|
+
#isInsideStraightDraw() {
|
|
906
884
|
if (this.evaluation.isInsideStraightDraw !== undefined) return this.evaluation.isInsideStraightDraw;
|
|
907
|
-
if (this.evaluation.isPair === undefined) this
|
|
908
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
909
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
910
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
911
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
912
|
-
if (this.evaluation.isStraight === undefined) this
|
|
885
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
886
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
887
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
888
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
889
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
890
|
+
if (this.evaluation.isStraight === undefined) this.#isStraight();
|
|
913
891
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
|
|
914
892
|
// Check wheel equivalent before returning false
|
|
915
|
-
if (this
|
|
893
|
+
if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
|
|
916
894
|
return this.evaluation.isInsideStraightDraw = false;
|
|
917
895
|
}
|
|
918
896
|
|
|
919
|
-
this
|
|
920
|
-
const result = this
|
|
897
|
+
this.#sort();
|
|
898
|
+
const result = this.#getDistance(this.cards.slice(0,4)) === 4 || this.#getDistance(this.cards.slice(1,5)) === 4;
|
|
921
899
|
if (result) return this.evaluation.isInsideStraightDraw = true;
|
|
922
900
|
// Check wheel equivalent
|
|
923
|
-
if (this
|
|
901
|
+
if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
|
|
924
902
|
return this.evaluation.isInsideStraightDraw = false;
|
|
925
903
|
}
|
|
926
904
|
|
|
@@ -931,18 +909,18 @@ class HandEvaluator {
|
|
|
931
909
|
*
|
|
932
910
|
* @returns {boolean} True if the hand is a wheel-equivalent inside straight draw, false otherwise
|
|
933
911
|
*/
|
|
934
|
-
isInsideStraightDrawWheel() {
|
|
912
|
+
#isInsideStraightDrawWheel() {
|
|
935
913
|
if (this.evaluation.isInsideStraightDrawWheel !== undefined) return this.evaluation.isInsideStraightDrawWheel;
|
|
936
|
-
if (this.evaluation.isPair === undefined) this
|
|
937
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
938
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
939
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
940
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
941
|
-
if (this.evaluation.isStraightWheel === undefined) this
|
|
914
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
915
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
916
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
917
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
918
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
919
|
+
if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
|
|
942
920
|
if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isInsideStraightDrawWheel = false;
|
|
943
921
|
|
|
944
|
-
this
|
|
945
|
-
return this.evaluation.isInsideStraightDrawWheel = this
|
|
922
|
+
this.#sortWheel();
|
|
923
|
+
return this.evaluation.isInsideStraightDrawWheel = this.#getDistanceWheel(this.cards.slice(0,4)) === 4 || this.#getDistanceWheel(this.cards.slice(1,5)) === 4;
|
|
946
924
|
}
|
|
947
925
|
|
|
948
926
|
/**
|
|
@@ -958,13 +936,181 @@ class HandEvaluator {
|
|
|
958
936
|
* const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '8h']);
|
|
959
937
|
* evaluator.isStraightDraw(); // Returns true (inside straight draw)
|
|
960
938
|
*/
|
|
961
|
-
isStraightDraw() {
|
|
939
|
+
#isStraightDraw() {
|
|
962
940
|
if (this.evaluation.isStraightDraw !== undefined) return this.evaluation.isStraightDraw;
|
|
963
|
-
if (this.evaluation.isOpenEndedStraightDraw === undefined) this
|
|
964
|
-
if (this.evaluation.isInsideStraightDraw === undefined) this
|
|
941
|
+
if (this.evaluation.isOpenEndedStraightDraw === undefined) this.#isOpenEndedStraightDraw();
|
|
942
|
+
if (this.evaluation.isInsideStraightDraw === undefined) this.#isInsideStraightDraw();
|
|
965
943
|
return this.evaluation.isStraightDraw = this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw;
|
|
966
944
|
}
|
|
967
945
|
|
|
946
|
+
// ==================== KEY CARDS AND KICKER CARDS ====================
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Helper method to extract the 5 cards that form a straight.
|
|
950
|
+
* Handles both standard straights and wheel straights.
|
|
951
|
+
*
|
|
952
|
+
* @private
|
|
953
|
+
* @returns {string[]} Array of 5 cards forming the straight, sorted from highest to lowest
|
|
954
|
+
*/
|
|
955
|
+
#getStraightCards() {
|
|
956
|
+
// Try standard straight first
|
|
957
|
+
this.#sort();
|
|
958
|
+
const gaps = this.#getGaps();
|
|
959
|
+
if (gaps.every(g => g === 1)) {
|
|
960
|
+
return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Try wheel straight
|
|
964
|
+
this.#sortWheel();
|
|
965
|
+
const gapsWheel = this.#getGapsWheel();
|
|
966
|
+
if (gapsWheel.every(g => g === 1)) {
|
|
967
|
+
// Convert back to standard sorting for return
|
|
968
|
+
return [...this.cards].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return [];
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Helper method to extract the 4 cards that form a straight draw.
|
|
976
|
+
* Handles both standard and wheel straight draws.
|
|
977
|
+
*
|
|
978
|
+
* @private
|
|
979
|
+
* @returns {string[]} Array of 4 cards forming the draw, sorted from highest to lowest
|
|
980
|
+
*/
|
|
981
|
+
#getStraightDrawCards() {
|
|
982
|
+
// Try standard straight draw
|
|
983
|
+
this.#sort();
|
|
984
|
+
const firstFour = this.cards.slice(0, 4);
|
|
985
|
+
const lastFour = this.cards.slice(1, 5);
|
|
986
|
+
|
|
987
|
+
const firstFourDist = this.#getDistance(firstFour);
|
|
988
|
+
if (firstFourDist === 3 || firstFourDist === 4) {
|
|
989
|
+
return [...firstFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
990
|
+
}
|
|
991
|
+
const lastFourDist = this.#getDistance(lastFour);
|
|
992
|
+
if (lastFourDist === 3 || lastFourDist === 4) {
|
|
993
|
+
return [...lastFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Try wheel straight draw
|
|
997
|
+
this.#sortWheel();
|
|
998
|
+
const firstFourWheel = this.cards.slice(0, 4);
|
|
999
|
+
const lastFourWheel = this.cards.slice(1, 5);
|
|
1000
|
+
|
|
1001
|
+
const firstFourDistWheel = this.#getDistanceWheel(firstFourWheel);
|
|
1002
|
+
if (firstFourDistWheel === 3 || firstFourDistWheel === 4) {
|
|
1003
|
+
return [...firstFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
1004
|
+
}
|
|
1005
|
+
const lastFourDistWheel = this.#getDistanceWheel(lastFourWheel);
|
|
1006
|
+
if (lastFourDistWheel === 3 || lastFourDistWheel === 4) {
|
|
1007
|
+
return [...lastFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return [];
|
|
1011
|
+
}
|
|
1012
|
+
|
|
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
|
+
|
|
968
1114
|
// ==================== HIGH CARD ====================
|
|
969
1115
|
|
|
970
1116
|
/**
|
|
@@ -976,15 +1122,15 @@ class HandEvaluator {
|
|
|
976
1122
|
* const evaluator = new HandEvaluator(['As', 'Kh', 'Qd', 'Jc', '9s']);
|
|
977
1123
|
* evaluator.isHighCard(); // Returns true (no made hand)
|
|
978
1124
|
*/
|
|
979
|
-
isHighCard() {
|
|
1125
|
+
#isHighCard() {
|
|
980
1126
|
if (this.evaluation.isHighCard !== undefined) return this.evaluation.isHighCard;
|
|
981
|
-
if (this.evaluation.isPair === undefined) this
|
|
982
|
-
if (this.evaluation.isTwoPair === undefined) this
|
|
983
|
-
if (this.evaluation.isThreeOfAKind === undefined) this
|
|
984
|
-
if (this.evaluation.isFullHouse === undefined) this
|
|
985
|
-
if (this.evaluation.isFourOfAKind === undefined) this
|
|
986
|
-
if (this.evaluation.isFlush === undefined) this
|
|
987
|
-
if (this.evaluation.isStraight === undefined) this
|
|
1127
|
+
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
1128
|
+
if (this.evaluation.isTwoPair === undefined) this.#isTwoPair();
|
|
1129
|
+
if (this.evaluation.isThreeOfAKind === undefined) this.#isThreeOfAKind();
|
|
1130
|
+
if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
|
|
1131
|
+
if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
|
|
1132
|
+
if (this.evaluation.isFlush === undefined) this.#isFlush();
|
|
1133
|
+
if (this.evaluation.isStraight === undefined) this.#isStraight();
|
|
988
1134
|
return this.evaluation.isHighCard = !this.evaluation.isPair && !this.evaluation.isTwoPair && !this.evaluation.isThreeOfAKind && !this.evaluation.isFullHouse && !this.evaluation.isFourOfAKind && !this.evaluation.isFlush && !this.evaluation.isStraight;
|
|
989
1135
|
}
|
|
990
1136
|
}
|