<script>
//write minimal holdem api

class Card {
    constructor(suit, rank) {
        this.suit = suit;
        this.rank = rank;
    }
}

class Deck {
    constructor() {
        this.cards = [];
        const suits = ["♥", "♦", "♣", "♠"];
        const ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];

        for (const suit of suits) {
            for (const rank of ranks) {
                this.cards.push(new Card(suit, rank));
            }
        }

        this.shuffle();
    }

    shuffle() {
        const { cards } = this;
        let m = cards.length,
            i;

        while (m) {
            m--;
            const i = Math.floor(Math.random() * (m + 1));
            [cards[m], cards[i]] = [cards[i], cards[m]];
        }

        return this;
    }

    draw(num = 1) {
        return this.cards.splice(0, num);
    }
}

function dealCards(deck, numPlayers) {
    const playerHands = Array.from({ length: numPlayers }, () => []);

    for (let i = 0; i < 2; i++) {
        playerHands.forEach((hand) => hand.push(...deck.draw()));
    }

    return playerHands;
}

function dealFlop(deck) {
    deck.draw(); // Burn card
    return deck.draw(3); // Flop
}

function dealTurn(deck) {
    deck.draw(); // Burn card
    return deck.draw(); // Turn
}

function dealRiver(deck) {
    deck.draw(); // Burn card
    return deck.draw(); // River
}

//In Texas Hold'em, players can only change their cards before the pre-flop action by discarding one or both of their hole cards and receiving new ones from the dealer. This option is called "mucking" or "burn and turn," but it's not commonly used in most games.

//During the betting rounds, each player can take one of these actions:

//1. Check: if there was no bet made during that round, the player has the option to check (i.e., take no action, end their turn).

//2. Bet: a player can make a wager on their hand’s strength - this amount is determined by the table limit or agreed-upon amount.

//3. Call: if a bet has already been made during that round, a player can choose to match that bet rather than raise.

//4. Raise: if a bet has already been made during that round, a player can choose to increase the size of the wager with an additional amount equal to or greater than what was originally bet.

//5. Fold: A player may choose to forfeit their hand and leave the game at any time.


///////////////////////////////////////////////////////////////////////////////////////////////////
function evaluateHand(hand) {
    const suits = {};
    const ranks = {};

    // Count the number of each suit and rank in the hand
    for (const card of hand) {
        suits[card.suit] = (suits[card.suit] || 0) + 1;
        ranks[card.rank] = (ranks[card.rank] || 0) + 1;
    }

    // Check for royal flush
    if (Object.values(suits).some((count) => count >= 5) && ["10", "J", "Q", "K", "A"].every((rank) => ranks[rank])) {
        return { rank: 10, tiebreaker:[] };
    }

    // Check for straight flush
    const straightFlushSuit = Object.keys(suits).find((suit) => suits[suit] >= 5 && isStraight(getCardsWithSuit(hand, suit)));
    if (straightFlushSuit) {
        return { rank: 9, tiebreaker: getHighestRank(getCardsWithSuit(hand, straightFlushSuit)) };
    }

    // Check for four of a kind
    const fourOfAKindRank = Object.keys(ranks).find((rank) => ranks[rank] === 4);
    if (fourOfAKindRank) {
        return { rank: 8, tiebreaker: getTiebreakersForFourOfAKind(fourOfAKindRank, hand) };
    }

    // Check for full house
    const fullHouseRanks = getFullHouseRanks(ranks);
    if (fullHouseRanks.length > 0) {
        return { rank: 7, tiebreaker: fullHouseRanks };
    }

    // Check for flush
    const flushSuit = Object.keys(suits).find((suit) => suits[suit] >= 5);
    if (flushSuit) {
        return { rank: 6, tiebreaker: getTiebreakersForFlush(getCardsWithSuit(hand, flushSuit)) };
    }

    // Check for straight
    if (isStraight(hand)) {
        return { rank: 5, tiebreaker: getHighestRank(hand) };
    }

    // Check for three of a kind
    const threeOfAKindRank = Object.keys(ranks).find((rank) => ranks[rank] === 3);
    if (threeOfAKindRank) {
        return { rank: 4, tiebreaker: getTiebreakersForThreeOfAKind(threeOfAKindRank, hand) };
    }

    // Check for two pair
    const twoPairRanks = getTwoPairRanks(ranks);
    if (twoPairRanks.length > 0) {
        return { rank: 3, tiebreaker: twoPairRanks };
    }

    // Check for pair
    const pairRank = Object.keys(ranks).find((rank) => ranks[rank] === 2);
    if (pairRank) {
        return { rank: 2, tiebreaker: getTiebreakersForPair(pairRank, hand) };
    }

    // Highest card
    return { rank: 1, tiebreaker: getHighestRank(hand) };
}

function isStraight(cards) {
    const values = cards.map((card) => (["J", "Q", "K", "A"].includes(card.rank) ? ["J", "Q", "K", "A"].indexOf(card.rank) + 11 : parseInt(card.rank)));
    const uniqueValues = new Set(values);

    if (uniqueValues.size < 5) {
        return false;
    }

    let sortedValues = [...uniqueValues].sort((a, b) => a - b);
    return sortedValues[sortedValues.length - 1] - sortedValues[0] === 4;
}

function getCardsWithSuit(cards, suit) {
    return cards.filter((card) => card.suit === suit);
}

function getHighestRank(cards) {
    const ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];

    for (const rank of ranks.reverse()) {
        if (cards.some((card) => card.rank === rank)) {
            return [rank];
        }
    }
}

