Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance refactor #6

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A collection of useful functions for determining the strongest possible hand giv

The following types are defined & utilized by this package.

- `EvaluatedHand`: An object representing the effective hand & strength, given a coordination of cards.
- `EvaluatedHand`: An object representing the effective hand, strength, and calculated value, given a coordination of cards.
- `Odds`: An object representing how a hand will perform given a scenario. Includes the number of `wins`, `ties`, and `total` possible outcomes.

### Core Functions
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"prepublishOnly": "yarn clean && yarn lint && yarn format && yarn test && yarn build"
},
"dependencies": {
"@poker-apprentice/types": "^1.4.0",
"@poker-apprentice/types": "^1.4.2",
"assert-never": "^1.2.1",
"lodash": "^4.17.21",
"v8-profiler-next": "^1.10.0"
},
Expand Down
25 changes: 25 additions & 0 deletions src/__tests__/evaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,47 @@ describe('evaluate', () => {
expect(evaluate({ holeCards: ['Qh', 'Qd', 'Td', 'Qs', 'Kd', 'Ad', 'Jd'] })).toEqual({
strength: HandStrength.RoyalFlush,
hand: ['Ad', 'Kd', 'Qd', 'Jd', 'Td'],
value: 135004160n,
});
});

it('recognizes straight flushes', () => {
expect(evaluate({ holeCards: ['Qh', 'Qd', 'Td', 'Qs', 'Kd', '9d', 'Jd'] })).toEqual({
strength: HandStrength.StraightFlush,
hand: ['Kd', 'Qd', 'Jd', 'Td', '9d'],
value: 134938624n,
});
});

it('recognizes straight flushes with ace treated as low', () => {
expect(evaluate({ holeCards: ['Qh', '5d', '2d', '3d', '8d', 'Ad', '4d'] })).toEqual({
strength: HandStrength.StraightFlush,
hand: ['5d', '4d', '3d', '2d', 'Ad'],
value: 134414336n,
});
});

it('recognizes four of a kind', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', 'Qs', 'Qc', 'Qh'] })).toEqual({
strength: HandStrength.FourOfAKind,
hand: ['Qd', 'Qs', 'Qc', 'Qh', 'As'],
value: 118145024n,
});
});

it('recognizes full houses', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', 'Qs', 'Jc', 'Qh'] })).toEqual({
strength: HandStrength.FullHouse,
hand: ['Qd', 'Qs', 'Qh', 'Js', 'Jc'],
value: 101355520n,
});
});

it('recognizes stronger full houses', () => {
expect(evaluate({ holeCards: ['Js', 'Qd', 'Jc', 'Qs', 'Ac', 'Qh', 'Ah'] })).toEqual({
strength: HandStrength.FullHouse,
hand: ['Qd', 'Qs', 'Qh', 'Ac', 'Ah'],
value: 101367808n,
});
});

Expand All @@ -55,48 +61,63 @@ describe('evaluate', () => {
).toEqual({
strength: HandStrength.FullHouse,
hand: ['Kc', 'Kd', 'Kh', '5d', '5c'],
value: 101396480n,
});
});

it('recognizes flushes', () => {
expect(evaluate({ holeCards: ['Js', 'Qd', '8s', '4s', '6c', 'Qs', 'As'] })).toEqual({
strength: HandStrength.Flush,
hand: ['As', 'Qs', 'Js', '8s', '4s'],
value: 84715874n,
});
});

it('recognizes straights', () => {
expect(evaluate({ holeCards: ['Qh', 'Qd', 'Td', 'Qs', 'Kh', '9d', 'Jc'] })).toEqual({
strength: HandStrength.Straight,
hand: ['Kh', 'Qh', 'Jc', 'Td', '9d'],
value: 67829760n,
});
});

it('recognizes straights with ace treated as low', () => {
expect(evaluate({ holeCards: ['Qh', '5d', '2h', '3d', '8c', 'As', '4d'] })).toEqual({
strength: HandStrength.Straight,
hand: ['5d', '4d', '3d', '2h', 'As'],
value: 67305472n,
});
});

it('recognizes three of a kind', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', 'Qs', 'Qc', '2h'] })).toEqual({
strength: HandStrength.ThreeOfAKind,
hand: ['Qd', 'Qs', 'Qc', 'As', 'Js'],
value: 51038464n,
});
});

it('recognizes two pair', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', 'Qs', '2h', 'Jh'] })).toEqual({
strength: HandStrength.TwoPair,
hand: ['Qd', 'Qs', 'Js', 'Jh', 'As'],
value: 34249728n,
});
});

it('recognizes one pair', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', 'Qs', '2h', '3h'] })).toEqual({
strength: HandStrength.OnePair,
hand: ['Qd', 'Qs', 'As', 'Js', '3h'],
value: 17484048n,
});
});

