UNPKG

@xpressit/winning-poker-hand-rank

Version:
307 lines (272 loc) 8.03 kB
import { cactusFastRankHand } from './cactusFastRankHand'; import { Card } from './card'; type RankerFunc = (hand: [Card, Card, Card, Card, Card]) => number; export const ROYAL_FLUSH = 1; export const STRAIGHT_FLUSH = 10; export const FOUR_OF_A_KIND = 166; export const FULL_HOUSE = 322; export const FLUSH = 1599; export const STRAIGHT = 1609; export const THREE_OF_A_KIND = 2467; export const TWO_PAIR = 3325; export const PAIR = 6185; export const NOTHING = 7462; export const HIGH_CARD = NOTHING; export const INVALID = 65535; export const SIXPLUS_FLUSH = 166 + 1277; export const SIXPLUS_FULL_HOUSE = SIXPLUS_FLUSH + 156; export const EIGHT_OR_BETTER_MAX = 512; const t7c5 = [ [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 5, 4, 6], [0, 1, 2, 3, 6, 4, 5], [0, 1, 2, 4, 5, 3, 6], [0, 1, 2, 4, 6, 3, 5], [0, 1, 2, 5, 6, 3, 4], [0, 1, 3, 4, 5, 2, 6], [0, 1, 3, 4, 6, 2, 5], [0, 1, 3, 5, 6, 2, 4], [0, 1, 4, 5, 6, 2, 3], [0, 2, 3, 4, 5, 1, 6], [0, 2, 3, 4, 6, 1, 5], [0, 2, 3, 5, 6, 1, 4], [0, 2, 4, 5, 6, 1, 3], [0, 3, 4, 5, 6, 1, 2], [1, 2, 3, 4, 5, 0, 6], [1, 2, 3, 4, 6, 0, 5], [1, 2, 3, 5, 6, 0, 4], [1, 2, 4, 5, 6, 0, 3], [1, 3, 4, 5, 6, 0, 2], [2, 3, 4, 5, 6, 0, 1], ]; export type HandRank = { rank: number; madeHand: [Card, Card, Card, Card, Card]; low?: { rank: number; madeHand: [Card, Card, Card, Card, Card]; }; }; const rank567cardHand = (hand: Card[], f: RankerFunc = cactusFastRankHand): HandRank => { if (hand.length === 5) { const rank = f([hand[0], hand[1], hand[2], hand[3], hand[4]]); return { rank, madeHand: [hand[0], hand[1], hand[2], hand[3], hand[4]], }; } if (hand.length === 6) { const possibleHands: [Card, Card, Card, Card, Card][] = [ [hand[0], hand[1], hand[2], hand[3], hand[4]], [hand[0], hand[1], hand[2], hand[3], hand[5]], [hand[0], hand[1], hand[2], hand[4], hand[5]], [hand[0], hand[1], hand[3], hand[4], hand[5]], [hand[0], hand[2], hand[3], hand[4], hand[5]], [hand[1], hand[2], hand[3], hand[4], hand[5]], ]; const sortedHands = possibleHands .map((h) => ({ rank: f(h), madeHand: h, })) .sort((a, b) => a.rank - b.rank); return sortedHands[0]; } if (hand.length === 7) { let r = 0; let rank = 9999; let bestHand: [Card, Card, Card, Card, Card] = [hand[0], hand[1], hand[2], hand[3], hand[4]]; for (let i = 0; i < 21; i++) { const inputHand: [Card, Card, Card, Card, Card] = [ hand[t7c5[i][0]], hand[t7c5[i][1]], hand[t7c5[i][2]], hand[t7c5[i][3]], hand[t7c5[i][4]], ]; r = f(inputHand); if (r < rank) { rank = r; bestHand = inputHand; } } return { rank, madeHand: bestHand, }; } throw new Error(`Hand ranker doesn't support ${hand.length} cards`); }; export const toFixedTexasRank = (r: number) => { if (r === ROYAL_FLUSH) { return ROYAL_FLUSH; } if (r <= STRAIGHT_FLUSH) { return STRAIGHT_FLUSH; } if (r <= FOUR_OF_A_KIND) { return FOUR_OF_A_KIND; } if (r <= FULL_HOUSE) { return FULL_HOUSE; } if (r <= FLUSH) { return FLUSH; } if (r <= STRAIGHT) { return STRAIGHT; } if (r <= THREE_OF_A_KIND) { return THREE_OF_A_KIND; } if (r <= TWO_PAIR) { return TWO_PAIR; } if (r <= PAIR) { return PAIR; } if (r != INVALID) { return NOTHING; } return INVALID; }; export const toFixedSixPlusRank = (r: number) => { if (r === ROYAL_FLUSH) { return ROYAL_FLUSH; } if (r <= STRAIGHT_FLUSH) { return STRAIGHT_FLUSH; } if (r <= FOUR_OF_A_KIND) { return FOUR_OF_A_KIND; } if (r <= SIXPLUS_FLUSH) { return SIXPLUS_FLUSH; } if (r <= SIXPLUS_FULL_HOUSE) { return SIXPLUS_FULL_HOUSE; } if (r <= STRAIGHT) { return STRAIGHT; } if (r <= THREE_OF_A_KIND) { return THREE_OF_A_KIND; } if (r <= TWO_PAIR) { return TWO_PAIR; } if (r <= PAIR) { return PAIR; } if (r != INVALID) { return NOTHING; } return INVALID; }; export const rankTexasHand = (pocket: Card[], board: Card[]): HandRank => { return rank567cardHand([...pocket, ...board], cactusFastRankHand); }; export const convertToSixPlusHandRank = (handRank: number): number => { if (handRank === 747) { handRank = 6; } else if (handRank === 6610) { handRank = 1605; } // Swap full_house with flush const fixedRank = toFixedTexasRank(handRank); if (fixedRank === FULL_HOUSE) { handRank = handRank - FOUR_OF_A_KIND + 166 + 1277; } else if (fixedRank === FLUSH) { handRank = handRank - FULL_HOUSE + 166; } return handRank; }; export const rankShortDeckHand = (pocket: Card[], board: Card[]): HandRank => { const f = (hand: [Card, Card, Card, Card, Card]) => { const handRank = cactusFastRankHand(hand); return convertToSixPlusHandRank(handRank); }; return rank567cardHand([...pocket, ...board], f); }; const t4c2 = [ [0, 1, 2, 3], [0, 2, 1, 3], [0, 3, 1, 2], [1, 2, 0, 3], [1, 3, 0, 2], [2, 3, 0, 1], ]; const t5c3 = [ [0, 1, 2, 3, 4], [0, 1, 3, 2, 4], [0, 1, 4, 2, 3], [0, 2, 3, 1, 4], [0, 2, 4, 1, 3], [0, 3, 4, 1, 2], [1, 2, 3, 0, 4], [1, 2, 4, 0, 3], [1, 3, 4, 0, 2], [2, 3, 4, 0, 1], ]; // AceRank returns the card [Ace]-low index. export const getAceRank = (card: Card) => { // int(c>>8&0xf+1) % 13 return (((card >> 8) & 0xf) + 1) % 13; }; export const rankAceFiveLow = (mask: number, hand: [Card, Card, Card, Card, Card]) => { let rank = 0; for (const card of hand) { const n = getAceRank(card); rank |= (1 << n) | ((((mask & (1 << n)) >>> n) & 1) * 0x8000); mask |= 1 << n; } return rank; }; // RankEightOrBetter is a 8-or-better low rank eval func. [Ace]'s are low, // [Straight]'s and [Flush]'s do not count. export const rankEightOrBetter = (hand: [Card, Card, Card, Card, Card]) => { return rankAceFiveLow(0xff00, hand); }; export const rankOmahaHand = (pocket: Card[], board: Card[], low = false): HandRank => { let bestHand: [Card, Card, Card, Card, Card] = [pocket[0], pocket[1], pocket[2], pocket[3], board[0]]; let bestRank = INVALID; let lowBestHand: [Card, Card, Card, Card, Card] = [pocket[0], pocket[1], pocket[2], pocket[3], board[0]]; let lowBestRank = INVALID; for (let i = 0; i < 6; i++) { for (let j = 0; j < 10; j++) { const hand: [Card, Card, Card, Card, Card] = [ pocket[t4c2[i][0]], pocket[t4c2[i][1]], board[t5c3[j][0]], board[t5c3[j][1]], board[t5c3[j][2]], ]; const rank = cactusFastRankHand(hand); if (rank < bestRank) { bestHand = hand; bestRank = rank; } if (low) { const lowRank = rankEightOrBetter(hand); if (lowRank < lowBestRank) { lowBestHand = hand; lowBestRank = lowRank; } } } } if (low && lowBestRank < EIGHT_OR_BETTER_MAX) { return { rank: bestRank, madeHand: bestHand, low: { rank: lowBestRank, madeHand: lowBestHand, }, }; } return { rank: bestRank, madeHand: bestHand, }; };