diff --git a/src/evaluate.ts b/src/evaluate.ts index 3651469..adb5f22 100644 --- a/src/evaluate.ts +++ b/src/evaluate.ts @@ -1,4 +1,4 @@ -import { Card, Hand, HandStrength, Suit, getRank, getSuit } from '@poker-apprentice/types'; +import { Card, Hand, HandStrength, getRank, getSuit } from '@poker-apprentice/types'; import { assertNever } from 'assert-never'; import findKey from 'lodash/findKey'; import { compare } from './compare'; @@ -11,11 +11,15 @@ import { CARD_MASK, HAND_MASK_BIT_SHIFT, } from './constants/bitmasks'; +import { CARD_RANK_TABLE } from './constants/cardRankTable'; import { rankOrder } from './constants/rankOrder'; import { EvaluatedHand } from './types'; +import { bigintKey } from './utils/bigintKey'; import { getCombinations } from './utils/getCombinations'; -import { getEffectiveHandMask } from './utils/getEffectiveHandMask'; +import { getHandMask } from './utils/getHandMask'; +import { getHandValueMask } from './utils/getHandValueMask'; import { getMaskedCardRank } from './utils/getMaskedCardRank'; +import { getSuitedRankMasks } from './utils/getSuitedRankMasks'; export interface EvaluateOptions { holeCards: Card[]; @@ -98,29 +102,12 @@ const take = (array: T[], index: number): T => { return item; }; -const getFlushCards = (cards: Card[]): Card[] => { - const suitCounts = cards.reduce( - (counts, card) => { - counts[getSuit(card)] += 1; - return counts; - }, - { c: 0, d: 0, h: 0, s: 0 } satisfies Record, - ); - - const flushSuit = findKey(suitCounts, (v) => v >= 5) as Suit; - - return cards.filter((card) => getSuit(card) === flushSuit); -}; - const constructHand = ( - originalCards: Card[], + cards: Card[], cardMasks: bigint[], maskIndices: [number, number, number, number, number], - isFlush: boolean = false, -): Hand => { - const cards = isFlush ? getFlushCards(originalCards) : [...originalCards]; - - return maskIndices.reduce((result: Hand, maskIndex, i) => { +): Hand => + maskIndices.reduce((result: Hand, maskIndex, i) => { if (maskIndex >= 0) { const cardMask = cardMasks[maskIndex]; const maskedCardRank = getMaskedCardRank(cardMask); @@ -142,17 +129,27 @@ const constructHand = ( } return result; }, []); -}; -const getHand = (handMask: bigint, strength: HandStrength, cards: Card[]): Hand => { +const getHand = ( + originalCards: Card[], + handMask: bigint, + handValueMask: bigint, + strength: HandStrength, +): Hand => { const cardMasks = [ - (handMask >> CARD_1_BIT_SHIFT) & CARD_MASK, - (handMask >> CARD_2_BIT_SHIFT) & CARD_MASK, - (handMask >> CARD_3_BIT_SHIFT) & CARD_MASK, - (handMask >> CARD_4_BIT_SHIFT) & CARD_MASK, - (handMask >> CARD_5_BIT_SHIFT) & CARD_MASK, + (handValueMask >> CARD_1_BIT_SHIFT) & CARD_MASK, + (handValueMask >> CARD_2_BIT_SHIFT) & CARD_MASK, + (handValueMask >> CARD_3_BIT_SHIFT) & CARD_MASK, + (handValueMask >> CARD_4_BIT_SHIFT) & CARD_MASK, + (handValueMask >> CARD_5_BIT_SHIFT) & CARD_MASK, ]; + const suits = getSuitedRankMasks(handMask); + const flushSuit = findKey(suits, (v) => CARD_RANK_TABLE[bigintKey(v)] >= 5); + const cards = flushSuit + ? originalCards.filter((card) => getSuit(card) === flushSuit) + : originalCards; + switch (strength) { case HandStrength.HighCard: return constructHand(cards, cardMasks, [0, 1, 2, 3, 4]); @@ -165,15 +162,15 @@ const getHand = (handMask: bigint, strength: HandStrength, cards: Card[]): Hand case HandStrength.Straight: return constructHand(cards, cardMasks, [0, -1, -1, -1, -1]); case HandStrength.Flush: - return constructHand(cards, cardMasks, [0, 1, 2, 3, 4], true); + return constructHand(cards, cardMasks, [0, 1, 2, 3, 4]); case HandStrength.FullHouse: return constructHand(cards, cardMasks, [0, 0, 0, 1, 1]); case HandStrength.FourOfAKind: return constructHand(cards, cardMasks, [0, 0, 0, 0, 1]); case HandStrength.StraightFlush: - return constructHand(cards, cardMasks, [0, -1, -1, -1, -1], true); + return constructHand(cards, cardMasks, [0, -1, -1, -1, -1]); case HandStrength.RoyalFlush: - return constructHand(cards, cardMasks, [0, -1, -1, -1, -1], true); + return constructHand(cards, cardMasks, [0, -1, -1, -1, -1]); default: return assertNever(strength); } @@ -190,9 +187,10 @@ const getStrength = (handMask: bigint): HandStrength => { }; const evaluateHand = (cards: Card[]): EvaluatedHand => { - const value = getEffectiveHandMask(cards); + const handMask = getHandMask(cards); + const value = getHandValueMask(handMask); const strength = getStrength(value); - const hand = getHand(value, strength, cards); + const hand = getHand(cards, handMask, value, strength); return { strength, hand, value }; }; diff --git a/src/utils/getEffectiveHandMask.ts b/src/utils/getHandValueMask.ts similarity index 90% rename from src/utils/getEffectiveHandMask.ts rename to src/utils/getHandValueMask.ts index a4dfd61..f100059 100644 --- a/src/utils/getEffectiveHandMask.ts +++ b/src/utils/getHandValueMask.ts @@ -1,4 +1,4 @@ -import { Card } from '@poker-apprentice/types'; +import sum from 'lodash/sum'; import { CARD_1_BIT_SHIFT, CARD_1_MASK, @@ -16,34 +16,25 @@ import { HAND_MASK_STRAIGHT_FLUSH, HAND_MASK_THREE_OF_A_KIND, HAND_MASK_TWO_PAIR, - MASK_OFFSET_CLUBS, - MASK_OFFSET_DIAMONDS, - MASK_OFFSET_HEARTS, - MASK_OFFSET_SPADES, - RANK_MASK, } from '../constants/bitmasks'; import { CARD_RANK_TABLE } from '../constants/cardRankTable'; import { STRAIGHT_TABLE } from '../constants/straightTable'; import { TOP_CARD_TABLE } from '../constants/topCardTable'; import { TOP_FIVE_CARDS_TABLE } from '../constants/topFiveCardsTable'; import { bigintKey } from './bigintKey'; -import { getHandMask } from './getHandMask'; +import { getSuitedRankMasks } from './getSuitedRankMasks'; import { uint } from './uint'; // Returns a bit-mask representing the strength of the best possible hand from the provided cards. -export const getEffectiveHandMask = (cards: Card[]): bigint => { - const handMask = getHandMask(cards); +export const getHandValueMask = (handMask: bigint): bigint => { let retval = 0n; - // seperate out by suit - const sc = uint((handMask >> MASK_OFFSET_CLUBS) & RANK_MASK); - const sd = uint((handMask >> MASK_OFFSET_DIAMONDS) & RANK_MASK); - const sh = uint((handMask >> MASK_OFFSET_HEARTS) & RANK_MASK); - const ss = uint((handMask >> MASK_OFFSET_SPADES) & RANK_MASK); + const { c: sc, d: sd, h: sh, s: ss } = getSuitedRankMasks(handMask); const ranks = sc | sd | sh | ss; const ranksCount = CARD_RANK_TABLE[bigintKey(ranks)]; - const possibleDuplicatesCount = cards.length - ranksCount; + const numCards = sum([sc, sd, sh, ss].map((mask) => CARD_RANK_TABLE[bigintKey(mask)])); + const possibleDuplicatesCount = numCards - ranksCount; // Check for straight, flush, or straight flush, and return if we can // determine immediately that that this is the best possible hand. diff --git a/src/utils/getSuitedRankMasks.ts b/src/utils/getSuitedRankMasks.ts new file mode 100644 index 0000000..6d93a28 --- /dev/null +++ b/src/utils/getSuitedRankMasks.ts @@ -0,0 +1,16 @@ +import { Suit } from '@poker-apprentice/types'; +import { + MASK_OFFSET_CLUBS, + MASK_OFFSET_DIAMONDS, + MASK_OFFSET_HEARTS, + MASK_OFFSET_SPADES, + RANK_MASK, +} from '../constants/bitmasks'; +import { uint } from './uint'; + +export const getSuitedRankMasks = (handMask: bigint): Record => ({ + c: uint((handMask >> MASK_OFFSET_CLUBS) & RANK_MASK), + d: uint((handMask >> MASK_OFFSET_DIAMONDS) & RANK_MASK), + h: uint((handMask >> MASK_OFFSET_HEARTS) & RANK_MASK), + s: uint((handMask >> MASK_OFFSET_SPADES) & RANK_MASK), +});