poker-extval 3.3.1 → 3.3.2

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/evaluator.js +46 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -213,6 +213,8 @@ The `evaluateHand()` function returns an object where:
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.
215
215
 
216
+ **Note**: Straight draws count paired cards once, so a paired hand can still hold one: 5-4 on an 8-8-6 flop is a pair and an inside straight draw. With `holeCards`, a hole card that only pairs a board rank does not create a straight draw (9-2 on a 9-8-7-6 board has no straight draw of its own).
217
+
216
218
  #### Wheel Straight Support
217
219
  - `isStraightWheel` - Wheel straight (A-2-3-4-5)
218
220
  - `isOpenEndedStraightDrawWheel` - Open-ended draw using wheel ranking
package/evaluator.js CHANGED
@@ -1082,8 +1082,9 @@ class HandEvaluator {
1082
1082
  /**
1083
1083
  * Determines if the hand is an open-ended straight draw (one card away from a straight on either end).
1084
1084
  * Checks both standard and wheel-equivalent open-ended draws.
1085
- * When hole cards are supplied, the four drawing cards must include at least one hole card.
1086
- * Returns false if the hand already contains pairs, two pair, three of a kind, full house, four of a kind, or a straight.
1085
+ * Paired cards count once, so a paired hand can still hold a straight draw.
1086
+ * When hole cards are supplied, a hole card must add a rank the board does not already have.
1087
+ * Returns false if the hand is already a straight.
1087
1088
  *
1088
1089
  * @returns {boolean} True if the hand is an open-ended straight draw, false otherwise
1089
1090
  * @example
@@ -1098,7 +1099,7 @@ class HandEvaluator {
1098
1099
  if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
1099
1100
  if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
1100
1101
  if (this.evaluation.isStraight === undefined) this.#isStraight();
1101
- if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
1102
+ if (this.evaluation.isStraight) {
1102
1103
  // Check wheel equivalent before returning false
1103
1104
  if (this.#isOpenEndedStraightDrawWheel()) return this.evaluation.isOpenEndedStraightDraw = true;
1104
1105
  return this.evaluation.isOpenEndedStraightDraw = false;
@@ -1113,7 +1114,7 @@ class HandEvaluator {
1113
1114
  /**
1114
1115
  * Determines if the hand is an open-ended straight draw using wheel ranking.
1115
1116
  * Checks for wheel-equivalent open-ended draws (e.g., A-2-3-4 needs 5 or 2).
1116
- * Returns false if the hand already contains pairs or higher, or is a wheel straight.
1117
+ * Paired cards count once. Returns false if the hand is already a wheel straight.
1117
1118
  *
1118
1119
  * @returns {boolean} True if the hand is a wheel-equivalent open-ended straight draw, false otherwise
1119
1120
  */
@@ -1125,15 +1126,16 @@ class HandEvaluator {
1125
1126
  if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
1126
1127
  if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
1127
1128
  if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
1128
- if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isOpenEndedStraightDrawWheel = false;
1129
+ if (this.evaluation.isStraightWheel) return this.evaluation.isOpenEndedStraightDrawWheel = false;
1129
1130
  return this.evaluation.isOpenEndedStraightDrawWheel = this.#findStraightDrawWindow([3], true) !== null;
1130
1131
  }
1131
1132
 
1132
1133
  /**
1133
1134
  * Determines if the hand is an inside straight draw (one card away from a straight, but the needed card is in the middle).
1134
1135
  * Checks both standard and wheel-equivalent inside draws.
1135
- * When hole cards are supplied, the four drawing cards must include at least one hole card.
1136
- * Returns false if the hand already contains pairs, two pair, three of a kind, full house, four of a kind, or a straight.
1136
+ * Paired cards count once, so a paired hand can still hold a straight draw.
1137
+ * When hole cards are supplied, a hole card must add a rank the board does not already have.
1138
+ * Returns false if the hand is already a straight.
1137
1139
  *
1138
1140
  * @returns {boolean} True if the hand is an inside straight draw, false otherwise
1139
1141
  * @example
@@ -1148,7 +1150,7 @@ class HandEvaluator {
1148
1150
  if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
1149
1151
  if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
1150
1152
  if (this.evaluation.isStraight === undefined) this.#isStraight();
1151
- if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraight) {
1153
+ if (this.evaluation.isStraight) {
1152
1154
  // Check wheel equivalent before returning false
1153
1155
  if (this.#isInsideStraightDrawWheel()) return this.evaluation.isInsideStraightDraw = true;
1154
1156
  return this.evaluation.isInsideStraightDraw = false;
@@ -1164,7 +1166,7 @@ class HandEvaluator {
1164
1166
  /**
1165
1167
  * Determines if the hand is an inside straight draw using wheel ranking.
1166
1168
  * Checks for wheel-equivalent inside draws (e.g., A-2-3-5 needs 4).
1167
- * Returns false if the hand already contains pairs or higher, or is a wheel straight.
1169
+ * Paired cards count once. Returns false if the hand is already a wheel straight.
1168
1170
  *
1169
1171
  * @returns {boolean} True if the hand is a wheel-equivalent inside straight draw, false otherwise
1170
1172
  */
@@ -1176,7 +1178,7 @@ class HandEvaluator {
1176
1178
  if (this.evaluation.isFullHouse === undefined) this.#isFullHouse();
1177
1179
  if (this.evaluation.isFourOfAKind === undefined) this.#isFourOfAKind();
1178
1180
  if (this.evaluation.isStraightWheel === undefined) this.#isStraightWheel();
1179
- if (this.evaluation.isPair || this.evaluation.isTwoPair || this.evaluation.isThreeOfAKind || this.evaluation.isFullHouse || this.evaluation.isFourOfAKind || this.evaluation.isStraightWheel) return this.evaluation.isInsideStraightDrawWheel = false;
1181
+ if (this.evaluation.isStraightWheel) return this.evaluation.isInsideStraightDrawWheel = false;
1180
1182
 
1181
1183
  return this.evaluation.isInsideStraightDrawWheel = this.#findStraightDrawWindow([4], true) !== null;
1182
1184
  }
@@ -1696,9 +1698,11 @@ class HandEvaluator {
1696
1698
  }
1697
1699
 
1698
1700
  /**
1699
- * Finds 4 rank-adjacent cards (the highest four or the lowest four) whose rank span is one of
1700
- * the given distances. When hole cards are supplied, a window made only of board cards is
1701
- * skipped, since a draw the board gives every player is not the player's draw.
1701
+ * Finds 4 cards of distinct, rank-adjacent ranks whose rank span is one of the given distances.
1702
+ * 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. When hole cards are supplied, a
1704
+ * window whose ranks are all already on the board is skipped, since a draw the board gives
1705
+ * every player is not the player's draw.
1702
1706
  *
1703
1707
  * @private
1704
1708
  * @param {number[]} distances - Accepted rank spans for the 4 cards (3 = open-ended, 4 = inside)
@@ -1708,11 +1712,19 @@ class HandEvaluator {
1708
1712
  #findStraightDrawWindow(distances, wheel) {
1709
1713
  const rankValue = wheel ? c => this.#getRankValueWheel(c) : c => this.#getRankValue(c);
1710
1714
  if (wheel) this.#sortWheel(); else this.#sort();
1711
- for (const window of [this.cards.slice(0, 4), this.cards.slice(1, 5)]) {
1712
- const distance = wheel ? this.#getDistanceWheel(window) : this.#getDistance(window);
1713
- if (distances.includes(distance) && this.#usesHoleCard(window)) {
1714
- return [...window].sort((a, b) => rankValue(b) - rankValue(a));
1715
+ const distinct = [];
1716
+ for (const card of this.cards) {
1717
+ const last = distinct[distinct.length - 1];
1718
+ if (last && rankValue(last) === rankValue(card)) {
1719
+ if (this.holeCards && this.holeCards.includes(card)) distinct[distinct.length - 1] = card;
1720
+ continue;
1715
1721
  }
1722
+ distinct.push(card);
1723
+ }
1724
+ for (let i = 0; i + 4 <= distinct.length; i++) {
1725
+ const window = distinct.slice(i, i + 4);
1726
+ const distance = rankValue(window[0]) - rankValue(window[3]);
1727
+ if (distances.includes(distance) && this.#addsRankToBoard(window)) return window;
1716
1728
  }
1717
1729
  return null;
1718
1730
  }
@@ -1743,6 +1755,23 @@ class HandEvaluator {
1743
1755
  return cards.some(c => this.holeCards.includes(c));
1744
1756
  }
1745
1757
 
1758
+ /**
1759
+ * Determines whether a straight draw's cards include a rank the board does not already have.
1760
+ * A hole card that only pairs a board rank adds nothing to a straight draw, so when hole cards
1761
+ * are supplied the draw must contain a rank found only in the hole cards. Without hole cards
1762
+ * every draw qualifies.
1763
+ *
1764
+ * @private
1765
+ * @param {string[]} cards - The cards forming the draw
1766
+ * @returns {boolean} True if no hole cards were supplied or a drawing rank is missing from the board
1767
+ */
1768
+ #addsRankToBoard(cards) {
1769
+ if (!this.holeCards) return true;
1770
+ const fullHand = this.originalCards || this.cards;
1771
+ const boardRanks = new Set(fullHand.filter(c => !this.holeCards.includes(c)).map(c => c.slice(0, -1)));
1772
+ return cards.some(c => !boardRanks.has(c.slice(0, -1)));
1773
+ }
1774
+
1746
1775
  // ==================== HIGH CARD ====================
1747
1776
 
1748
1777
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-extval",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Poker hand evaluator with comprehensive test suite",
5
5
  "main": "evaluator.js",
6
6
  "files": [