UNPKG

texas-without-memcpy

Version:

Texas Hold'em hand evaluator for node.js.

220 lines (195 loc) 6.54 kB
// Texas Hold'em hand evaluator for [node.js](http://nodejs.org/). // *Dependencies*: [underscore.js](http://documentcloud.github.com/underscore/), // [compress-buffer](http://github.com/egorFiNE/node-compress-buffer/). var _ = require('underscore'); var fs = require('fs'); var crypto = require('crypto'); var zlib = require('zlib'); //var memcpy = require('memcpy'); function toArrayBuffer(buffer) { var ab = new ArrayBuffer(buffer.length); var view = new Uint8Array(ab); for (var i = 0; i < buffer.length; ++i) { view[i] = buffer[i]; } return ab; } // ## Definitions // Standard deck size. var deckSize = 52; // Card abbreviations. var chars = '23456789TJQKAc♣d♦h♥s♠'; var abbr = _.object(chars, _.range(chars.length)); // Card ranks. var ranks = ['Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King', 'Ace']; // Card suits. var suits = ['Clubs', 'Diamonds', 'Hearts', 'Spades']; // Types of hand. var hands = ['Invalid', 'High Card', 'One Pair', 'Two Pairs', 'Three of a Kind', 'Straight', 'Flush', 'Full House', 'Four of a Kind', 'Straight Flush']; // Loads the look-up table. var buffer = fs.readFileSync(__dirname + '/HandRanks.dat.gz'); var zbuffer = zlib.gunzipSync(buffer); //var bufferArray = new ArrayBuffer(zbuffer.length); //memcpy(bufferArray,0,zbuffer); var bufferArray = toArrayBuffer(zbuffer); var evaluator = new Int32Array(bufferArray); // ## Internal Functions // Parses the formatted card to get its numeric code. var getCode = function (card) { if (typeof card == 'number') return card >= 1 && card <= deckSize ? card : undefined; // Already codified if (typeof card != 'string' || card.length != 2) return undefined; // Invalid input var posRank = abbr[card[0]]; var posSuit = (abbr[card[1]] - ranks.length) >> 1; if (typeof posRank != 'number' || posRank >= ranks.length || posSuit < 0) return undefined; // Invalid characters return posRank * suits.length + posSuit + 1; }; // Helper function to create card formatters. var filter = function (format) { return function (card) { card = getCode(card); if (!card) return undefined; card--; return format({rank: card >> 2, suit: card & 3}); }; }; // ## Main Functions module.exports = { // Creates a new shuffled deck. deck: function (format) { var res = _.range(1, deckSize + 1); var buffer = crypto.randomBytes(deckSize << 2); for (var pos = res.length - 1; pos > 0; pos--) { var rand = buffer.readUInt32LE(pos << 2) % (pos + 1); var temp = res[pos]; res[pos] = res[rand]; res[rand] = temp; }; return format ? _.map(res, format) : res; }, // Evaluates the 5 to 7 card hands. evaluate: function (cards) { var res = deckSize + 1; for (var c = 0; c < cards.length; c++) res = evaluator[res + getCode(cards[c])]; if (cards.length < 7) res = evaluator[res]; return {name: hands[res >> 12], value: res}; }, // Sorts the set of cards. sort: function (cards) { return _.sortBy(cards, getCode); }, // Calculates the exact odds. odds: function (hands, table, dead) { // Preprocesses the input data. table = table ? _.map(table, getCode) : []; dead = dead ? _.map(dead, getCode) : []; hands = _.map(hands, function (hand) { return _.map(hand, getCode); }); var res = deckSize + 1; for (var c = 0; c < table.length; c++) res = evaluator[res + table[c]]; var deck = _.chain(_.range(1, deckSize + 1)).difference(table) .difference(_.flatten(hands)).difference(dead).value(); var player = _.map(hands, function (hand) { return evaluator[evaluator[res + hand[0]] + hand[1]]; }); var combinations = function (n, k, res, callback) { if (k <= 0) { callback(res); } else { while (n < deck.length) { var parcial = evaluator[res + deck[n++]]; combinations(n, k - 1, parcial, callback); } } }; // Calculates the outcome of each play. var remaining = 5 - table.length; var plays = deck.length / remaining; for (var c = 1; c < remaining; c++) plays *= (deck.length - c) / c; var values = new Array(hands.length); for (var p = 0; p < hands.length; p++) { var s = 0; values[p] = new Int32Array(plays); combinations(0, remaining, player[p], function (res) { values[p][s++] = res; }); } // Determines the results. var wins = new Int32Array(hands.length); var splits = new Int32Array(hands.length); for (var s = 0; s < plays; s++) { var winner = [0]; for (var p = 1; p < hands.length; p++) { if (values[p][s] > values[winner[0]][s]) winner = [p]; else if (values[p][s] == values[winner[0]][s]) winner.push(p); } if (winner.length == 1) { wins[winner[0]]++; } else { for (var p = 0; p < winner.length; p++) splits[winner[p]]++; } } // Formats odds output. var odds = []; for (var p = 0; p < hands.length; p++) odds[p] = {win: wins[p] / plays, split: splits[p] / plays}; return odds; }, // Formats the card to extended text. extended: filter(function (card) { return ranks[card.rank] + ' of ' + suits[card.suit]; }), // Formats the card to abbreviated text. abbr: filter(function (card) { return chars[card.rank] + chars[(card.suit << 1) + ranks.length]; }), // Formats the card to unicode text. unicode: filter(function (card) { return chars[card.rank] + chars[(card.suit << 1) + ranks.length + 1]; }), // Parses the formatted card to get its numeric code. code: getCode, // Benchmarks the evaluator within all possible 7 card hands. benchmark: function () { var freq = new Int32Array(hands.length); var start = Date.now(); for (var c1 = 1; c1 <= deckSize; c1++) { var r1 = evaluator[deckSize + c1 + 1]; for (var c2 = c1 + 1; c2 <= deckSize; c2++) { var r2 = evaluator[r1 + c2]; for (var c3 = c2 + 1; c3 <= deckSize; c3++) { var r3 = evaluator[r2 + c3]; for (var c4 = c3 + 1; c4 <= deckSize; c4++) { var r4 = evaluator[r3 + c4]; for (var c5 = c4 + 1; c5 <= deckSize; c5++) { var r5 = evaluator[r4 + c5]; for (var c6 = c5 + 1; c6 <= deckSize; c6++) { var r6 = evaluator[r5 + c6]; for (var c7 = c6 + 1; c7 <= deckSize; c7++) { var r7 = evaluator[r6 + c7]; freq[r7 >> 12]++; } } } } } } } var finish = Date.now(); var total = 0; for (var key = 0; key < hands.length; key++) { total += freq[key]; console.log(hands[key] + ': ' + freq[key]); } console.log('Total: ' + total); console.log((finish - start) + 'ms'); } };