poker-evaluator
Version:
A library to evaluate 3, 5, 6 or 7 card poker hands
181 lines • 8.11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.winningOddsForTable = exports.winningOddsForPlayer = exports.evalCard = exports.evalHand = void 0;
var tslib_1 = require("tslib");
var fs = tslib_1.__importStar(require("fs"));
var path = tslib_1.__importStar(require("path"));
var constants_1 = require("./constants");
var three_card_converter_1 = tslib_1.__importDefault(require("./three-card-converter"));
// This is outside the class so evalHand is static, to keep same api as @chenosaurus/poker-evaluator
var RANKS_DATA = fs.readFileSync(path.join(__dirname, '../data/HandRanks.dat'));
function evalHand(cards) {
if (!RANKS_DATA) {
throw new Error('HandRanks.dat not loaded.');
}
if (cards.length !== 7
&& cards.length !== 6
&& cards.length !== 5
&& cards.length !== 3) {
throw new Error("Hand must be 3, 5, 6, or 7 cards, but " + cards.length + " cards were provided");
}
if (cardsAreValidNumbers(cards)) {
if (cards.length === 3) {
throw new Error("Please supply 3 card hands as string[] of \"cards\" only.");
}
return evaluate(cards);
}
else if (cardsAreValidStrings(cards)) {
var stringCards = cards;
if (stringCards.length === 3) { // If a 3 card hand, fill in to make 5 card
stringCards = three_card_converter_1.default.fillHand(stringCards);
}
return evaluate(convertCardsToNumbers(stringCards));
}
else {
throw new Error("\n Please supply input as a valid string[] | number[] of \"cards\".\n See src/constants/deck.const.ts for the deck's values\n ");
}
}
exports.evalHand = evalHand;
function evalCard(card) {
return RANKS_DATA.readUInt32LE(card * 4);
}
exports.evalCard = evalCard;
function convertCardsToNumbers(cards) {
return cards.map(function (card) { return constants_1.DECK[card.toLowerCase()]; });
}
function cardsAreValidStrings(cards) {
return cards.every(function (card) {
return typeof card === 'string' && constants_1.DECK_KEYS.has(card.toLowerCase());
});
}
function cardsAreValidNumbers(cards) {
return cards.every(function (card) {
return typeof card === 'number' && constants_1.DECK_VALUES.has(card);
});
}
function evaluate(cardValues) {
var p = 53;
cardValues.forEach(function (cardValue) { return p = evalCard(p + cardValue); });
if (cardValues.length === 5 || cardValues.length === 6) {
p = evalCard(p);
}
return {
handType: p >> 12,
handRank: p & 0x00000fff,
value: p,
handName: constants_1.HAND_TYPES[p >> 12]
};
}
/**
* This function takes the cards in a players hand as well as the community cards (if any) on the table.
* Given the player count at the table and the number of simulation cycles to run, this estimates the odds of winning the hand.
* Split pots are not counted as a win, so with a royal flush community hand, every player would have winning odds of 0.0.
* The more cycles used, the more precise the number will become. The time complexity of this function is a factor
* of the player count multiplied by the number of cycles. Hands with fewer cards on the table will take slightly longer.
*
* This does not use any tables, mathematical functions, or other heuristics. It simply uses a Monte Carlo method.
* This means that winning odds will vary with the same cards between runs, even with a very high cycle count.
*
* Cycles counts of 1000 tend to yield similar results to 100000 however, so unlike a chess engine, there are not
* dramatically different qualities of results with longer calculating times. In cases where the player may be aiming
* for something very unlikely, like a straight flush or four of a kind, running 30000 or more cycles will help in getting
* odds other than 0, instead yielding the correct ~0.0001.
*/
function winningOddsForPlayer(hand, community, playerCount, cycles) {
// Above 23 players, we run out of cards in a deck. 23 * 2 + 5 = 51
if (playerCount > 23) {
throw new Error("You may have at most 23 players.");
}
// Hand with no knowledge of other players hands
return winningOddsForTable(tslib_1.__spreadArrays([hand], Array(playerCount - 1).fill([])), community, playerCount, cycles)['players'][0];
}
exports.winningOddsForPlayer = winningOddsForPlayer;
function winningOddsForTable(knownPartialHands, community, playerCount, cycles) {
var numCommunity = convertCardsToNumbers(community);
var numHands = knownPartialHands.map(convertCardsToNumbers);
var allHoleCards = numHands.reduce(function (group, currentHand) { return tslib_1.__spreadArrays(group, currentHand); }, []);
var startingDeck = deckWithoutSpecifiedCards(tslib_1.__spreadArrays(numCommunity, allHoleCards));
var startingSplits = [];
for (var i = 0; i < playerCount; i++) {
startingSplits.push(Array(playerCount - 1).fill(0));
}
var data = {
'wins': Array(playerCount).fill(0),
'splits': startingSplits
};
var _loop_1 = function (i) {
shuffleDeck(startingDeck);
var deckPosition = 0;
var interimCommunity = tslib_1.__spreadArrays(numCommunity);
// Fill in players cards from the deck if not provided
var holeCards = [];
for (var p = 0; p < playerCount; p++) {
var card1 = numHands[p].length >= 1 ? numHands[p][0] : startingDeck[deckPosition++];
var card2 = numHands[p].length == 2 ? numHands[p][1] : startingDeck[deckPosition++];
holeCards.push([card1, card2]);
}
while (interimCommunity.length < 5) {
interimCommunity.push(startingDeck[deckPosition++]);
}
// Calculate the ranks of each hand
var handValues = holeCards.map(function (hand) { return evalHand(tslib_1.__spreadArrays(hand, interimCommunity)).value; });
// Find the winning hands this round
var winningIndexes = [];
var bestRank = -1;
for (var p = 0; p < playerCount; p++) {
if (handValues[p] > bestRank) {
winningIndexes = [p];
bestRank = handValues[p];
}
else if (handValues[p] === bestRank) {
winningIndexes.push(p);
}
}
if (winningIndexes.length > 1) {
for (var i_1 = 0; i_1 < winningIndexes.length; i_1++) {
// Increment that players split count of this size
data['splits'][winningIndexes[i_1]][winningIndexes.length - 2] += 1;
}
}
else {
data['wins'][winningIndexes[0]] += 1;
}
};
for (var i = 0; i < cycles; i++) {
_loop_1(i);
}
return buildPlayerData(data, cycles);
}
exports.winningOddsForTable = winningOddsForTable;
function buildPlayerData(rawData, cycles) {
var playerCount = rawData['wins'].length;
var players = [];
for (var p = 0; p < playerCount; p++) {
var winRate = rawData['wins'][p] / cycles;
var splitList = [];
for (var i = 0; i < rawData['splits'][0].length; i++) {
splitList.push({ 'rate': rawData['splits'][p][i] / cycles, 'ways': i + 2 });
}
players.push({ 'winRate': winRate, 'splitRates': splitList });
}
return { 'players': players };
}
/**
* Given a list of cards already dealt out, return the remaining cards that would be in the deck.
*/
function deckWithoutSpecifiedCards(cards) {
var providedSet = new Set(cards);
return Object.values(constants_1.DECK).filter(function (name) { return !providedSet.has(name); });
}
/**
* TS implementation of https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
* Code based on: https://stackoverflow.com/a/12646864
*/
function shuffleDeck(deck) {
var _a;
for (var i = deck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
_a = [deck[j], deck[i]], deck[i] = _a[0], deck[j] = _a[1];
}
}
//# sourceMappingURL=poker-evaluator.js.map