"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.winningOddsForTable = exports.winningOddsForPlayer = exports.evalCard = exports.evalHand = void 0;
var tslib_1 = require("tslib");
var fs = tslib_1.__importStar(require("fs"));
var path = tslib_1.__importStar(require("path"));
var constants_1 = require("./constants");
var three_card_converter_1 = tslib_1.__importDefault(require("./three-card-converter"));
// This is outside the class so evalHand is static, to keep same api as @chenosaurus/poker-evaluator
var RANKS_DATA = fs.readFileSync(path.join(__dirname, '../data/HandRanks.dat'));
function evalHand(cards) {
    if (!RANKS_DATA) {
        throw new Error('HandRanks.dat not loaded.');
    }
    if (cards.length !== 7
        && cards.length !== 6
        && cards.length !== 5
        && cards.length !== 3) {
        throw new Error("Hand must be 3, 5, 6, or 7 cards, but " + cards.length + " cards were provided");
    }
    if (cardsAreValidNumbers(cards)) {
        if (cards.length === 3) {
            throw new Error("Please supply 3 card hands as string[] of \"cards\" only.");
        }
        return evaluate(cards);
    }
    else if (cardsAreValidStrings(cards)) {
        var stringCards = cards;
        if (stringCards.length === 3) { // If a 3 card hand, fill in to make 5 card
            stringCards = three_card_converter_1.default.fillHand(stringCards);
        }
        return evaluate(convertCardsToNumbers(stringCards));
    }
    else {
        throw new Error("\n      Please supply input as a valid string[] | number[] of \"cards\".\n      See src/constants/deck.const.ts for the deck's values\n    ");
    }
}
exports.evalHand = evalHand;
function evalCard(card) {
    return RANKS_DATA.readUInt32LE(card * 4);
}
exports.evalCard = evalCard;
function convertCardsToNumbers(cards) {
    return cards.map(function (card) { return constants_1.DECK[card.toLowerCase()]; });
}
function cardsAreValidStrings(cards) {
    return cards.every(function (card) {
        return typeof card === 'string' && constants_1.DECK_KEYS.has(card.toLowerCase());
    });
}
function cardsAreValidNumbers(cards) {
    return cards.every(function (card) {
        return typeof card === 'number' && constants_1.DECK_VALUES.has(card);
    });
}
function evaluate(cardValues) {
    var p = 53;
    cardValues.forEach(function (cardValue) { return p = evalCard(p + cardValue); });
    if (cardValues.length === 5 || cardValues.length === 6) {
        p = evalCard(p);
    }
    return {
        handType: p >> 12,
        handRank: p & 0x00000fff,
        value: p,
        handName: constants_1.HAND_TYPES[p >> 12]
    };
}
/**
 * This function takes the cards in a players hand as well as the community cards (if any) on the table.
 * Given the player count at the table and the number of simulation cycles to run, this estimates the odds of winning the hand.
 * Split pots are not counted as a win, so with a royal flush community hand, every player would have winning odds of 0.0.
 * The more cycles used, the more precise the number will become.  The time complexity of this function is a factor
 * of the player count multiplied by the number of cycles.  Hands with fewer cards on the table will take slightly longer.
 *
 * This does not use any tables, mathematical functions, or other heuristics.  It simply uses a Monte Carlo method.
 * This means that winning odds will vary with the same cards between runs, even with a very high cycle count.
 *
 * Cycles counts of 1000 tend to yield similar results to 100000 however, so unlike a chess engine, there are not
 * dramatically different qualities of results with longer calculating times.  In cases where the player may be aiming
 * for something very unlikely, like a straight flush or four of a kind, running 30000 or more cycles will help in getting
 * odds other than 0, instead yielding the correct ~0.0001.
 */
