-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
determine hand from hand mask & value mask
The `hand` property value of `EvaluatedHand` objects returned by the `evaluate` function now order matching cards by suit alphabetically (c, d, h, s) rather than the order they appeared in the provided hole card & community card arrays. BREAKING CHANGE
- Loading branch information
Showing
5 changed files
with
190 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { | ||
ALL_SUITS, | ||
Hand, | ||
HandStrength, | ||
Rank, | ||
Suit, | ||
getRank, | ||
isRank, | ||
} from '@poker-apprentice/types'; | ||
import { assertNever } from 'assert-never'; | ||
import { | ||
CARD_1_BIT_SHIFT, | ||
CARD_2_BIT_SHIFT, | ||
CARD_3_BIT_SHIFT, | ||
CARD_4_BIT_SHIFT, | ||
CARD_5_BIT_SHIFT, | ||
CARD_MASK, | ||
HAND_MASK_BIT_SHIFT, | ||
RANK_BITS_MAP, | ||
} from '../constants/bitmasks'; | ||
import { rankOrder } from '../constants/rankOrder'; | ||
import { EvaluatedHand } from '../types'; | ||
import { findKey } from './findKey'; | ||
import { getRankMask } from './getHandMask'; | ||
import { getHandValueMask } from './getHandValueMask'; | ||
import { getMaskedCardRank } from './getMaskedCardRank'; | ||
import { getSuitedRankMasks } from './getSuitedRankMasks'; | ||
|
||
const constructHand = ( | ||
handMask: bigint, | ||
cardMasks: bigint[], | ||
maskIndices: [number, number, number, number, number], | ||
isSuited = false, | ||
): Hand => { | ||
const suits = getSuitedRankMasks(handMask); | ||
|
||
const getReferencedRank = (hand: Hand, offset: number) => { | ||
const referencedCard = hand[hand.length - 1]; | ||
const referencedRank = rankOrder.indexOf(getRank(referencedCard)); | ||
return rankOrder.at((referencedRank + offset + 13) % 13) as Rank; | ||
}; | ||
|
||
if (isSuited) { | ||
const combinedCardMasks = cardMasks.reduce( | ||
(acc, current) => acc | getRankMask(getMaskedCardRank(current)), | ||
0n, | ||
); | ||
for (const suit of ALL_SUITS) { | ||
const suitedCardsMask = suits[suit]; | ||
if ((suitedCardsMask & combinedCardMasks) === combinedCardMasks) { | ||
return maskIndices.reduce((hand: Hand, maskIndex) => { | ||
const rank = | ||
maskIndex >= 0 | ||
? getMaskedCardRank(cardMasks[maskIndex]) | ||
: getReferencedRank(hand, maskIndex); | ||
hand.push(`${rank}${suit}`); | ||
return hand; | ||
}, []); | ||
} | ||
} | ||
} | ||
|
||
const getMatchingSuit = ( | ||
hand: Hand, | ||
maskIndex: number, | ||
): [bigint, Suit] | [undefined, undefined] => { | ||
if (maskIndex >= 0) { | ||
const cardMask = cardMasks[maskIndex]; | ||
const rankMask = getRankMask(getMaskedCardRank(cardMask)); | ||
const matchingSuit = findKey( | ||
suits, | ||
(suitedCardsMask) => (rankMask & suitedCardsMask) === rankMask, | ||
); | ||
if (matchingSuit) { | ||
return [cardMask, matchingSuit]; | ||
} | ||
} | ||
|
||
const nextRank = getReferencedRank(hand, maskIndex); | ||
if (nextRank !== undefined && isRank(nextRank)) { | ||
const rankMask = getRankMask(nextRank); | ||
const matchingSuit = findKey( | ||
suits, | ||
(suitedCardsMask) => (rankMask & suitedCardsMask) === rankMask, | ||
); | ||
if (matchingSuit) { | ||
return [RANK_BITS_MAP[nextRank], matchingSuit]; | ||
} | ||
} | ||
|
||
return [undefined, undefined]; | ||
}; | ||
|
||
return maskIndices.reduce((hand: Hand, maskIndex) => { | ||
const [cardMask, matchingSuit] = getMatchingSuit(hand, maskIndex); | ||
if (matchingSuit) { | ||
suits[matchingSuit] -= getRankMask(getMaskedCardRank(cardMask)); | ||
hand.push(`${getMaskedCardRank(cardMask)}${matchingSuit}`); | ||
} | ||
return hand; | ||
}, []); | ||
}; | ||
|
||
export const getHand = (handMask: bigint, handValueMask: bigint, strength: HandStrength): Hand => { | ||
const cardMasks = [ | ||
(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, | ||
]; | ||
|
||
switch (strength) { | ||
case HandStrength.HighCard: | ||
return constructHand(handMask, cardMasks, [0, 1, 2, 3, 4]); | ||
case HandStrength.OnePair: | ||
return constructHand(handMask, cardMasks, [0, 0, 1, 2, 3]); | ||
case HandStrength.TwoPair: | ||
return constructHand(handMask, cardMasks, [0, 0, 1, 1, 2]); | ||
case HandStrength.ThreeOfAKind: | ||
return constructHand(handMask, cardMasks, [0, 0, 0, 1, 2]); | ||
case HandStrength.Straight: | ||
return constructHand(handMask, cardMasks, [0, -1, -1, -1, -1]); | ||
case HandStrength.Flush: | ||
return constructHand(handMask, cardMasks, [0, 1, 2, 3, 4], true); | ||
case HandStrength.FullHouse: | ||
return constructHand(handMask, cardMasks, [0, 0, 0, 1, 1]); | ||
case HandStrength.FourOfAKind: | ||
return constructHand(handMask, cardMasks, [0, 0, 0, 0, 1]); | ||
case HandStrength.StraightFlush: | ||
return constructHand(handMask, cardMasks, [0, -1, -1, -1, -1], true); | ||
case HandStrength.RoyalFlush: | ||
return constructHand(handMask, cardMasks, [0, -1, -1, -1, -1], true); | ||
default: | ||
return assertNever(strength); | ||
} | ||
}; | ||
|
||
export const getStrength = (handMask: bigint): HandStrength => { | ||
const baseStrength: HandStrength = Number(handMask >> HAND_MASK_BIT_SHIFT); | ||
const highCardRank = getMaskedCardRank((handMask >> CARD_1_BIT_SHIFT) & CARD_MASK); | ||
|
||
const strength = | ||
baseStrength === HandStrength.StraightFlush && highCardRank === 'A' | ||
? HandStrength.RoyalFlush | ||
: baseStrength; | ||
|
||
return strength; | ||
}; | ||
|
||
export const evaluateHandMask = (handMask: bigint): EvaluatedHand => { | ||
const value = getHandValueMask(handMask); | ||
const strength = getStrength(value); | ||
const hand = getHand(handMask, value, strength); | ||
|
||
return { strength, hand, value }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
type Entries<T> = { | ||
[K in keyof T]-?: [K, T[K]]; | ||
}[keyof T][]; | ||
|
||
const getEntries = <T extends object>(obj: T) => Object.entries(obj) as Entries<T>; | ||
|
||
export const findKey = <K extends PropertyKey, V>( | ||
obj: Record<K, V>, | ||
predicate: (v: V) => boolean, | ||
): K | undefined => { | ||
const [key] = getEntries(obj).find(([_key, value]) => predicate(value)) ?? []; | ||
return key; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters