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.