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.
- package/evaluator.js +197 -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
|
-
//
|
|
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