0.1.0 • Published 1 year ago

holdem v0.1.0

Weekly downloads
-
License
-
Repository
-
Last release
1 year ago

holdem

A fast implementation of Texas Hold'em poker odds calculator and its related data models.

npm version license

  • 🏃‍♀️ Blazing fast odds evaluation
  • ⚙️ Modern TypeScript interface and providing useful helpers
  • 💯 Written in 100% pure universal TypeScript - run on both Node and browsers
  • ✅ Fully tested

API

Rank

A enum-like representation of rank in playing cards.

Rank.Ace;    // => A of playing cards
Rank.Deuce;  // => 2 of playing cards

Rank#parse(char: string): Rank

Parses a char (= 1-charactor-length string) into a Rank.

Rank.parse("A") === Rank.Ace;
Rank.parse("T") === Rank.Ten;
Rank.parse("5") === Rank.Five;

Only "A", "K", "Q", "J", "T", "9", "8", "7", "6", "5", "4", "3" or "2" is acceptable.

Rank.parse("a");  // => Error: "a" is not a valid string value for Rank.parse().

For consistency reason, it needs to be a string even for number-based ranks (e.g. "4").

Rank.parse("4") === Rank.Four;
Rank.parse(4);  // => Error: 4 is not a valid string value for Rank.parse().

Rank#compare(other: Rank): number

Compares two ranks in power order and returns integer compatible with Array#sort().

Rank.Ace.compare(Rank.King);   // => negative integer
Rank.King.compare(Rank.Ace);   // => positive integer
Rank.Ace.compare(Rank.Ace);    // => 0
Rank.Ace.compare(Rank.Deuce);  // => positive integer

Rank#format(): string

Returns a char for the Rank. The returning string is compatible for Rank.parse().

Rank.Ace.format() === "A";
Rank.Ten.format() === "T";
Rank.Five.format() === "5";

Suit

A enum-like representation of suit in playing cards.

Suit.Spade;    // => spade of playing cards
Suit.Diamond;  // => diamond of playing cards

Suit.parse(char: string): Suit

Parses a char (= 1-charactor-length string) into a Suit.

Suit.parse("s") === "s";
Suit.parse("h") === "h";
Suit.parse("d") === "d";
Suit.parse("c") === "c";

Only "s", "h", "d", or "c" is acceptable.

Suit.parse("S");  // => Error: "S" is not a valid string value for Suit.parse().

Suit#compare(other: Suit): number

Compares two ranks in index order and returns integer compatible with Array#sort().

It results in the ordinal order that how it should usually be in poker.

Suit.Spade.compare(Suit.Heart);   // => negative integer
Suit.Club.compare(Suit.Diamond);  // => positive integer
Suit.Heart.compare(Suit.Heart);   // => 0

Suit#format(): string

Returns a char for the Suit. The returning string is compatible for Suit.parse().

Suit.Spade.format() === "s";
Suit.Diamond.format() === "d";

Card

A class representing a piece of playing cards.

Card.parse(expression: string): Card

Parses a string into a Card.

Card.parse("As").equals(new Card(Rank.Ace, Suit.Spade));    // => true
Card.parse("2s").equals(new Card(Rank.Deuce, Suit.Spade));  // => true
Card.parse("Kc").equals(new Card(Rank.King, Suit.Club));    // => true

new Card(rank: Rank, suit: Suit): Card

Creates a Card from a given pair of Rank and Suit.

new Card(Rank.Ace, Suit.Spade);

Card#rank: Rank

Rank of the Card.

new Card(Rank.Ace, Suit.Spade).rank === Rank.Ace;    // => true
new Card(Rank.Trey, Suit.Heart).rank === Rank.Trey;  // => true
new Card(Rank.Ten, Suit.Diamond).rank === Rank.Ten;  // => true
new Card(Rank.Six, Suit.Club).rank === Rank.Six;     // => true

Card#suit: Suit

Suit of the Card.

new Card(Rank.Ace, Suit.Spade).suit === Suit.Spade;      // => true
new Card(Rank.Trey, Suit.Heart).suit === Suit.Heart;     // => true
new Card(Rank.Ten, Suit.Diamond).suit === Suit.Diamond;  // => true
new Card(Rank.Six, Suit.Club).suit === Suit.Club;        // => true

Card#compare(other: Card): number

Compares two cards in power order and returns integer compatible with Array#sort().

new Card(Rank.Ace, Suit.Spade).compare(new Card(Rank.Ace, Suit.Diamond));  // => negative integer
new Card(Rank.Ace, Suit.Diamond).compare(new Card(Rank.Six, Suit.Heart));  // => negative integer
new Card(Rank.Queen, Suit.Spade).compare(new Card(Rank.Ace, Suit.Heart));  // => positive integer
new Card(Rank.Ace, Suit.Club).compare(new Card(Rank.Ace, Suit.Club));      // => 0

Card#equals(other: Card): boolean

Whether the given card has the same rank and suit or not.

