Poker equity simulation by monte carlo

54 Views Asked by At

I've implement the simulation by Monte Carlo method for find a equity of range vs range with eveluatedCards with this library. So I don't clearly understand how probabilites heppened or wher my logic was wrong in my code.

import { evaluateCardsFast, evaluateCards } from 'phe';
import { cardRanks, cardSuits } from './constant';

const decks: string[] = [];

for (let i = 0; i < cardRanks.length; i++) {
  for (let j = 0; j < cardSuits.length; j++) {
    decks.push(cardRanks[i] + cardSuits[j]);
  }
}

function isSuited(card: string): boolean {
  return card.includes('s');
}

function isOffSuited(card: string): boolean {
  return card.includes('o');
}

function isPair(card: string): boolean {
  return card[0] === card[1];
}

function extractCardsFromType(card: string, weight: number): Array<string[]> {
  const cards: Array<string[]> = [];
  if (isPair(card)) {
    for (let i = 0; i < cardSuits.length; i++) {
      for (let j = i + 1; j < cardSuits.length; j++) {
        cards.push([card[0] + cardSuits[i], card[1] + cardSuits[j]]);
      }
    }
  }

  if (isSuited(card)) {
    for (let i = 0; i < cardSuits.length; i++) {
      cards.push([card[0] + cardSuits[i], card[1] + cardSuits[i]]);
    }
  }

  if (isOffSuited(card)) {
    for (let i = 0; i < cardSuits.length; i++) {
      for (let j = 0; j < cardSuits.length; j++) {
        if (cardSuits[i] !== cardSuits[j]) {
          cards.push([card[0] + cardSuits[i], card[1] + cardSuits[j]]);
        }
      }
    }
  }

  if (weight < 100) {
    const cardsToRemove = Math.floor(cards.length * (1 - weight / 100));
    cards.splice(0, cardsToRemove);
  }

  return cards;
}

function getCardsFromRange(range: string[], weight: number[]): Array<string[]> {
  const cards: Array<string[]> = [];
  range.forEach((cardCombo: string, index: number): void => {
    const result = extractCardsFromType(cardCombo, weight[index]);
    cards.push(...result);
  });
  return cards;
}

function randomCardFromRange(range: Array<string[]>): string[] {
  const random = Math.floor(Math.random() * range.length);
  return range[random];
}

// shuffle deck
function shuffle(deck: string[]): string[] {
  const shuffledDeck = [...deck];
  for (let i = shuffledDeck.length - 1; i > 0; i--) {
    const random = Math.floor(Math.random() * (i + 1));
    [shuffledDeck[i], shuffledDeck[random]] = [shuffledDeck[random], shuffledDeck[i]];
  }
  return shuffledDeck;
};

/**
 * 
 * @param handData 
 * 
 * @returns 99 if tie, else return index of winner
 */
export function evaluateRange(handData: Array<[string[], number[]]>): number {
  const allCards: string[] = [];

  // get player cards from range
  const playerRangeCards = handData.map(([range, weight]: [string[], number[]]): Array<string[]> => {
    return getCardsFromRange(range, weight);
  });

  // deal cards to player
  const playersCard: Array<string[]> = [];
  const playerIndexes = Array.from({ length: playerRangeCards.length }, (_, i) => i);

  // Shuffle the playerIndexes array to randomize the order of dealing cards.
  for (let i = playerIndexes.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [playerIndexes[i], playerIndexes[j]] = [playerIndexes[j], playerIndexes[i]];
  }

  for (let i = 0; i < playerRangeCards.length; i++) {
    const playerIndex = playerIndexes[i];
    const playerRange = playerRangeCards[playerIndex];

    if (i === 0) {
      const card = randomCardFromRange(playerRange);
      playersCard[playerIndex] = card;

      allCards.push(...card);
    } else {
      let isCardValid = false;
      let count = 0;
      while (!isCardValid) {
        if (count > 10000) {
          throw new Error('Cannot find valid card');
        }
        const card = randomCardFromRange(playerRange);

        // Check if the card is already in the hands of other players
        const cardInOtherHands = playersCard.some((otherHand) => otherHand.includes(card[0]) || otherHand.includes(card[1]));

        if (!cardInOtherHands) {
          playersCard[playerIndex] = card;
          
          allCards.push(...card);
          isCardValid = true;
        }
        count++;
      }
    }
  }

  // shuffle deck
  const deck = shuffle([...decks].filter((card: string): boolean => !allCards.includes(card)));

  // deal cards to board
  const boardCards: string[] = [];
  while (boardCards.length < 5) {
    const card = deck.pop();
    if (!card) {
      throw new Error('Deck is empty');
    }
    boardCards.push(card);
  }

  let result = 0 // 0 is win, 1 is lose, 99 is tie
  const heroHand = playersCard[0];
  const heroHandRank = evaluateCardsFast([...heroHand, ...boardCards]);

  const villainHandsRank = playersCard[1];
  const villainHandRank = evaluateCardsFast([...villainHandsRank, ...boardCards]);

  if(villainHandRank < heroHandRank) {
    result = 1
    return result
  }

  if(villainHandRank === heroHandRank) {
    result = 99
  }
  
  return result
}

The problem is when I put the both range that overlapped eg. (AA AK) and (KK). the result of simulation is incorrect. that's difference from the correct result(come from the other tools) around 3% in this example.

  • Range (AA AKs) (KK)
  • my result 66% 33%
  • correct result 70% 30%

in my code the difference coming from won probability in (KK) range. I don't understand why it increase by 3%. Thanks a lot from help.

0

There are 0 best solutions below