it('recognizes high card', () => {
expect(evaluate({ holeCards: ['As', 'Qd', 'Js', '8s', '2h', '3h'] })).toEqual({
strength: HandStrength.HighCard,
hand: ['As', 'Qd', 'Js', '8s', '3h'],
value: 829793n,
});
});

Expand All @@ -111,6 +132,7 @@ describe('evaluate', () => {
expect(omahaHand).toEqual({
strength: HandStrength.Straight,
hand: ['5d', '4s', '3d', '2s', 'As'],
value: 67305472n,
});
});

Expand All @@ -123,6 +145,7 @@ describe('evaluate', () => {
expect(pineappleHand).toEqual({
strength: HandStrength.HighCard,
hand: ['Jc', 'Tc', '9h', '8c', '2d'],
value: 624480n,
});
});
});
Expand All @@ -137,6 +160,7 @@ describe('evaluate', () => {
).toEqual({
strength: HandStrength.HighCard,
hand: ['Kh', 'Jc'],
value: 757760n,
});

expect(
Expand All @@ -148,6 +172,7 @@ describe('evaluate', () => {
).toEqual({
strength: HandStrength.OnePair,
hand: ['As', 'Ad'],
value: 17563648n,
});
});
});
7 changes: 3 additions & 4 deletions src/compare.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { EvaluatedHand } from './types';
import { handComparator } from './utils/handComparator';

export const compare = (a: EvaluatedHand, b: EvaluatedHand) => {
if (a.strength === b.strength) {
return handComparator(a.hand, b.hand);
if (a.value === b.value) {
return 0;
}
return a.strength < b.strength ? 1 : -1;
return a.value < b.value ? 1 : -1;
};
61 changes: 61 additions & 0 deletions src/constants/bitmasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { HandStrength, Rank, Suit } from '@poker-apprentice/types';

const SUIT_CLUBS = 0n;
const SUIT_DIAMONDS = 1n;
const SUIT_HEARTS = 2n;
const SUIT_SPADES = 3n;

export const MASK_OFFSET_CLUBS = 13n * SUIT_CLUBS;
export const MASK_OFFSET_DIAMONDS = 13n * SUIT_DIAMONDS;
export const MASK_OFFSET_HEARTS = 13n * SUIT_HEARTS;
export const MASK_OFFSET_SPADES = 13n * SUIT_SPADES;

export const RANK_MASK = 0b1111111111111n;

export const CARD_BIT_WIDTH = 4n;
export const CARD_5_BIT_SHIFT = 0n;
export const CARD_4_BIT_SHIFT = CARD_BIT_WIDTH + CARD_5_BIT_SHIFT; // 4n
export const CARD_3_BIT_SHIFT = CARD_BIT_WIDTH + CARD_4_BIT_SHIFT; // 8n
export const CARD_2_BIT_SHIFT = CARD_BIT_WIDTH + CARD_3_BIT_SHIFT; // 12n
export const CARD_1_BIT_SHIFT = CARD_BIT_WIDTH + CARD_2_BIT_SHIFT; // 16n
export const HAND_MASK_BIT_SHIFT = 24n;

export const CARD_MASK = 0x0fn;
export const CARD_1_MASK = 0x000f0000n;
export const CARD_2_MASK = 0x0000f000n;
export const CARD_3_MASK = 0x00000f00n;
export const CARD_4_MASK = 0x000000f0n;
export const CARD_5_MASK = 0x0000000fn;

export const HAND_MASK_HIGH_CARD = BigInt(HandStrength.HighCard) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_ONE_PAIR = BigInt(HandStrength.OnePair) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_TWO_PAIR = BigInt(HandStrength.TwoPair) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_THREE_OF_A_KIND = BigInt(HandStrength.ThreeOfAKind) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_STRAIGHT = BigInt(HandStrength.Straight) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_FLUSH = BigInt(HandStrength.Flush) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_FULL_HOUSE = BigInt(HandStrength.FullHouse) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_FOUR_OF_A_KIND = BigInt(HandStrength.FourOfAKind) << HAND_MASK_BIT_SHIFT;
export const HAND_MASK_STRAIGHT_FLUSH = BigInt(HandStrength.StraightFlush) << HAND_MASK_BIT_SHIFT;

export const RANK_BITS_MAP: Record<Rank, bigint> = {
'2': 0n,
'3': 1n,
'4': 2n,
'5': 3n,
'6': 4n,
'7': 5n,
'8': 6n,
'9': 7n,
T: 8n,
J: 9n,
Q: 10n,
K: 11n,
A: 12n,
};

export const SUIT_BITS_MAP: Record<Suit, bigint> = {
c: 0n,
d: 1n,
h: 2n,
s: 3n,
};
Loading
Loading