poker-extval 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/evaluator.js +197 -2
  2. package/package.json +1 -2
package/evaluator.js CHANGED
@@ -158,7 +158,7 @@ class HandEvaluator {
158
158
  * - Pair categorization: isTopPair, isMiddlePair, isBottomPair
159
159
  * - Two pair categorization: isTopAndMiddlePair, isTopAndBottomPair, isMiddleAndBottomPair
160
160
  * - Three of a kind categorization: isTopThreeOfAKind, isMiddleThreeOfAKind, isBottomThreeOfAKind
161
- * - Draws: isFlushDraw, isBackdoorFlushDraw, isOpenEndedStraightDraw, isInsideStraightDraw
161
+ * - Draws: isFlushDraw, isBackdoorFlushDraw, isOpenEndedStraightDraw, isInsideStraightDraw, isStraightDraw
162
162
  */
163
163
  #evaluateSingleHand() {
164
164
  // Temporarily set this.evaluation to an empty object for the evaluation methods to use
@@ -188,11 +188,18 @@ class HandEvaluator {
188
188
  this.isStraightFlush();
189
189
  this.isOpenEndedStraightDraw();
190
190
  this.isInsideStraightDraw();
191
+ this.isStraightDraw();
191
192
  this.isHighCard();
192
193
 
193
- // Get the evaluation object and restore original
194
+ // Add keyCards and kickerCards before restoring original evaluation
195
+ // (these methods need access to this.evaluation)
194
196
  const evaluation = this.evaluation;
197
+ evaluation.keyCards = this.#getKeyCards();
198
+ evaluation.kickerCards = this.#getKickerCards();
199
+
200
+ // Restore original evaluation
195
201
  this.evaluation = originalEvaluation;
202
+
196
203
  return evaluation;
197
204
  }
198
205
 
@@ -944,6 +951,194 @@ class HandEvaluator {
944
951
  return this.evaluation.isInsideStraightDrawWheel = this.getDistanceWheel(this.cards.slice(0,4)) === 4 || this.getDistanceWheel(this.cards.slice(1,5)) === 4;
945
952
  }
946
953
 
954
+ /**
955
+ * Determines if the hand is any type of straight draw (open-ended or inside, standard or wheel).
956
+ * Returns true if any of the other straight draw methods return true.
957
+ *
958
+ * @returns {boolean} True if the hand is any type of straight draw, false otherwise
959
+ * @example
960
+ * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '9h']);
961
+ * evaluator.isStraightDraw(); // Returns true (open-ended straight draw)
962
+ *
963
+ * @example
964
+ * const evaluator = new HandEvaluator(['As', 'Ks', 'Qs', 'Js', '8h']);
965
+ * evaluator.isStraightDraw(); // Returns true (inside straight draw)
966
+ */
967
+ isStraightDraw() {
968
+ if (this.evaluation.isStraightDraw !== undefined) return this.evaluation.isStraightDraw;
969
+ if (this.evaluation.isOpenEndedStraightDraw === undefined) this.isOpenEndedStraightDraw();
970
+ if (this.evaluation.isInsideStraightDraw === undefined) this.isInsideStraightDraw();
971
+ return this.evaluation.isStraightDraw = this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw;
972
+ }
973
+
974
+ // ==================== KEY CARDS AND KICKER CARDS ====================
975
+
976
+ /**
977
+ * Helper method to extract the 5 cards that form a straight.
978
+ * Handles both standard straights and wheel straights.
979
+ *
980
+ * @private
981
+ * @returns {string[]} Array of 5 cards forming the straight, sorted from highest to lowest
982
+ */
983
+ #getStraightCards() {
984
+ // Try standard straight first
985
+ this.sort();
986
+ const gaps = this.#getGaps();
987
+ if (gaps.every(g => g === 1)) {
988
+ return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
989
+ }
990
+
991
+ // Try wheel straight
992
+ this.sortWheel();
993
+ const gapsWheel = this.#getGapsWheel();
994
+ if (gapsWheel.every(g => g === 1)) {
995
+ // Convert back to standard sorting for return
996
+ return [...this.cards].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
997
+ }
998
+
999
+ return [];
1000
+ }
1001
+
1002
+ /**
1003
+ * Helper method to extract the 4 cards that form a straight draw.
1004
+ * Handles both standard and wheel straight draws.
1005
+ *
1006
+ * @private
1007
+ * @returns {string[]} Array of 4 cards forming the draw, sorted from highest to lowest
1008
+ */
1009
+ #getStraightDrawCards() {
1010
+ // Try standard straight draw
1011
+ this.sort();
1012
+ const firstFour = this.cards.slice(0, 4);
1013
+ const lastFour = this.cards.slice(1, 5);
1014
+
1015
+ const firstFourDist = this.getDistance(firstFour);
1016
+ if (firstFourDist === 3 || firstFourDist === 4) {
1017
+ return [...firstFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1018
+ }
1019
+ const lastFourDist = this.getDistance(lastFour);
1020
+ if (lastFourDist === 3 || lastFourDist === 4) {
1021
+ return [...lastFour].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1022
+ }
1023
+
1024
+ // Try wheel straight draw
1025
+ this.sortWheel();
1026
+ const firstFourWheel = this.cards.slice(0, 4);
1027
+ const lastFourWheel = this.cards.slice(1, 5);
1028
+
1029
+ const firstFourDistWheel = this.getDistanceWheel(firstFourWheel);
1030
+ if (firstFourDistWheel === 3 || firstFourDistWheel === 4) {
1031
+ return [...firstFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
1032
+ }
1033
+ const lastFourDistWheel = this.getDistanceWheel(lastFourWheel);
1034
+ if (lastFourDistWheel === 3 || lastFourDistWheel === 4) {
1035
+ return [...lastFourWheel].sort((a, b) => this.#getRankValueWheel(b) - this.#getRankValueWheel(a));
1036
+ }
1037
+
1038
+ return [];
1039
+ }
1040
+
1041
+ /**
1042
+ * Determines the primary made hand or draw and extracts the key cards.
1043
+ * Assumes cards are already sorted and evaluation is complete.
1044
+ *
1045
+ * @private
1046
+ * @returns {string[]} Array of key cards, sorted from highest to lowest rank
1047
+ */
1048
+ #getKeyCards() {
1049
+ // Ensure cards are sorted once (they likely already are from evaluation)
1050
+ const sortedCards = [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1051
+
1052
+ // Calculate these once and reuse
1053
+ const rankCounts = this.#getRankCounts();
1054
+ const suits = this.countSuits();
1055
+
1056
+ // Use evaluation results directly - no re-checking
1057
+ // Check made hands in priority order
1058
+ if (this.evaluation.isRoyalFlush || this.evaluation.isStraightFlush ||
1059
+ this.evaluation.isFullHouse) {
1060
+ return sortedCards; // All 5 cards are key cards
1061
+ }
1062
+
1063
+ if (this.evaluation.isStraight) {
1064
+ return this.#getStraightCards();
1065
+ }
1066
+
1067
+ if (this.evaluation.isFourOfAKind) {
1068
+ const fourRank = Object.keys(rankCounts).find(r => rankCounts[r] === 4);
1069
+ return sortedCards.filter(c => c.slice(0, -1) === fourRank)
1070
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1071
+ }
1072
+
1073
+ if (this.evaluation.isFlush) {
1074
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 5);
1075
+ return sortedCards.filter(c => c.slice(-1) === flushSuit)
1076
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1077
+ }
1078
+
1079
+ if (this.evaluation.isThreeOfAKind) {
1080
+ const threeRank = Object.keys(rankCounts).find(r => rankCounts[r] === 3);
1081
+ return sortedCards.filter(c => c.slice(0, -1) === threeRank)
1082
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1083
+ }
1084
+
1085
+ if (this.evaluation.isTwoPair) {
1086
+ const pairRanks = Object.keys(rankCounts).filter(r => rankCounts[r] === 2);
1087
+ return sortedCards.filter(c => pairRanks.includes(c.slice(0, -1)))
1088
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1089
+ }
1090
+
1091
+ if (this.evaluation.isPair) {
1092
+ const pairRank = Object.keys(rankCounts).find(r => rankCounts[r] === 2);
1093
+ return sortedCards.filter(c => c.slice(0, -1) === pairRank)
1094
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1095
+ }
1096
+
1097
+ // Check draws (only if no made hand)
1098
+ if (this.evaluation.isFlushDraw) {
1099
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 4);
1100
+ return sortedCards.filter(c => c.slice(-1) === flushSuit)
1101
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1102
+ }
1103
+
1104
+ if (this.evaluation.isBackdoorFlushDraw) {
1105
+ const flushSuit = Object.keys(suits).find(s => suits[s] === 3);
1106
+ return sortedCards.filter(c => c.slice(-1) === flushSuit)
1107
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1108
+ }
1109
+
1110
+ if (this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw) {
1111
+ const drawCards = this.#getStraightDrawCards();
1112
+ // Only return if we actually found draw cards (safety check)
1113
+ if (drawCards.length === 4) {
1114
+ return drawCards;
1115
+ }
1116
+ }
1117
+
1118
+ // High card or no hand/draw
1119
+ return [];
1120
+ }
1121
+
1122
+ /**
1123
+ * Gets the kicker cards (cards not in the made hand or draw).
1124
+ *
1125
+ * @private
1126
+ * @returns {string[]} Array of kicker cards, sorted from highest to lowest rank
1127
+ */
1128
+ #getKickerCards() {
1129
+ const keyCards = this.#getKeyCards();
1130
+ if (keyCards.length === 0) {
1131
+ // All cards are kickers (high card)
1132
+ return [...this.cards].sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1133
+ }
1134
+
1135
+ // Use Set for O(1) lookup
1136
+ const keyCardsSet = new Set(keyCards);
1137
+ return this.cards
1138
+ .filter(c => !keyCardsSet.has(c))
1139
+ .sort((a, b) => this.#getRankValue(b) - this.#getRankValue(a));
1140
+ }
1141
+
947
1142
  // ==================== HIGH CARD ====================
948
1143
 
949
1144
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-extval",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Poker hand evaluator with comprehensive test suite",
5
5
  "main": "evaluator.js",
6
6
  "files": [
@@ -39,4 +39,3 @@
39
39
  "jest": "^29.7.0"
40
40
  }
41
41
  }
42
-