<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) {
	//playerHands = playerHands.filter(arr => arr.length > 0);
    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;
}


///////////////////////////////////////////////////////////////////////////////////////////////////function
function randomFold(playerHands)
{
	return;
	for(var i=0;i<playerHands.length;i++)
		if(Math.random()<0.5)playerHands[Math.floor(Math.random()*playerHands.length)]=[];

}
///////////////////////////////////////////////////////////////////////////////////////////////////

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
randomFold(playerHands)
var flop = dealFlop(deck1);
//console.log(flop);
//betting
randomFold(playerHands)
var turn = dealTurn(deck1);
//console.log(turn);
//betting
randomFold(playerHands)
var river = dealRiver(deck1);
//console.log(river);
//betting
randomFold(playerHands)
var winners = determineWinners(playerHands, flop.concat(turn, river));
//console.log(winners);
	winners.forEach((winner) => {
		 var playerId = "PLAYER"+winner;
		 if (stats[playerId]==null)stats[playerId]=0;
		 stats[playerId]++;
		 if (stats.TOTALWINS==null)stats.TOTALWINS=0;
		 stats.TOTALWINS++;
	});

}
stats.ROUNDS=i;
console.log(JSON.stringify(stats));document.getElementById("result").innerHTML+="\n\n"+outputStatsTable(stats);

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

}
stats.ROUNDS=i;
console.log(JSON.stringify(stats));document.getElementById("result").innerHTML+="\n\n"+outputStatsTable(stats);
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
function outputStatsTable(stats) {
  const players = Object.keys(stats).filter(key => key.includes("PLAYER")).sort();

  let html = "<table border=1 style='font-size:inherit'>";

  // Output player rows
  for (let i = 0; i < players.length; i++) {
    html += "<tr>";
    html += `<td>${players[i]}</td>`;
    html += `<td>${stats[players[i]]}</td>`;
    html += "</tr>";
  }

  // Output other values column
  //html += "<tr><td colspan=2>Other Values:</td></tr>";

  const otherKeys = Object.keys(stats).filter(key => !key.includes("PLAYER"));

  for (let i = 0; i < otherKeys.length; i++) {
    html += "<tr>";
    html += `<td>${otherKeys[i]}</td>`;
    html += `<td>${stats[otherKeys[i]]}</td>`;
    html+="</tr>"
    
  }
  
  



html+= "</table>"
return(html)


}
correct assuming player can fold, so their hand appears empty array, OUTPUT CORRECTED PART ONLY