new Card(Rank.Ace, Suit.Spade).equals(new Card(Rank.Ace, Suit.Spade));    // => true
new Card(Rank.Ace, Suit.Spade).equals(new Card(Rank.Ace, Suit.Diamond));  // => false
new Card(Rank.Ace, Suit.Spade).equals(new Card(Rank.Deuce, Suit.Spade));  // => false

Card#format(): string

Stringifies a Card.

new Card(Rank.Ace, Suit.Spade).format() === "As";
new Card(Rank.Deuce, Suit.Spade).format() === "2s";
new Card(Rank.King, Suit.Club).format() === "Kc";

CardSet

Immutable and performant set of Card(s).

CardSet.empty(): CardSet

An empty CardSet.

CardSet.full(): CardSet

A CardSet that has all the cards.

CardSet.from(cards: Iterable<Card>): CardSet

Creates a CardSet from Cards.

const cardSet = CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Rank.Club),
  new Card(Rank.Queen, Rank.Queen),
  new Card(Rank.Jack, Rank.Diamond),
]);

CardSet.parse(expression: string): CardSet

Parses a string into CardSet.

CardSetUtils.parse("AsQhJdKc") === CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Rank.Club),
  new Card(Rank.Queen, Rank.Queen),
  new Card(Rank.Jack, Rank.Diamond),
]);  // => true
CardSetUtils.parse("") === CardSetUtils.empty;  // => true

CardSet#size: number

Returns number of cards in a CardSet.

const cardSet = CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Rank.Club),
  new Card(Rank.Queen, Rank.Queen),
  new Card(Rank.Jack, Rank.Diamond),
]);

cardSet.size;          // => 4
CardSet.empty().size;  // => 0
CardSet.full().size;   // => 52

CardSet#has(other: Card | CardSet): boolean

Returns whether a CardSet contains a Card or all the Card in another CardSet.

const cardSet = CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Suit.Club),
  new Card(Rank.Queen, Suit.Heart),
  new Card(Rank.Jack, Suit.Diamond),
]);

cardSet.has(CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.Queen, Suit.Heart),
]));  // => true

cardSet.has(CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.Ace, Suit.Heart),
]));  // => false

cardSet.has(new Card(Rank.Ace, Suit.Spade));  // => true

cardSet.has(CardSet.empty());  // => true

CardSet#at(index: number): Card | null

Returns Card at the index.

const cardSet = CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Suit.Club),
  new Card(Rank.Queen, Suit.Heart),
  new Card(Rank.Jack, Suit.Diamond),
]);

cardSet.at(2);  // => Card<Jd>

CardSet#added(other: Card | CardSet): CardSet

Returns an union of CardSet(s).

CardSetUtils.union(
  CardSet.from([
    new Card(Rank.Ace, Suit.Spade),
    new Card(Rank.Queen, Rank.Queen),
  ]),
  CardSet.from([
    new Card(Rank.King, Rank.Club),
    new Card(Rank.Jack, Rank.Diamond),
  ]),
) === CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.King, Rank.Club),
  new Card(Rank.Queen, Rank.Queen),
  new Card(Rank.Jack, Rank.Diamond),
]);

CardSet#removed(other: Card | CardSet): CardSet

Returns a difference (=relative complement) of CardSet(s).

CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.Queen, Rank.Queen),
]).union(
  CardSet.from([
    new Card(Rank.King, Rank.Club),
    new Card(Rank.Jack, Rank.Diamond),
  ]),
).equals(
  CardSet.from([
    new Card(Rank.Ace, Suit.Spade),
    new Card(Rank.King, Rank.Club),
    new Card(Rank.Queen, Rank.Queen),
    new Card(Rank.Jack, Rank.Diamond),
  ])
);  // => true

CardSet#format(): string

Stringifies a CardSet.

CardSet.from([
  new Card(Rank.Ace, Suit.Spade),
  new Card(Rank.Queen, Rank.Queen),
  new Card(Rank.Jack, Rank.Diamond),
  new Card(Rank.King, Rank.Club),
]).format() === "AsQhJdKc";

HandRange

An immutable map class that represents a set of CardPair(s) and their existance probability.

As HandRange is iterable of [CardSet, number], you can use this in for loop.

for (const [cardSet, probability] of HandRange.parse("88-66")) {
  // ...
}

HandRange.empty(): HandRange

Returns an empty HandRange.

const handRange = HandRange.empty();

HandRange.parse(expression: string): HandRange

Parses a string and returns a HandRange.

The parameter string needs to be comma-separated parts. Each parts should be <hand>:<probability> where <hand> is the following specifiers and <probability> is float.

  • AsKc - specific pair of cards.
  • 66 - all pocket pair combinations of six.
  • AKs - all suited pair combinations of ace and king.
  • T9o - all ofsuit pair combinations of ten and nine.
  • JJ-99 - all pocket combinations of JJ, TT and 99.
  • 86s-84s - all suited combinations of 86s, 85s and 84s.
  • AJo-A9o - all ofsuite combinations of AJo, ATo and A9o.
  • JJ+ - equivalent to AA-JJ.
  • 85s+ - equivalent to 87s-85s.
  • AQo+ - equivalent to AKo-AQo.

