texas.js | |
|---|---|
| Texas Hold'em hand evaluator for node.js. | |
| Dependencies: underscore.js, compress-buffer. | var _ = require('underscore');
var fs = require('fs');
var crypto = require('crypto');
var gzip = require('compress-buffer'); |
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 evaluator = new Int32Array(gzip.uncompress(buffer)); |
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');
}
};
|