Creating a Video Poker Game with Raphael
In this post, we will continue our examination of Raphaël in creating a video poker game. As we learned in the previous post, SVG lets you easily keep track of objects in the DOM and have them respond to events. Coupled with the fact that besides the cards, most of the objects in the game don't change very much and the few number of objects makes this game perfect for Raphaël/SVG.
Video Poker Basics
Video Poker is a variant of poker typically played on touchscreen devices in casinos. Before the hand begins the player makes a bet. Five cards are then drawn from the deck. Any or none of the cards can be selected to be held through the next draw. The cards that were not held are discarded and new cards are drawn. At that point, the hand is evaluated for a win. The list below shows the winning hands from strongest to weakest.
- Royal Flush - 10, J, Q, K, A of the same suit
- Straight Flush - five cards of the same suit in numeric order
- Four of a Kind
- Full House - A three of a kind and a pair
- Flush - five cards of the same suit
- Straight - cards of any suit in numeric order
- Three of a Kind
- Two Pair
- Pair - generally Jacks or Higher
Our game will only use a single hand at a time. Other variants allow you to play up to one hundred hands at a time or employ thresholds that must be met before you can play a subsequent hand.
Creating a Deck and Cards
The Deck and Card classes are both classes that were written for a Concentration game in the book that have been tweaked to work better with Poker. The Deck class is essentially a array with some extra functions to facilitate shuffling, dealing, and instantiating cards. The Card class contains two images, one each for the front and back of the card with some metadata to help in evaluating poker hands. The images for the cards were generated from data included in the SVG-cards project. While the raw SVG paths could have been used instead, dealing with images is a touch bit more concise. The code block below shows the function for shuffling a deck. The current shuffling algorithm guarantees that each card will be swapped. The number of swaps will grow with the number of decks. Luckily, swaps are cheap and most games will use 5 or less decks and only re-shuffle when deck runs out.
self.shuffleDecks = function () {
var swap = function(i,j) {
var temp = self.cards[j];
self.cards[j] = self.cards[i];
self.cards[i] = temp;
}
for(var j = 0; j<numDecks; j++) {
for(var i = (numDecks * 51); i>=0; i--) {
var r = self.rand(i);
swap(i,r);
}
}
To counter card-counting in games like BlackJack, some casinos employ continuous shuffling machines. Card-counting is not an issue in Poker but if modeling a BlackJack game, it's one thing you might consider.
Evaluating Poker Hands
Having already created Decks, Cards, and Buttons for other applications, most of the new work for this game centered around finding an algorithm to evaluate poker hands. It's a bit of a stumper for a while until you realize that a Hand is just an array of Cards. Evaluating them is easy as long as you have good tools to do array manipulation.
I chose underscore.js
which is a utility kit for interacting with arrays, maps, functions, and
objects. What JQuery does for the DOM, underscore.js does for everything
else. As its name implies, all of its functions attach to the _
namespace. There are four functions that are of particular use to us:
compact, groupBy, max,
and pluck
. compact
returns an array with
all the "falsy" values removed(NaN, 0, false, undefined, ''). groupBy
returns an object grouping the values as indicated in a function
parameter. The snippet below shows a groupBy applied to a pseudo-hand.
_.groupBy( ["A", "3", "5", "5", "5"], (num) -> num);
Returns => {A:["A"], 3:[3],5:[5,5,5]}
Our conditions require us to consider both the ordinal value of the cards and the suit. Listed below are functions to make the groupBy operate based on the ordinal or suit value.
ordinalHandler: (card) ->
return card.val
suitHandler: (card) ->
return card.suit
We next need a way to check the length of individual keys in the object groupBy returns. findLength walks the keys of an object and attempts to find a key with the indicated length. The presence of a match means there is a pair, three of a kind, or four of a kind present in the hand. The last line of the function uses compact to remove all the misses. Just below it, we can see how the those functions combine to find a pair.
findLength: (grouped, value) ->
x = []
for key of grouped
if grouped[key].length is value
x.push(key)
_.compact(x)
checkPair: (hand) ->
sorted = _.groupBy hand.cards, @ordinalHandler
pair = @findLength(sorted, 2)
return pair
Finding a Straight hand is easy thanks to the pluck
function. It
returns an array of values that correspond to the given property. In
this case, it is Card.val. After sorting the array, we evaluate each
value to see if it corresponds to the expected value in the range. We
can see the code for this in the snippet below.
checkStraight: (hand) ->
vals = _.pluck(hand.cards, "val")
vals.sort()
startValue = vals[0]
for i in [0...5]
return false if startValue+i isnt vals[i]
return "Straight" if vals is [1, 10, 11, 12, 13]
return "Straight"
checkFlush: (hand) ->
sorted = _.groupBy hand.cards, @suitHandler
flush = @findLength(sorted, 5)
if flush.length isnt 0
royal = @checkRoyalFlush(hand)
straightFlush = @checkStraightFlush(hand)
return royal if royal
return straightFlush if straightFlush
return "Flush"
Because there are only seven hand variants to check, I went with a mode of attack that checks them all. All the returned values get popped into an array and that array is walked to generate a new array with the winnings for each possible hand(shown in basePayouts). compact is used to remove the values that don't result in a win. From those values, the maximum is found. This tactic allows the proper payout to be calculated even though for instance, a Royal Flush is a Flush and a Straight Flush as well.
evaluate: (hand) ->
currentValue = 0
a = @checkFlush(hand)
b = @checkFourKind(hand)
c = @checkFullHouse(hand)
d = @checkThreeKind(hand)
e = @checkStraight(hand)
f = @checkTwoPair(hand)
g = @checkJacksOrBetter(hand)
values = (@basePayouts[val] for val in [a,b,c,d,e,f,g])
values = _.compact(values)
_.max(values)
@basePayouts = {
JacksOrBetter: 1
TwoPair: 2
ThreeKind: 3
Straight: 4
Flush: 6
FullHouse: 9
FourKind: 25
StraightFlush: 50
RoyalFlush: 250
}
Source Code
Now that you've learned how it works, play the game or download the source code here.