function getTiebreakersForFourOfAKind(rank, hand) {
    const fourOfAKindCards = hand.filter((card) => card.rank === rank);
    const kickerCard = hand.find((card) => card.rank !== rank);
    return [rank, kickerCard.rank];
}

function getFullHouseRanks(ranks) {
    const threeOfAKindRank = Object.keys(ranks).find((rank) => ranks[rank] === 3);

    if (!threeOfAKindRank || Object.keys(ranks).length < 2) {
        return [];
    }

    delete ranks[threeOfAKindRank];

    const pairRank = Object.keys(ranks).find((rank) => ranks[rank] >= 2);

    if (!pairRank || Object.keys(ranks).length > 1 || (pairRank === threeOfAKindRank && ranks[pairRank] < 2)) {
        return [];
    }

    return [threeOfAKindRank, pairRank];
}

function getTiebreakersForFlush(cards) {
    return cards
        .map((card) => card.rank)
        .sort()
        .reverse();
}

function getTiebreakersForThreeOfAKind(rank, hand) {
    const threeOfAKindCards = hand.filter((card) => card.rank === rank);
    const kickerCards = hand.filter((card) => card.rank !== rank);
    return [rank, ...getHighestNRank(kickerCards, 2)];
}

function getTwoPairRanks(ranks) {
    const pairs = [];

    for (const rank of Object.keys(ranks)) {
        if (ranks[rank] >= 2) {
            pairs.push(rank);
        }
    }

    if (pairs.length < 2 || Object.keys(ranks).length > 2) {
        return [];
    }

    return [...new Set(pairs)].sort().reverse();
}

function getTiebreakersForPair(rank, hand) {
    const pairCards = hand.filter((card) => card.rank === rank);
    const kickerCards = hand.filter((card) => card.rank !== rank);
    return [rank, ...getHighestNRank(kickerCards, 3)];
}

function getHighestNRank(cards, n) {
    const ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];

    return ranks
        .reverse()
        .reduce((matches, rank) => (cards.some((card) => card.rank === rank && matches.indexOf(rank) < 0) ? [...matches, rank] : matches), [])
        .slice(0, n);
}


///////////////////////////////////////////////////////////////////////////////////////////////////

/*function determineWinners(playerHands, sharedCards) {
    const playerEvaluations = playerHands.map((hand) => evaluateHand(hand.concat(sharedCards)));

    let maxRank = null;
    let maxTiebreakers = [];
    const winners = [];

    // Find maximum rank and set of tiebreakers across all players
    for (const { rank, tiebreaker } of playerEvaluations) {
	//console.log(rank, tiebreaker)
        if (maxRank === null || rank > maxRank) {
            maxRank = rank;
            maxTiebreakers = [tiebreaker];
        } else if (rank === maxRank) {
            maxTiebreakers.push(tiebreaker);
        }
    }

    // Find winning players based on maximum rank and tiebreakers
    for (let i = 0; i < playerEvaluations.length; i++) {
        const { rank, tiebreaker } = playerEvaluations[i];
		//console.log(rank, tiebreaker,playerEvaluations,i,maxTiebreakers,">>>>>",tiebreaker, maxTiebreakers[0]);
        if (rank !== maxRank || !arraysEqual(tiebreaker, maxTiebreakers[0])) {
            continue;
        }
        winners.push(i);
    }

    return winners;
}

function arraysEqual(a1, a2) {
    return a1.length === a2.length && a1.every((val, idx) => val === a2[idx]);
}*/
function determineWinners(playerHands, sharedCards) {
    const playerEvaluations = playerHands.map((hand) => evaluateHand(hand.concat(sharedCards)));

    let maxRank = null;
    const winners = [];
    let maxTiebreakers = [];

    for (const { rank, tiebreaker } of playerEvaluations) {
        if (maxRank === null || rank > maxRank) {
            maxRank = rank;
            maxTiebreakers = [tiebreaker];
        } else if (rank === maxRank) {
            maxTiebreakers.push(tiebreaker);
        }
    }

    for (let i = 0; i < playerEvaluations.length; i++) {
        const { rank, tiebreaker } = playerEvaluations[i];

        if (rank !== maxRank) {
            continue;
        }

        if (maxTiebreakers.every((maxTiebreaker) => compareTiebreakers(tiebreaker, maxTiebreaker))) {
            winners.push(i);
        }
    }

    return winners;
}

function compareTiebreakers(a1, a2) {
    for (let i = 0; i < a1.length; i++) {
        if (a1[i] !== a2[i]) {
            return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
var stats={}
for(var i=0;i<10000;i++)
{
var deck1 = new Deck();
var numPlayers = 6;
var playerHands = dealCards(deck1, numPlayers);
//console.log(playerHands);
//betting
var flop = dealFlop(deck1);
//console.log(flop);
//betting
var turn = dealTurn(deck1);
//console.log(turn);
//betting
var river = dealRiver(deck1);
//console.log(river);
//betting
var winners = determineWinners(playerHands, flop.concat(turn, river));
//console.log(winners);
	winners.forEach((winner) => {
		 var playerId = winner;
		 if (stats[playerId]==null)stats[playerId]=0;
		 stats[playerId]++;
		 
	});

}
console.log(JSON.stringify(stats));
//WHY NUMBER OF WINS FOR TWO AND SIG PLAYERS SO MUCH DIFFERS FOR 10000 ROUNDS OF TEXAS HOLDEM? {"0":260,"1":268,"2":241,"3":256,"4":248,"5":257}
//WHY NUMBER OF WINS FOR TWO AND SIG PLAYERS SO MUCH DIFFERS FOR 10000 ROUNDS OF TEXAS HOLDEM? {"0":2711,"1":2682}
</script>