function winningOddsForPlayer(hand, community, playerCount, cycles) {
    // Above 23 players, we run out of cards in a deck.  23 * 2 + 5 = 51
    if (playerCount > 23) {
        throw new Error("You may have at most 23 players.");
    }
    // Hand with no knowledge of other players hands
    return winningOddsForTable(tslib_1.__spreadArrays([hand], Array(playerCount - 1).fill([])), community, playerCount, cycles)['players'][0];
}
exports.winningOddsForPlayer = winningOddsForPlayer;
function winningOddsForTable(knownPartialHands, community, playerCount, cycles) {
    var numCommunity = convertCardsToNumbers(community);
    var numHands = knownPartialHands.map(convertCardsToNumbers);
    var allHoleCards = numHands.reduce(function (group, currentHand) { return tslib_1.__spreadArrays(group, currentHand); }, []);
    var startingDeck = deckWithoutSpecifiedCards(tslib_1.__spreadArrays(numCommunity, allHoleCards));
    var startingSplits = [];
    for (var i = 0; i < playerCount; i++) {
        startingSplits.push(Array(playerCount - 1).fill(0));
    }
    var data = {
        'wins': Array(playerCount).fill(0),
        'splits': startingSplits
    };
    var _loop_1 = function (i) {
        shuffleDeck(startingDeck);
        var deckPosition = 0;
        var interimCommunity = tslib_1.__spreadArrays(numCommunity);
        // Fill in players cards from the deck if not provided
        var holeCards = [];
        for (var p = 0; p < playerCount; p++) {
            var card1 = numHands[p].length >= 1 ? numHands[p][0] : startingDeck[deckPosition++];
            var card2 = numHands[p].length == 2 ? numHands[p][1] : startingDeck[deckPosition++];
            holeCards.push([card1, card2]);
        }
        while (interimCommunity.length < 5) {
            interimCommunity.push(startingDeck[deckPosition++]);
        }
        // Calculate the ranks of each hand
        var handValues = holeCards.map(function (hand) { return evalHand(tslib_1.__spreadArrays(hand, interimCommunity)).value; });
        // Find the winning hands this round
        var winningIndexes = [];
        var bestRank = -1;
        for (var p = 0; p < playerCount; p++) {
            if (handValues[p] > bestRank) {
                winningIndexes = [p];
                bestRank = handValues[p];
            }
            else if (handValues[p] === bestRank) {
                winningIndexes.push(p);
            }
        }
        if (winningIndexes.length > 1) {
            for (var i_1 = 0; i_1 < winningIndexes.length; i_1++) {
                // Increment that players split count of this size
                data['splits'][winningIndexes[i_1]][winningIndexes.length - 2] += 1;
            }
        }
        else {
            data['wins'][winningIndexes[0]] += 1;
        }
    };
    for (var i = 0; i < cycles; i++) {
        _loop_1(i);
    }
    return buildPlayerData(data, cycles);
}
exports.winningOddsForTable = winningOddsForTable;
function buildPlayerData(rawData, cycles) {
    var playerCount = rawData['wins'].length;
    var players = [];
    for (var p = 0; p < playerCount; p++) {
        var winRate = rawData['wins'][p] / cycles;
        var splitList = [];
        for (var i = 0; i < rawData['splits'][0].length; i++) {
            splitList.push({ 'rate': rawData['splits'][p][i] / cycles, 'ways': i + 2 });
        }
        players.push({ 'winRate': winRate, 'splitRates': splitList });
    }
    return { 'players': players };
}
/**
 * Given a list of cards already dealt out, return the remaining cards that would be in the deck.
 */
function deckWithoutSpecifiedCards(cards) {
    var providedSet = new Set(cards);
    return Object.values(constants_1.DECK).filter(function (name) { return !providedSet.has(name); });
}
/**
 * TS implementation of https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
 * Code based on: https://stackoverflow.com/a/12646864
 */
function shuffleDeck(deck) {
    var _a;
    for (var i = deck.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        _a = [deck[j], deck[i]], deck[i] = _a[0], deck[j] = _a[1];
    }
}
//# sourceMappingURL=poker-evaluator.js.map