UNPKG

poker-odds-calculator

Version:

A pre-flop and post-flop odds calculator for Texas Holdem

224 lines 8.26 kB
/** * OddsCalculator * */ import uniqBy from 'lodash/uniqBy.js'; import random from 'lodash/random.js'; import { Card, Suit } from './Card.js'; import { CardGroup } from './CardGroup.js'; import { FullDeckGame, ShortDeckGame } from './Game.js'; import { HandRank } from './HandRank.js'; export class HandEquity { constructor() { this.possibleHandsCount = 0; this.bestHandCount = 0; this.tieHandCount = 0; } addPossibility(isBestHand, isTie) { this.possibleHandsCount += 1; if (isBestHand) { this.bestHandCount += 1; } else if (isTie) { this.tieHandCount += 1; } } getEquity() { if (this.possibleHandsCount === 0) { return 0; } return Math.round(this.bestHandCount * 100.0 / this.possibleHandsCount); } getTiePercentage() { if (this.possibleHandsCount === 0) { return 0; } return Math.round(this.tieHandCount * 100.0 / this.possibleHandsCount); } toString() { let s = `${this.getEquity()}%`; const tie = this.getTiePercentage(); if (tie > 0) { s += ` (Tie: ${tie}%)`; } return s; } } export class OddsCalculator { constructor(equities, handranks, iterations, elapsedTime) { this.equities = equities; this.handranks = handranks; this.iterations = iterations; this.elapsedTime = elapsedTime; } static calculate(cardgroups, board, gameVariant, iterations) { if (board && [0, 3, 4, 5].indexOf(board.length) === -1) { throw new Error('The board must contain 0, 3, 4 or 5 cards'); } const allGroups = board ? cardgroups.concat(board) : cardgroups; let allCards = []; allGroups.forEach((group) => { allCards = allCards.concat(group); }); // Invalid card values if (gameVariant === 'short') { allCards.forEach((card) => { if (card.getRank() < 6) { throw new Error('Only cards rank 6 through A are valid.'); } }); } const uniqCards = uniqBy(allCards, (card) => { return card.getRank() + '-' + card.getSuit(); }); if (uniqCards.length !== allCards.length) { throw new Error('Detected duplicate cards'); } iterations = iterations || 0; let game; if (gameVariant === 'short') { game = new ShortDeckGame(); } else { game = new FullDeckGame(); } let handranks = []; // Find out which cards are left in the deck const remainingCards = new CardGroup(); if (!board || board.length <= 4) { for (const suit of Suit.all()) { for (const rank of game.rank.all()) { const c = new Card(rank, suit); let isUsed = false; if (board) { for (const boardCard of board) { if (c.equals(boardCard)) { isUsed = true; break; } } } if (!isUsed) { for (const cardgroup of cardgroups) { for (const card of cardgroup) { if (c.equals(card)) { isUsed = true; break; } } if (isUsed) { break; } } } if (!isUsed) { remainingCards.push(c); } } } } const remainingCount = remainingCards.length; // Figure out hand ranking handranks = cardgroups.map((cardgroup) => { return HandRank.evaluate(game, board ? cardgroup.concat(board) : cardgroup); }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const equities = cardgroups.map((cardgroup) => { return new HandEquity(); }); const selectWinners = (simulatedBoard) => { let highestRanking = null; let highestRankingIndex = []; for (let i = 0; i < cardgroups.length; i += 1) { const handranking = HandRank.evaluate(game, cardgroups[i].concat(simulatedBoard)); const isBetter = highestRanking ? handranking.compareTo(highestRanking) : -1; if (highestRanking === null || isBetter >= 0) { if (isBetter === 0) { highestRankingIndex.push(i); } else { highestRankingIndex = [i]; } highestRanking = handranking; } } for (let i = 0; i < cardgroups.length; i += 1) { let isWinning = false; let isTie = false; if (highestRankingIndex.length > 1) { isTie = (highestRankingIndex.indexOf(i) > -1); } else { isWinning = (highestRankingIndex.indexOf(i) > -1); } equities[i].addPossibility(isWinning, isTie); } }; const jobStartedAt = +new Date(); if (!board || board.length === 0) { iterations = iterations || OddsCalculator.DEFAULT_ITERATIONS; for (let x = iterations; x > 0; x -= 1) { const index1 = random(0, remainingCount - 1); let index2; let index3; let index4; let index5; do { index2 = random(0, remainingCount - 1); } while (index2 === index1); do { index3 = random(0, remainingCount - 1); } while (index3 === index1 || index3 === index2); do { index4 = random(0, remainingCount - 1); } while (index4 === index1 || index4 === index2 || index4 === index3); do { index5 = random(0, remainingCount - 1); } while (index5 === index1 || index5 === index2 || index5 === index3 || index5 === index4); const simulatedBoard = CardGroup.fromCards([ remainingCards[index1], remainingCards[index2], remainingCards[index3], remainingCards[index4], remainingCards[index5] ]); selectWinners(simulatedBoard); } } else if (board.length >= 5) { iterations = 1; selectWinners(board); } else if (board.length === 4) { for (const c of remainingCards) { const simulatedBoard = board.concat(CardGroup.fromCards([c])); iterations += 1; selectWinners(simulatedBoard); } } else if (board.length === 3) { for (let a = 0; a < remainingCount; a += 1) { for (let b = a + 1; b < remainingCount; b += 1) { const simulatedBoard = board.concat(CardGroup.fromCards([remainingCards[a], remainingCards[b]])); iterations += 1; selectWinners(simulatedBoard); } } } const jobEndedAt = +new Date(); return new OddsCalculator(equities, handranks, iterations, jobEndedAt - jobStartedAt); } getIterationCount() { return this.iterations; } getElapsedTime() { return this.elapsedTime; } getHandRank(index) { return this.handranks[index]; } } OddsCalculator.DEFAULT_ITERATIONS = 100000; //# sourceMappingURL=OddsCalculator.js.map