poker-extval 1.0.1 → 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 +175 -1
  2. package/package.json +1 -1
package/evaluator.js CHANGED
@@ -191,9 +191,15 @@ class HandEvaluator {
191
191
  this.isStraightDraw();
192
192
  this.isHighCard();
193
193
 
194
- // Get the evaluation object and restore original
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
 
@@ -965,6 +971,174 @@ class HandEvaluator {
965
971
  return this.evaluation.isStraightDraw = this.evaluation.isOpenEndedStraightDraw || this.evaluation.isInsideStraightDraw;
966
972
  }
967
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
+
968
1142
  // ==================== HIGH CARD ====================
969
1143
 
970
1144
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poker-extval",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Poker hand evaluator with comprehensive test suite",
5
5
  "main": "evaluator.js",
6
6
  "files": [