Skip to content

Commit

Permalink
extract function for reuse, use hand mask to determine flush suit
Browse files Browse the repository at this point in the history
  • Loading branch information
mhuggins committed May 1, 2024
1 parent e21ec03 commit d33e383
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 49 deletions.
66 changes: 32 additions & 34 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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[];
Expand Down Expand Up @@ -98,29 +102,12 @@ const take = <T>(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<Suit, number>,
);

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);
Expand All @@ -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]);
Expand All @@ -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);
}
Expand All @@ -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 };
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Card } from '@poker-apprentice/types';
import sum from 'lodash/sum';
import {
CARD_1_BIT_SHIFT,
CARD_1_MASK,
Expand All @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions src/utils/getSuitedRankMasks.ts
Original file line number Diff line number Diff line change
@@ -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<Suit, bigint> => ({
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),
});

0 comments on commit d33e383

Please sign in to comment.