The probability can be omitted. AsKs works as same as AsKs:1.

const handRange = HandRange.parse("88-66:0.66,JJ+:0.5,44,AQs-A9s:0.2");

HandRange.from(cardSets: Iterable<CardSet>): HandRange

Returns a HandRange that contains all the given entries of CardSet and its existance probability.

const handRange = HandRange.from([
  CardSet.parse("AsKs"),
  CardSet.parse("8h8d"),
  CardSet.parse("6s5s"),
]);

HandRange#size: number

Number of entries in the HandRange.

const handRange = HandRange.parse("88-66");
handRange.size;  // => 18

HandRange#get(cardSet: CardSet): number | null

Returns probability for the given CardSet.

const handRange = HandRange.parse("88:1,77:0.5,66:0.25");
const probability = handRange.get(CardSet.parse("7s7d"));

HandRange#added(other: HandRange): HandRange

Returns an union of the HandRange and the given one.

const handRange = HandRange.parse("88-66");
const ninesAdded = handRange.added(HandRange.parse("99"));

HandRange#removed(other: HandRange): HandRange

Returns a new HandRange that the given hand range removed.

const handRange = HandRange.parse("88-66");
const sevensRemoved = handRange.removed(HandRange.parse("77"));

HandRange#onlyRankPairs(): Map<string, number>

Returns a map of rank pair string and its probability. Map key is string representation of rank pair such as AsKh. Detatched card pairs are ignored.

const handRange = HandRange.parse("AKs:0.5");
const onlyRankPairs = handRange.onlyRankPairs();

onlyRankPairs.entries();  // => Iterable of ["AsKs", 0.5], ["AhKh", 0.5], ...

HandRange#format(): string

Returns formatted string representation of the HandRange. Each card pairs will be merged and grouped into rank pairs.

const handRange = HandRange.parse("AsKs:1,AhKh:1,AdKd:1,AcKc:1");

handRange.format();  // => "AKs:1"

Evaluator

A class that evaluates equity of each player in a certain situation.

You can use either of these implementations:

  • new MontecarloEvaluator() - randomly decides the possible situations can come and evaluate equities.
  • new ExhaustiveEvaluator() - exhaustively iterates all the possible situations and evaluate equities.

Evaluator implements Iterable<Matchup> so you can use it in for-loop.

const evaluator = new ExhaustiveEvaluator({
  board: CardSet.parse("AsKcQh2d"),
  players: [
    HandRange.parse("KdJd"),
    HandRange.parse("Ah3h"),
  ],
});

for (const matchup of evaluator) {
  // ...
}

Evaluator#board: CardSet

The initial board cards for the situation.

Evaluator#players: HandRange[]

The HandRange(s) that the players have.

MontecarloEvaluator

An Evaluator that does montecarlo simulation for certain times.

Do not forget to call #take() because this evaluator can run unlimited times.

const evaluator = new MontecarloEvaluator({
  board: CardSet.parse("AsKcQh2d"),
  players: [
    HandRange.parse("KdJd"),
    HandRange.parse("Ah3h"),
  ],
});

for (const matchup of evaluator.take(10000)) {
  // ...
}

MontecarloEvaluator#take(times: number): Iterable<Matchup>

Limits times to run and returns Iterable<Matchup>.

const evaluator = new MontecarloEvaluator({
  board: CardSet.parse("AsKcQh2d"),
  players: [
    HandRange.parse("KdJd"),
    HandRange.parse("Ah3h"),
  ],
});

for (const matchup of evaluator.take(10000)) {
  // ...
}

ExhaustiveEvaluator

An Evaluator that exhaustively simulates all the possible situations. This is suitable for the situation that has only small number of possible futures.

const evaluator = new ExhaustiveEvaluator({
  board: CardSet.parse("AsKcQh2d"),
  players: [
    HandRange.parse("KdJd"),
    HandRange.parse("Ah3h"),
  ],
});

for (const matchup of evaluator) {
  // ...
}

Matchup

The eventual situation of the evaluation and its result.

Matchup#board: CardSet

The eventual board card in the situation.

Matchup#players: { cards: CardSet; hand: MadeHand; win: boolean }[]

Result of each player in the situation.

Matchup#wonPlayerCount: number

Number of the players won the pot.

MadeHand

The final hand that player made at showdown.

MadeHand#powerIndex: number

The power index of the MadeHand. 0 is the strongest (the top straight flush), 7462 is the worst trash hand.

MadeHand#type: "highcard" | "pair" | "two-pairs" | "trips" | "straight" | "flush" | "full-house" | "quads" | "straight-flush"

The type of MadeHand.

Contributing

Please folk and clone the repository and run npm install. Make a pull request towards main branch in this repository.

License

MIT

0.1.0

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.3

3 years ago

0.0.1

3 years ago

0.0.2

3 years ago

0.0.0

9 years ago