Created
October 24, 2014 13:14
-
-
Save npiv/438c084a4c6a719b886d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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