// Poker Hand Evaluator by Andrey Kurdyumov ©2013
// v1.0.0
/* tslint:disable:no-bitwise */
// tslint:disable-next-line:no-namespace
namespace HoldemHand {
export interface HandRepresentation {
Cards: number[];
Suits: number[];
}
export enum HandParseResultStatus {
Ok,
InvalidHand,
CardsMissing,
SuitsMissing,
AllCardsShouldHaveOneSuit,
InsufficientCardsAndDuplicates,
InsufficientCards,
DuplicatesDetected,
}
export interface HandParseResult {
Status: HandParseResultStatus;
Hand?: HandRepresentation;
}
export interface CardRank {
HandType: number; // Hand type. First part of universal card strength.
Score: number; // Score of the hand within same hand type. Second part of universal card strength.
WinnerCardsSet: number[]; // Indexes of the cards within original hand which form the best card combination.
}
/// Human readable names of the hand types.
export let handTypeNames = ["4 of a Kind", "Straight Flush", "Straight", "Flush", "High Card",
"1 Pair", "2 Pair", "Royal Flush", "3 of a Kind", "Full House", "-Invalid-"];
/// Strength of each hand type.
///
/// Due to nature of the algorithm, the hand types not
/// have order corresponding to the strength or cards combinations.
///
export let handTypeRanks = [8, 9, 5, 6, 1, 2, 3, 10, 4, 7, 0];
/**
* Gets type of hand for given hand representation.
* @param hand 5 cards hand for which current combination should be calculated.
* @return Hand type.
* @description To obtain real strength of hand type, use hand
* type returned from this function as index to the handTypeRanks array.
*/
export function getHandType(hand: HandRepresentation) {
let rankCountBitMask = 0; // index of the card.
let o = 0;
let rankBitMask;
for (let i = -1; i < 5; i++, o = Math.pow(2, hand.Cards[i] * 4)) {
rankCountBitMask += o * ((rankCountBitMask / o & 15) + 1);
}
// tslint:disable-next-line:no-conditional-assignment
if ((rankCountBitMask %= 15) !== 5) {
return rankCountBitMask - 1;
}
rankBitMask = 1 << hand.Cards[0]
| 1 << hand.Cards[1]
| 1 << hand.Cards[2]
| 1 << hand.Cards[3]
| 1 << hand.Cards[4];
rankCountBitMask -= ((rankBitMask / (rankBitMask & -rankBitMask) === 31) || (rankBitMask === 0x403c) ? 3 : 1);
// tslint:disable-next-line:max-line-length
const isSameSuit = (hand.Suits[0] === (hand.Suits[0] | hand.Suits[1] | hand.Suits[2] | hand.Suits[3] | hand.Suits[4])) as any;
return rankCountBitMask - isSameSuit * ((rankBitMask === 0x7c00) ? -5 : 1);
}
export function cardValue(v: number) {
if (v === 14) {
return "A";
}
if (v === 13) {
return "K";
}
if (v === 12) {
return "Q";
}
if (v === 11) {
return "J";
}
return v.toString();
}
export function getHandTypeEx(hand: HandRepresentation) {
// tslint:disable-next-line:max-line-length
const isSameSuit = hand.Suits[0] === (hand.Suits[0] | hand.Suits[1] | hand.Suits[2] | hand.Suits[3] | hand.Suits[4]);
let sortedCards = hand.Cards.slice(0);
sortedCards = sortedCards.sort(function (left, right) {
if (left < right) {
return 1;
}
if (left > right) {
return -1;
}
return 0;
});
let isStraight = sortedCards[0] === sortedCards[1] + 1
&& sortedCards[1] === sortedCards[2] + 1
&& sortedCards[2] === sortedCards[3] + 1
&& sortedCards[3] === sortedCards[4] + 1;
if (!isStraight) {
isStraight = sortedCards[3] === sortedCards[4] + 1
&& sortedCards[1] === sortedCards[2] + 1
&& sortedCards[2] === sortedCards[3] + 1
&& sortedCards[4] === 2
&& sortedCards[0] === 14;
}
if (isSameSuit && isStraight) {
return {
Type: 1,
Cards: [sortedCards[0]],
};
}
const isCare = (sortedCards[0] === sortedCards[1]
&& sortedCards[1] === sortedCards[2]
&& sortedCards[2] === sortedCards[3])
|| (sortedCards[1] === sortedCards[2]
&& sortedCards[2] === sortedCards[3]
&& sortedCards[3] === sortedCards[4]);
if (isCare) {
if (sortedCards[0] === sortedCards[1]) {
return {
Type: 0,
Cards: [sortedCards[0], sortedCards[4]],
};
}
return {
Type: 0,
Cards: [sortedCards[1], sortedCards[0]],
};
}
const isFullHouse = (sortedCards[0] === sortedCards[1]
&& sortedCards[1] === sortedCards[2]
&& sortedCards[3] === sortedCards[4])
|| (sortedCards[0] === sortedCards[1]
&& sortedCards[2] === sortedCards[3]
&& sortedCards[3] === sortedCards[4]);
if (isFullHouse) {
return {
Type: 9,
Cards: [sortedCards[1], sortedCards[3]],
};
}
if (isSameSuit) {
return {
Type: 3,
Cards: sortedCards,
};
}
if (isStraight) {
return {
Type: 2,
Cards: [Math.max(sortedCards[0], sortedCards[4])],
};
}
// is triple
if (sortedCards[0] === sortedCards[1]
&& sortedCards[1] === sortedCards[2]) {
return {
Type: 8,
Cards: [sortedCards[0], sortedCards[3], sortedCards[4]],
};
}
if (sortedCards[1] === sortedCards[2]
&& sortedCards[2] === sortedCards[3]) {
return {
Type: 8,
Cards: [sortedCards[1], sortedCards[0], sortedCards[4]],
};
}
if (sortedCards[2] === sortedCards[3]
&& sortedCards[3] === sortedCards[4]) {
return {
Type: 8,
Cards: [sortedCards[2], sortedCards[0], sortedCards[1]],
};
}
// is 2 pair
if (sortedCards[0] === sortedCards[1]
&& sortedCards[2] === sortedCards[3]) {
return {
Type: 6,
Cards: [sortedCards[0], sortedCards[2], sortedCards[4]],
};
}
if (sortedCards[0] === sortedCards[1]
&& sortedCards[3] === sortedCards[4]) {
return {
Type: 6,
Cards: [sortedCards[0], sortedCards[3], sortedCards[2]],
};
}
if (sortedCards[1] === sortedCards[2]
&& sortedCards[3] === sortedCards[4]) {
return {
Type: 6,
Cards: [sortedCards[1], sortedCards[3], sortedCards[0]],
};
}
// is pair
if (sortedCards[0] === sortedCards[1]) {
return {
Type: 5,
Cards: [sortedCards[0], sortedCards[2], sortedCards[3], sortedCards[4]],
};
}
if (sortedCards[1] === sortedCards[2]) {
return {
Type: 5,
Cards: [sortedCards[1], sortedCards[0], sortedCards[3], sortedCards[4]],
};
}
if (sortedCards[2] === sortedCards[3]) {
return {
Type: 5,
Cards: [sortedCards[2], sortedCards[0], sortedCards[1], sortedCards[4]],
};
}
if (sortedCards[3] === sortedCards[4]) {
return {
Type: 5,
Cards: [sortedCards[3], sortedCards[0], sortedCards[1], sortedCards[2]],
};
}
return {
Type: 4,
Cards: sortedCards,
};
}
export function getCombinations(k: number, n: number) {
///
///
/// Generates all possible unordered permutations
/// of k element in the set with n elements.
///
///
const result: number[][] = [];
const combination: number[] = [];
function next_comb(comb: number[], k1: number, n1: number) {
let i: number;
if (comb.length === 0) {
for (i = 0; i < k1; ++i) {
comb[i] = i;
}
return true;
}
i = k1 - 1; ++comb[i];
while ((i > 0) && (comb[i] >= n1 - k1 + 1 + i)) {
--i; ++comb[i];
}
// No more combinations can be generated
if (comb[0] > n1 - k1) {
return false;
}
for (i = i + 1; i < k1; ++i) {
comb[i] = comb[i - 1] + 1;
}
return true;
}
while (next_comb(combination, k, n)) {
const nextPermutation = combination.slice(null);
result.push(nextPermutation);
}
return result;
}
export function decodeScore(score: number) {
const result = [];
result.push((score & 15));
score = score >> 4;
result.push((score & 15));
score = score >> 4;
result.push((score & 15));
score = score >> 4;
result.push((score & 15));
return result;
}
export function getPokerScore(cards: number[]) {
const tempCards = cards.slice(0);
const cardsCount = {};
for (let i = 0; i < 5; i++) {
const tempCardValue = tempCards[i];
cardsCount[tempCardValue] = (cardsCount[tempCardValue] >= 1) ? cardsCount[tempCardValue] + 1 : 1;
}
tempCards.sort(function (left, right) {
if (cardsCount[left] < cardsCount[right]) {
return 1;
}
if (cardsCount[left] > cardsCount[right]) {
return -1;
}
return right - left;
});
return tempCards[0] << 16
| tempCards[1] << 12
| tempCards[2] << 8
| tempCards[3] << 4
| tempCards[4];
}
export function parseHand(str: string): HandParseResult {
/// Convert string to internal representation.
/// String representation of the hand which should be converted.
/// Parsing result of internal conversion.
if (str.match(/((?:\s*)(10|[2-9]|[J|Q|K|A])[♠|♣|♥|♦](?:\s*)){1,7}/g) === null) {
return { Status: HandParseResultStatus.InvalidHand };
}
const cardStr = str.replace(/A/g, "14").replace(/K/g, "13").replace(/Q/g, "12")
.replace(/J/g, "11").replace(/♠|♣|♥|♦/g, ",");
const cards = cardStr.replace(/\s/g, "").slice(0, -1).split(",") as any[];
const suits = str.match(/♠|♣|♥|♦/g) as any[];
if (cards === null) {
return { Status: HandParseResultStatus.CardsMissing };
}
if (suits === null) {
return { Status: HandParseResultStatus.SuitsMissing };
}
if (cards.length !== suits.length) {
return { Status: HandParseResultStatus.AllCardsShouldHaveOneSuit };
}
const o = {};
let keyCount = 0;
for (let i = 0; i < cards.length; i++) {
const e = cards[i] + suits[i];
o[e] = 1;
}
for (const j in o) {
if (o.hasOwnProperty(j)) {
keyCount++;
}
}
const insufficientCards = cards.length < 5;
const duplicateCards = cards.length !== keyCount;
let status: HandParseResultStatus;
if (insufficientCards && duplicateCards) {
status = HandParseResultStatus.InsufficientCardsAndDuplicates;
} else if (insufficientCards) {
status = HandParseResultStatus.InsufficientCards;
} else if (duplicateCards) {
status = HandParseResultStatus.DuplicatesDetected;
} else {
status = HandParseResultStatus.Ok;
}
for (let i = 0; i < cards.length; i++) {
cards[i] -= 0; // Conversion to the numbers.
}
for (let i = 0; i < suits.length; i++) {
// Convert the suit characters to the numbers.
// Since unicode symbols starts with 0x2660 (9824) Unicode symbols
suits[i] = Math.pow(2, (suits[i].charCodeAt(0) - 9824));
}
return {
Status: status,
Hand: { Cards: cards, Suits: suits },
};
}
/**
* Get universal hand strength
* @param hand Hand representation for which strength should be converted.
* @return Rank of the card across all possible hands.
* @description This function could accept hands from 5 to 7 cards.
*/
export function getCardRank(hand: HandRepresentation): CardRank {
const totalCardsCount = hand.Cards.length;
const permutations = getCombinations(5, totalCardsCount);
let maxRank = 0;
let winIndex = 10;
let winningScore = -1;
let wci: number[];
// Generate permuted version of the original array.
const applyPermutation5 = function (source: number[], permutation: number[]) {
return [
source[permutation[0]],
source[permutation[1]],
source[permutation[2]],
source[permutation[3]],
source[permutation[4]],
];
};
for (let i = 0; i < permutations.length; i++) {
const currentPermutation = permutations[i];
const cs = applyPermutation5(hand.Cards, currentPermutation);
const ss = applyPermutation5(hand.Suits, currentPermutation);
const index = getHandType({ Cards: cs, Suits: ss });
if (handTypeRanks[index] > maxRank) {
maxRank = handTypeRanks[index];
winIndex = index;
wci = currentPermutation.slice(0);
winningScore = getPokerScore(cs);
} else if (handTypeRanks[index] === maxRank) {
// If by chance we have a tie, find the best one
const score1 = getPokerScore(cs);
if (score1 > winningScore) {
wci = currentPermutation.slice(0);
}
}
}
return {
HandType: winIndex,
Score: winningScore,
WinnerCardsSet: wci,
};
}
}