poker-extval 3.3.2 → 3.3.3
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 +3 -3
- package/evaluator.js +34 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,14 +201,14 @@ The `evaluateHand()` function returns an object where:
|
|
|
201
201
|
- `isTwoUndercards` - Both hole cards rank below every board card
|
|
202
202
|
|
|
203
203
|
#### Other Categorization
|
|
204
|
-
- `isUnderPair` - Pocket pair ranked below every
|
|
204
|
+
- `isUnderPair` - Pocket pair ranked below every board card. With `holeCards` this works on any street, including the flop; without them the board is unknown, so it requires 6+ cards and means a pair ranked below every other card
|
|
205
205
|
- `isComboDraw` - Both a flush draw (not backdoor) and a straight draw
|
|
206
206
|
|
|
207
207
|
#### Draws
|
|
208
208
|
- `isFlushDraw` - Four cards of the same suit (one away from flush)
|
|
209
209
|
- `isBackdoorFlushDraw` - Three cards of the same suit (two away from flush)
|
|
210
|
-
- `isOpenEndedStraightDraw` -
|
|
211
|
-
- `isInsideStraightDraw` - One card away from straight (
|
|
210
|
+
- `isOpenEndedStraightDraw` - Four in a row that a card at either end completes (e.g. 9-8-7-6). A-K-Q-J and A-2-3-4 complete at one end only, so they count as inside draws
|
|
211
|
+
- `isInsideStraightDraw` - One card away from a straight that only one rank completes (a gap in the middle, or A-K-Q-J / A-2-3-4)
|
|
212
212
|
- `isStraightDraw` - Any type of straight draw (open-ended or inside, standard or wheel)
|
|
213
213
|
|
|
214
214
|
**Note**: When `holeCards` are supplied, every draw (including `isComboDraw` and the wheel draws) must use at least one hole card. A draw made entirely of board cards is shared by every player, so it is not reported: 2-2 on a 9-8-7-6 board is not an open-ended straight draw, and A♦-Q♦ on a K♠-7♠-2♠ flop is not a backdoor flush draw. Without `holeCards` the evaluator cannot tell the board from the hand, so draws are detected from all cards.
|
package/evaluator.js
CHANGED
|
@@ -65,7 +65,8 @@ class HandEvaluator {
|
|
|
65
65
|
*/
|
|
66
66
|
constructor(cards, strict = true, holeCards = null, originalCards = null) {
|
|
67
67
|
this.#validateCards(cards);
|
|
68
|
-
|
|
68
|
+
// Copy so that in-place sorting during evaluation never reorders the caller's array
|
|
69
|
+
this.cards = [...cards];
|
|
69
70
|
this.strict = strict;
|
|
70
71
|
this.originalCards = originalCards || (cards.length > 5 ? cards : null);
|
|
71
72
|
if (holeCards === null || holeCards === undefined) {
|
|
@@ -370,11 +371,13 @@ class HandEvaluator {
|
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
/**
|
|
373
|
-
* Gets
|
|
374
|
+
* Gets key card ranks for an evaluation, in tiebreak order.
|
|
375
|
+
* Ranks are grouped by how often they appear (so a full house compares its three of a kind
|
|
376
|
+
* before its pair), then sorted descending. In a wheel straight (A-2-3-4-5) the Ace counts low.
|
|
374
377
|
*
|
|
375
378
|
* @private
|
|
376
379
|
* @param {Object} evaluation - The evaluation object
|
|
377
|
-
* @returns {number[]} Array of rank values
|
|
380
|
+
* @returns {number[]} Array of rank values in tiebreak order
|
|
378
381
|
*/
|
|
379
382
|
#getKeyCardRanks(evaluation) {
|
|
380
383
|
const handStrength = {
|
|
@@ -384,8 +387,11 @@ class HandEvaluator {
|
|
|
384
387
|
};
|
|
385
388
|
for (const [hand] of Object.entries(handStrength)) {
|
|
386
389
|
if (evaluation[hand]) {
|
|
387
|
-
const
|
|
388
|
-
|
|
390
|
+
const ranks = (evaluation.keyCards[hand] || []).map(c => this.#getRankValue(c));
|
|
391
|
+
const isWheel = (hand === 'isStraight' || hand === 'isStraightFlush') && ranks.includes(13) && ranks.includes(4);
|
|
392
|
+
const values = isWheel ? ranks.map(r => r === 13 ? 0 : r) : ranks;
|
|
393
|
+
const counts = values.reduce((acc, r) => (acc[r] = (acc[r] || 0) + 1, acc), {});
|
|
394
|
+
return values.sort((a, b) => counts[b] - counts[a] || b - a);
|
|
389
395
|
}
|
|
390
396
|
}
|
|
391
397
|
return [];
|
|
@@ -457,7 +463,7 @@ class HandEvaluator {
|
|
|
457
463
|
for (const [key, evaluation] of Object.entries(result)) {
|
|
458
464
|
const strength = this.#getHandStrength(evaluation);
|
|
459
465
|
const bestStrength = bestEval ? this.#getHandStrength(bestEval) : 0;
|
|
460
|
-
if (!bestEval || strength > bestStrength || (strength === bestStrength && this.#compareEvaluations(evaluation, bestEval)
|
|
466
|
+
if (!bestEval || strength > bestStrength || (strength === bestStrength && this.#compareEvaluations(evaluation, bestEval) < 0)) {
|
|
461
467
|
bestKey = key;
|
|
462
468
|
bestEval = evaluation;
|
|
463
469
|
}
|
|
@@ -1088,8 +1094,8 @@ class HandEvaluator {
|
|
|
1088
1094
|
*
|
|
1089
1095
|
* @returns {boolean} True if the hand is an open-ended straight draw, false otherwise
|
|
1090
1096
|
* @example
|
|
1091
|
-
* const evaluator = new HandEvaluator(['
|
|
1092
|
-
* evaluator.isOpenEndedStraightDraw(); // Returns true (needs
|
|
1097
|
+
* const evaluator = new HandEvaluator(['Ks', 'Qs', 'Js', 'Ts', '4h']);
|
|
1098
|
+
* evaluator.isOpenEndedStraightDraw(); // Returns true (needs A or 9 to complete straight)
|
|
1093
1099
|
*/
|
|
1094
1100
|
#isOpenEndedStraightDraw() {
|
|
1095
1101
|
if (this.evaluation.isOpenEndedStraightDraw !== undefined) return this.evaluation.isOpenEndedStraightDraw;
|
|
@@ -1113,7 +1119,7 @@ class HandEvaluator {
|
|
|
1113
1119
|
|
|
1114
1120
|
/**
|
|
1115
1121
|
* Determines if the hand is an open-ended straight draw using wheel ranking.
|
|
1116
|
-
* Checks for wheel-equivalent open-ended draws
|
|
1122
|
+
* Checks for wheel-equivalent open-ended draws. A-2-3-4 needs only a 5, so it counts as an inside draw.
|
|
1117
1123
|
* Paired cards count once. Returns false if the hand is already a wheel straight.
|
|
1118
1124
|
*
|
|
1119
1125
|
* @returns {boolean} True if the hand is a wheel-equivalent open-ended straight draw, false otherwise
|
|
@@ -1700,7 +1706,9 @@ class HandEvaluator {
|
|
|
1700
1706
|
/**
|
|
1701
1707
|
* Finds 4 cards of distinct, rank-adjacent ranks whose rank span is one of the given distances.
|
|
1702
1708
|
* Paired cards count once (a hole card is preferred as the representative of its rank), so a
|
|
1703
|
-
* paired hand such as 8-8-6-5-4 still finds its 8-6-5-4 draw.
|
|
1709
|
+
* paired hand such as 8-8-6-5-4 still finds its 8-6-5-4 draw. One-ended draws (A-K-Q-J and
|
|
1710
|
+
* A-2-3-4) count as inside draws, since only one rank completes them. Wheel windows must
|
|
1711
|
+
* contain the Ace. When hole cards are supplied, a
|
|
1704
1712
|
* window whose ranks are all already on the board is skipped, since a draw the board gives
|
|
1705
1713
|
* every player is not the player's draw.
|
|
1706
1714
|
*
|
|
@@ -1723,7 +1731,12 @@ class HandEvaluator {
|
|
|
1723
1731
|
}
|
|
1724
1732
|
for (let i = 0; i + 4 <= distinct.length; i++) {
|
|
1725
1733
|
const window = distinct.slice(i, i + 4);
|
|
1726
|
-
|
|
1734
|
+
// Wheel ranking only matters when the Ace plays low; other windows are standard draws
|
|
1735
|
+
if (wheel && !window.some(c => c.startsWith('A'))) continue;
|
|
1736
|
+
let distance = rankValue(window[0]) - rankValue(window[3]);
|
|
1737
|
+
// Four in a row capped by the Ace (A-K-Q-J or A-2-3-4) completes at one end only,
|
|
1738
|
+
// so it has the same 4 outs as an inside draw
|
|
1739
|
+
if (distance === 3 && window.some(c => c.startsWith('A'))) distance = 4;
|
|
1727
1740
|
if (distances.includes(distance) && this.#addsRankToBoard(window)) return window;
|
|
1728
1741
|
}
|
|
1729
1742
|
return null;
|
|
@@ -1795,7 +1808,9 @@ class HandEvaluator {
|
|
|
1795
1808
|
|
|
1796
1809
|
/**
|
|
1797
1810
|
* Determines if the hand contains an under pair (pocket pair smaller than all board cards).
|
|
1798
|
-
*
|
|
1811
|
+
* With hole cards supplied, the hole cards must be a pocket pair ranked below every board card;
|
|
1812
|
+
* this works on any street, including a 5-card flop. Without hole cards the board is unknown, so
|
|
1813
|
+
* it applies only to 6+ cards where the pair rank is smaller than all other cards.
|
|
1799
1814
|
*
|
|
1800
1815
|
* @returns {boolean} True if the hand is an under pair, false otherwise
|
|
1801
1816
|
* @example
|
|
@@ -1806,6 +1821,13 @@ class HandEvaluator {
|
|
|
1806
1821
|
#isUnderPair() {
|
|
1807
1822
|
if (this.evaluation.isUnderPair !== undefined) return this.evaluation.isUnderPair;
|
|
1808
1823
|
if (this.evaluation.isPair === undefined) this.#isPair();
|
|
1824
|
+
if (this.evaluation.isPair && this.holeCards) {
|
|
1825
|
+
const [first, second] = this.holeCards;
|
|
1826
|
+
const holeRank = first.slice(0, -1);
|
|
1827
|
+
if (second.slice(0, -1) !== holeRank || this.#getPairRank() !== holeRank) return this.evaluation.isUnderPair = false;
|
|
1828
|
+
const board = (this.originalCards || this.cards).filter(c => !this.holeCards.includes(c));
|
|
1829
|
+
return this.evaluation.isUnderPair = board.every(c => this.#getRankValue(c) > this.#getRankValue(first));
|
|
1830
|
+
}
|
|
1809
1831
|
if (!this.evaluation.isPair || !this.originalCards || this.originalCards.length < 6) {
|
|
1810
1832
|
return this.evaluation.isUnderPair = false;
|
|
1811
1833
|
}
|