Skip to content

Instantly share code, notes, and snippets.

@npiv
Created October 24, 2014 13:14
Show Gist options
  • Save npiv/438c084a4c6a719b886d to your computer and use it in GitHub Desktop.
Save npiv/438c084a4c6a719b886d to your computer and use it in GitHub Desktop.
(function() {
// window on the browser, exports on the server
var root = this;
// the Cards module
var Cards = root.Cards = {};
// utility functions
var util = Cards.util = {
bitwiseAnd : function(a, b) { return a & b; },
bitwiseOr : function(a, b) { return a | b; },
plus : function(a, b) { return a + b; },
sum : function(arr) { return _.reduce(arr, plus, 0); },
// http://stackoverflow.com/questions/53161/find-the-highest-order-bit-in-c
highbit : function(n) {
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n - (n >> 1);
},
/* Generate binary from string of 101... */
toBinary : function(s) { return parseInt(s.replace(/ /g, ""), 2); },
/* Generate binary 1s */
generateBinaryOnes : function(numOfOnes) {
return _.reduce(_.times(numOfOnes, function(i) {
return 1 << i
}), util.bitwiseOr, 0);
}
};
/*
A Card is represented by a number with a special bit allocation to represent the
rank and suit within the last 20 bits.
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
[hearts] [diamonds] [spades] [clubs] [ ] [ ] [ ] [A] [K] [Q] [J] [T] [9] [8] [7] [6] [5] [4] [3] [2]
*/
var model = Cards.model = new (function() {
this.suits = {}, this.ranks = {}, this.rankNames = {};
var that = this;
_.each(['HEARTS', 'DIAMONDS', 'SPADES', 'CLUBS'],
function(suitName, i) {
that.suits[suitName.toLowerCase()[0]] = that[suitName] = 1<<19-i;
});
_.each(['ACE', 'KING', 'QUEEN', 'JACK', 'TEN', '9', '8', '7', '6', '5', '4', '3', '2'],
function(rankName,i) {
that.ranks[rankName.toLowerCase()[0]] = that[rankName] = 1<<12-i;
that.rankNames[1<<12-i] = rankName
});
_.each(
['NINE', 'EIGHT', 'SEVEN', 'SIX', 'FIVE', 'FOUR', 'THREE', 'TWO'],
function(rankName, i) { that[rankName] = 1<<7-i; }
);
this.handTypes = ["HIGH_CARD", "PAIR", "TWO_PAIR", "TRIPS", "STRAIGHT", "FLUSH", "FULL_HOUSE", "QUADS", "STRAIGHT_FLUSH"];
_.each(this.handTypes, function(handType, i) { that[handType] = i; });
/**
For an explanation of the score boundaries see notes.txt
*/
this.handScores = {
'HIGH_CARD' : [0, 8000],
'PAIR' : [8001, 32776000],
'TWO_PAIR' : [32776001, 81936000],
'TRIPS' : [81936001, 114712000],
'STRAIGHT' : [120000000, 120008000],
'FLUSH' : [130000000, 130008000],
'FULL_HOUSE' : [140000000, 140006144],
'QUADS' : [150000000, 182776000],
'STRAIGHT_FLUSH': [190000000, 190008000],
}
this.rankOnly = util.generateBinaryOnes(13);
this.suitOnly = util.generateBinaryOnes(4) << 16;
})();
/* Parse one card in the format ad or 2s */
function parseCard(str) {
return model.ranks[str[0]] | model.suits[str[1]];
}
/*
Parse a full hand tolerant of casing and spaces in the format
as jd 2c 5s 3h
*/
Cards.hand = function() {
var hand = Array.prototype.join.call(arguments,'').replace(/ /g, '').toLowerCase();
return _.times(
hand.length / 2,
function(i) {
return parseCard(hand.substr(i * 2, i * 2 + 2));
}
);
};
/*
Set up the data object that the checkers will populate with maps/arrays
to perform quick operations
*/
var data = Cards.data = {};
/* Suited check */
Cards.isSuited = function (cards) {
if (cards.length == 5) {
return (cards[0] & cards[1] & cards[2] & cards[3] & cards[4] & model.suitOnly) > 0;
}
if (cards.length == 4) {
return (cards[0] & cards[1] & cards[2] & cards[3] & model.suitOnly) > 0;
}
if (cards.length == 3) {
return (cards[0] & cards[1] & cards[2] & model.suitOnly) > 0;
}
if (cards.length == 2) {
return (cards[0] & cards[1] & model.suitOnly) > 0;
}
return false;
};
/* Straight connectors check */
(function() {
function generateConnectedCombinations(len) {
var firstCombo = util.generateBinaryOnes(len) << 13-len;
var combos = _.times(13-len+1, function(i) { return firstCombo >> i });
var wheelCombo = util.generateBinaryOnes(len-1) | model.ACE
return _.flatten([combos, wheelCombo]);
}
data.connectedCombos = _.times(5, function(i) { return generateConnectedCombinations(i+2) });
})();
Cards.isConnected = function(cards) {
var score = Cards.highCardScore(cards);
var combos = data.connectedCombos[cards.length - 2];
for (var i = 0; i < combos.length; i++) {
if ((combos[i] ^ score) == 0) return true;
}
return false;
};
/* Paired, Trips, Quads, Full House Check */
var pairMap = {};
function calculatePairNumber(cards) {
var num = 0;
for (var i = 0; i < cards.length; i++) {
for (var j = i + 1; j < cards.length; j++) {
num += cards[i] & model.rankOnly & cards[j];
}
}
return num;
}
for (var i = 0; i < 13; i++) {
var iCard = model.ACE>>i;
// pairs
pairMap[calculatePairNumber([iCard, iCard])] = [model.PAIR, iCard];
// 3 kind
pairMap[calculatePairNumber([iCard, iCard, iCard])] = [model.TRIPS, iCard];
// 4 kind
pairMap[calculatePairNumber([iCard, iCard, iCard, iCard])] = [model.QUADS, iCard];
for (var j = 0; j < 13; j++) {
var jCard = model.ACE>>j;
// 2 pairs
if (j > i) {
pairMap[calculatePairNumber([iCard, iCard, jCard, jCard])] = [model.TWO_PAIR, iCard, jCard];
}
// full houses
if (i!=j) {
pairMap[calculatePairNumber([iCard, iCard, iCard, jCard, jCard])] = [model.FULL_HOUSE, iCard, jCard];
}
}
}
Cards.pairScore = function(cards) {
return pairMap[calculatePairNumber(cards)];
};
function Score() {
this.type = arguments[0]
this.score = arguments[1];
this.significantCard1 = model.rankNames[util.highbit(arguments[2])];
if (arguments.length > 3) {
this.significantCard2 = model.rankNames[util.highbit(arguments[3])];
}
}
Cards.score = function(cards) {
var highCardScore = Cards.highCardScore(cards);
var result = pairMap[calculatePairNumber(cards)];
if (result && result.length > 0) {
switch (result[0]) {
case model.PAIR:
score = model.handScores.PAIR[0] + model.handScores.HIGH_CARD[1] * result[1] + highCardScore;
return new Score("PAIR", score, result[1]);
case model.TWO_PAIR:
score = model.handScores.TWO_PAIR[0] + model.handScores.HIGH_CARD[1] * (result[1]|result[2]) + highCardScore;
return new Score("TWO_PAIR", score, result[1], result[2]);
case model.TRIPS:
score = model.handScores.TRIPS[0] + model.handScores.HIGH_CARD[1] * result[1] + highCardScore;
return new Score("TRIPS", score, result[1]);
case model.FULL_HOUSE:
score = model.handScores.FULL_HOUSE[0] + model.handScores.HIGH_CARD[1] * result[1] + highCardScore;
return new Score("FULL_HOUSE", score, result[1], result[2]);
case model.QUADS:
score = model.handScores.QUADS[0] + model.handScores.HIGH_CARD[1] * result[1] + highCardScore;
return new Score("QUADS", score, result[1]);
}
}
var connected = Cards.isConnected(cards);
var suited = Cards.isSuited(cards);
if (connected && suited) {
var score = model.handScores.STRAIGHT_FLUSH[0] + highCardScore;
return new Score("STRAIGHT_FLUSH", score, highCardScore);
}
if (suited) {
var score = model.handScores.FLUSH[0] + highCardScore;
return new Score("FLUSH", score, highCardScore);
}
if (connected) {
var score = model.handScores.STRAIGHT[0] + highCardScore;
return new Score("STRAIGHT", score, highCardScore);
}
return new Score("HIGH_CARD", highCardScore, highCardScore);
};
/* High Card Check */
Cards.highCardScore = function(cards) {
if (cards.length == 5) {
return (cards[0] | cards[1] | cards[2] | cards[3] | cards[4]) & model.rankOnly;
}
if (cards.length == 4) {
return (cards[0] | cards[1] | cards[2] | cards[3]) & model.rankOnly;
}
if (cards.length == 3) {
return (cards[0] | cards[1] | cards[2]) & model.rankOnly;
}
if (cards.length == 2) {
return (cards[0] | cards[1]) & model.rankOnly;
}
if (cards.length == 1) {
return (cards[0]) & model.rankOnly;
}
return 0;
};
})(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment