Video Poker






4.94/5 (18 votes)
Classic, Arcade-Style Video Poker
Introduction
I’ve enjoyed the simple gameplay of poker from the LCD handhelds of yesteryear to the arcade machines in casinos of today. Not finding an accessible Video Poker represented on CodeProject, I wanted to share an implementation of my favorite classic version with you, my technology friends.
To Use
To begin, download the source code file and unzip. Within extracted directory Video Poker, you will find three code files; VideoPoker.js, VideoPoker.css and VideoPoker.html, and two resource directories containing audio and image files. If you have depth of programming experience, you may comfortably skip to modifying code. It is relatively short, readable and heavily commented to aid comprehension. Just open code files in a text editor to read or modify, or open VideoPoker.html in any browser to run.
Architecture
As many others, I play games on both small and large displays while riding different hardware stacks. In order to facilitate this resolution and device diversity, I selected all-natural JavaScript, HTML and CSS.
To render at different resolutions, HTML and CSS provide qualified help. While many games use a single draw surface set to the window size, doing so may require arduous code to maintain positional and layout state of drawn objects at different resolutions. Instead, this game limits the drawn area to the dealt hand, and then leverages the baked-in layout skill of HTML with CSS to handle the remaining interface. As a result, this implementation plays well in a window as narrow as 480 pixels, or in much wider windows (e.g. 1200px) while retaining lighter-weight code.
To facilitate equal performance between differently-abled devices, screen updates are event-driven rather than rendered continuously. Continuous rendering requires games execute in infinite loop, rapidly switching between calculating state and rendering frames (often measured in frames-per-second). Alternatively, this game renders updates only in response to interaction. This event-driven design puts less stress on hardware because the UI calculates and draws only in response to the user.
So by structuring with HTML and using event-driven UI updates, we arguably have performant, responsive code split comfortably between VideoPoker.html, VideoPoker.js and VideoPoker.css. On a side note, this model works well beyond gaming applications. If we didn’t include our heavier audio and image files, we might run this game in a page as interactive advertising (e.g., casino affiliate marketing), or as a splash/loading screen to engage a user while a more intensive application loads.
Code
Upon opening the JavaScript file, VideoPoker.js, we first meet a self-describing enumeration GameStates
listing the game's states:
var GameStates = { // Game state enumeration
Uninitialized: 0,
FirstDeal: 1,
SecondDeal: 2,
HandLost: 3,
HandWon: 4,
GameOver: 5
}
The application begins in GameStates.Uninitialized
, with a topmost <div>
element covering the game window. This topmost loading screen masks the interface until assets are initialized. Each loaded resource (image or audio) fires a load-complete handler that decrements a global count of loading resources. The last loaded resource drops the <div
> to allow play.
Moving down the JavaScript, we find a block of global constants and properties defining gameplay, some of which look like:
var _GameState = GameStates.Uninitialized; // Initial game state
var _StartCredits = 100; // Number of starting credits
var _Credits = _StartCredits; // Number of current credits
var _CurrentBet = 1; // Amount of bet
...
And below that, we have a trio of objects that comprise our main data structures, Deck
, Hand
and Card
. A Deck
object represents a standard fifty-two card poker deck, and contains expected functions such as Shuffle
and Deal
.
var Deck = { // Deck Object - A 52 card poker deck
Cards: null, // Array of Card objects representing one deck
Shuffled: null, // Array of Card objects representing a shuffled deck
SpriteSheet: null, // Image object of uncut card deck
SpriteWidth: 230, // pixel width of card in source image
SpriteHeight: 300, // pixel height of card in source image
Initialize: function () {...},
Shuffle: function () {...},
Deal: function (numCards) {...}
...
The Deck
also contains a reference to the card images, which are chunked as a single image asset called a sprite sheet. Essentially, we use a sprite sheet because one Image
object requires less handler code than fifty-two separate Image
objects. For visualization, our card sprites look something like this:
Cards dealt from the Deck
go to the Hand
object which holds the player’s five active Card
objects.
function Hand(cards) { // Hand object - The player's active Card objects
this.Cards = cards; // Array of Card objects
this.Evaluate = function () {...} // Return ID of winning hand type, or -1 if losing hand
this.IsRoyal = function () {...}
this.IsFullHouse = function () {...}
this.IsFourOfAKind = function () {...}
this.IsFlush = function () {...}
this.IsStraight = function () {...}
this.IsThreeOfAKind = function () {...}
this.IsTwoPair = function () {...}
this.IsJacksOrBetter = function () {...}
...
The Hand
object additionally serves instance routines for checking winning-ness (e.g., flush, full-house, straight). A point to note is that the overall procedure for checking a winning hand is not optimized. For example, the IsFullHouse and IsStraight routines both require sorted cards as part of their evaluation. Instead of passing a sorted Card
array, each routine instead sorts the cards redundantly. This inefficiency was taken purposefully so that each routine is logically encapsulated, hopefully making it more modifiable for beginners. Also, there may be points of controversy surrounding interpretation of poker rules. For example, should the IsTwoPair
function return true if a hand contains four Kings? Technically, yes, so I coded it that way. Others may choose differently. The precedence of winning hands makes this a moot point in actual code execution, but thought I would note that subjectivity exists.
Moving along, the Card
object is purely structural, looking a lot like this:
function Card(id, suit, rank, x, y, width, height) { // Represents a standard playing card.
this.ID = id; // Card ID: 1-52
this.Suit = suit; // Card Suit: 1-4 {Club, Diamond, Heart, Spade}
this.Rank = rank; // Card Rank: 1-13 {Ace, Two, ..King}
this.X = x; // Horizontal coordinate position of card image on sprite sheet
this.Y = y; // Vertical coordinate position of card image on sprite sheet
this.Width = width; // Pixel width of card sprite
this.Height = height; // Pixel height of card sprite
this.Locked = false; // true if Card is Locked/Held
this.FlipState = 0; // The flip state of card: 0 or 1 (Back Showing or Face Showing)
}
Now alert coders may have already noticed that the Deck
, Hand
, and Card
objects would be their own class files in a proper object-oriented environment. In a small application like this, I have chosen to mash all JavaScript to a single file for expedience in understanding architecture. You might break it apart with further development.
Continuing on, we find a spattering of handlers like _DealClick
and _Bet
that execute on player interaction. I will not fully describe these routines in this article as the code should provide sufficient context, but we might look at one now. The _Bet
routine responds to a "bet up" or "bet down" action by adjusting the player's credits, playing a related sound effect, and then updating the UI. These steps are hopefully plain within the routine itself, i.e.:
function _Bet(action) {
if (_GameState !== GameStates.FirstDeal &&
_GameState !== GameStates.HandWon &&
_GameState !== GameStates.HandLost)
return; // Only allow bet before being dealt
if (action === '-') { // Bet down requested
if (_CurrentBet > 1) { // Govern minimum bet
_CurrentBet -= 1; // Decrement bet
GameAudio.Play('BetDown');
}
}
else if (action === '+') { // Bet up requested
if (_CurrentBet < 5 && _CurrentBet < _Credits) { // Govern maximum bet
_CurrentBet += 1; // Increment bet
GameAudio.Play('BetUp');
}
}
_UpdateBetLabel();
_UpdateCreditsLabel();
}
Also in the code, we find functions related to drawn elements. The player's five Card
objects filling the Hand
are a drawn part of the interface, handled by HTML's Canvas
object. By obtaining the graphics context from our instance of Canvas
, we may render with it. Here is our outermost draw routine:
function _DrawScreen() { // Render UI update
if (_GameState == GameStates.Uninitialized) // Redrawn only if loading screen is down
return;
var g = _Canvas.getContext('2d'); // Graphics context
g.clearRect(0, 0, _Canvas.width, _Canvas.height); // Wipe frame clean
for (var i = 0; i < _Hand.Cards.length; i++) { // for each Card in Hand
if (_Hand.Cards[i].FlipState === 1)
_DrawCardFace(g, i); // FlipState == 1
else
_DrawCardBack(g, i); // FlipState == 0
if (_GameState === GameStates.SecondDeal && _Hand.Cards[i].Locked) // Second deal
_DrawCardHold(g, i); // Card is locked by player
}
_UpdateBetLabel(); // Refresh html bet elements
_UpdateCreditsLabel(); // Refresh html credits elements
if (_GameState == GameStates.HandLost || _GameState == GameStates.HandWon)
_DrawHandOverMessage(g);
}
After checking we are in a state where drawing is allowed, we call getContext
to get our graphics context g
. With g
, we first clean the draw surface by calling its native function clearRect
and supplying the geometric bounds to clear. We then call our own draw routines _DrawCardFace
or _DrawCardBack
depending on the FlipState
of the Card
. We finish with drawing ancillary effects and updating HTML elements. If we look at the routine _DrawCardBack
, we may understand some actual rendering:
function _DrawCardBack(g, cardIndex) {
g.save(); // Push styling context
g.fillStyle = '#300'; // Set dark red card back
var cardX = _HandX + (cardIndex * (_CardWidth + 4) + 4); // Card x position (4px buffer)
g.fillRect(cardX, 0, _CardWidth, _CardHeight); // Render card back
g.restore(); // Pop styling context
}
We call save
to push the current styling context into memory, and call restore
to pop it back out. In this case, we are only setting the fillStyle
property, so we might just reset that specific property and remove any need to save
and restore
the entire styling context, but I have found always calling save
and restore
before setting style properties provides a standardization that prevents bugs and enhances readability.
Beyond manual drawing, we also utilize an in-built function for UI rendering. There is a special thread from the current Window
that executes a specific block of code each time a span of time has elapsed. We can use this interval function to handle a blinking effect on the prize marquee that occurs with each win. With each execution of the function, we alternate wax on and wax off, toggling the prize row's CSS:
function _PrizeWinBlink() // Handles marquee blink on winning prize row
{
_BlinkOn = !_BlinkOn; // Toggle the effect
var rowStyle = document.getElementById('row' + _WinID).style; // Winning prize
// row's style property
rowStyle.color = _BlinkOn ? '#fff' : '#fc5'; // white to yellow
rowStyle.textShadow = _BlinkOn ? '0 0 1px #fff' : '0 0 10px #a70'; // Toggle white to
// yellow shadow
}
We set this interval function by calling setInterval
and supplying a handler and the interval of time between executions (i.e., setInterval(_PrizeWinBlink, 400)
, which executes the above code after each four hundred millisecond span). While termination of the current Window
theoretically also terminates any running interval functions, I have noticed this is not always the case. Despite widespread support for the Window
object, it is not fully standardized. I can reproduce instances of orphaned interval functions under conditions of multiple browser windows. We can mitigate this by keeping a reference to each interval function, and then forcing shut down when the Window
attempts to unload normally:
window.onbeforeunload = function () {
if (_PrizeWinThread != null) // If marquee blinking effect is running
// when app is closing
clearInterval(_PrizeWinThread); // Terminate
};
Jumping to the bottom of the JavaScript is the last bit of code I want to address, the GameAudio
object, which handles the game's different Audio
objects.
The Audio
object is a native JavaScript object implemented per vendor. This equates to wavering audio support. To ensure we have audio effects that we can hear across different configurations, we need multiple encodings of the same file. For each sound effect in the game, three versions are created; OGG, MP3, and WAV. To help select which version we need, each vendor is required to respond to the question, can you play this media type (often called a MIME type)? Interestingly, Firefox (as others), reports back probably if the specified media type appears to be playable, maybe if it cannot tell if the media type is playable without playing it, or an empty string if the specified media type definitely cannot be played (https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType). Finding the non-empty responses a bit wonky, I did some cross-browser experimentation and found the empty string to be a reasonable indicator. By picking the media type using the following order, we arguably have good compatibility while factoring quality/size ratio:
var audio = new Audio();
var oggCapable = audio.canPlayType('audio/ogg') !== ''; // Check for OGG Media type
var mp3Capable = audio.canPlayType('audio/mpeg') !== ''; // Check for MPEG Media type
var wavCapable = audio.canPlayType('audio/wav') !== ''; // Check for WAV Media type
var extension = oggCapable ? 'ogg' : mp3Capable ? 'mp3' : wavCapable ? 'wav' : '';
...
Because an Audio
object wraps one sound, we require multiple Audio
objects of the same sound to achieve overlapping, asynchronous play of a particular effect. For example, a user may click the “Bet Up
” button twice in one second. If a single Audio
object one second in duration played, then the player would not hear a second sound on the second click. We must use multiple Audio
objects per effect, allowing us to buffer sound and achieve overlapping play. We just retrieve an effect's buffer, increment its buffer index (or set to beginning if at end of buffer), and then play it:
var buffer = this._SoundEffects[soundName]; // Get the buffer per sound effect
var bufferIndex = this._SoundEffects[soundName + "I"]; // Get buffer's current index
bufferIndex = bufferIndex === buffer.length - 1 ?
0 : bufferIndex + 1; // Increment or reset if at end
this._SoundEffects[soundName + "I"] = bufferIndex; // Set buffer index
buffer[bufferIndex].play(); // Play sound effect at buffer index
...
So that's largely it. Mix a few data structures, event handlers, draw routines and out pops Video Poker. I have glossed over sections of JavaScript and most of the HTML/CSS in this article assuming code reads easier than explanations of code. If I am wrong and you have pressing questions, I might answer them in the comments below. Otherwise, thanks for your attention and happy coding!
History
- 20th May, 2017 - Initial publication
- 21st May, 2017 - Fixed broken image links in article
- 24th May, 2017 - Updated JavaScript file and article to accurately reflect poker conventions as suggested by DSAlCoda in the comments below