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 = {
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;
var _StartCredits = 100;
var _Credits = _StartCredits;
var _CurrentBet = 1;
...
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 = {
Cards: null,
Shuffled: null,
SpriteSheet: null,
SpriteWidth: 230,
SpriteHeight: 300,
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) {
this.Cards = cards;
this.Evaluate = function () {...}
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) {
this.ID = id;
this.Suit = suit;
this.Rank = rank;
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
this.Locked = false;
this.FlipState = 0;
}
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;
if (action === '-') {
if (_CurrentBet > 1) {
_CurrentBet -= 1;
GameAudio.Play('BetDown');
}
}
else if (action === '+') {
if (_CurrentBet < 5 && _CurrentBet < _Credits) {
_CurrentBet += 1;
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() {
if (_GameState == GameStates.Uninitialized)
return;
var g = _Canvas.getContext('2d');
g.clearRect(0, 0, _Canvas.width, _Canvas.height);
for (var i = 0; i < _Hand.Cards.length; i++) {
if (_Hand.Cards[i].FlipState === 1)
_DrawCardFace(g, i);
else
_DrawCardBack(g, i);
if (_GameState === GameStates.SecondDeal && _Hand.Cards[i].Locked)
_DrawCardHold(g, i);
}
_UpdateBetLabel();
_UpdateCreditsLabel();
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();
g.fillStyle = '#300';
var cardX = _HandX + (cardIndex * (_CardWidth + 4) + 4);
g.fillRect(cardX, 0, _CardWidth, _CardHeight);
g.restore();
}
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()
{
_BlinkOn = !_BlinkOn;
var rowStyle = document.getElementById('row' + _WinID).style;
rowStyle.color = _BlinkOn ? '#fff' : '#fc5';
rowStyle.textShadow = _BlinkOn ? '0 0 1px #fff' : '0 0 10px #a70';
}
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)
clearInterval(_PrizeWinThread);
};
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') !== '';
var mp3Capable = audio.canPlayType('audio/mpeg') !== '';
var wavCapable = audio.canPlayType('audio/wav') !== '';
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];
var bufferIndex = this._SoundEffects[soundName + "I"];
bufferIndex = bufferIndex === buffer.length - 1 ?
0 : bufferIndex + 1;
this._SoundEffects[soundName + "I"] = bufferIndex;
buffer[bufferIndex].play();
...
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