npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

holdem

v0.1.0

Published

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

Downloads

10

Readme

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