A fast implementation of Texas Hold'em poker odds calculator and its related data models.
- ๐โโ๏ธ 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
A enum-like representation of rank in playing cards.
Rank.Ace; // => A of playing cards
Rank.Deuce; // => 2 of playing cards
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().
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
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";
A enum-like representation of suit in playing cards.
Suit.Spade; // => spade of playing cards
Suit.Diamond; // => diamond of playing cards
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().
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
Returns a char for the Suit. The returning string is compatible for Suit.parse()
.
Suit.Spade.format() === "s";
Suit.Diamond.format() === "d";
A class representing a piece of playing cards.
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
Creates a Card from a given pair of Rank and Suit.
new Card(Rank.Ace, Suit.Spade);
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
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
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
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
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";
Immutable and performant set of Card(s).
An empty CardSet.
A CardSet that has all the cards.
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),
]);
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
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
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
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>
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),
]);
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
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";
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")) {
// ...
}
Returns an empty HandRange.
const handRange = HandRange.empty();
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 ofJJ
,TT
and99
.86s-84s
- all suited combinations of86s
,85s
and84s
.AJo-A9o
- all ofsuite combinations ofAJo
,ATo
andA9o
.JJ+
- equivalent toAA-JJ
.85s+
- equivalent to87s-85s
.AQo+
- equivalent toAKo-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");
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"),
]);
Number of entries in the HandRange.
const handRange = HandRange.parse("88-66");
handRange.size; // => 18
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"));
Returns an union of the HandRange and the given one.
const handRange = HandRange.parse("88-66");
const ninesAdded = handRange.added(HandRange.parse("99"));
Returns a new HandRange that the given hand range removed.
const handRange = HandRange.parse("88-66");
const sevensRemoved = handRange.removed(HandRange.parse("77"));
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], ...
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"
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) {
// ...
}
The initial board cards for the situation.
The HandRange(s) that the players have.
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)) {
// ...
}
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)) {
// ...
}
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) {
// ...
}
The eventual situation of the evaluation and its result.
The eventual board card in the situation.
Result of each player in the situation.
Number of the players won the pot.
The final hand that player made at showdown.
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.
Please folk and clone the repository and run npm install
. Make a pull request towards main
branch in this repository.