Click here to Skip to main content
15,867,686 members
Articles / Web Development / HTML

Video Poker

Rate me:
Please Sign up or sign in to vote.
4.94/5 (19 votes)
24 May 2017Public Domain8 min read 33.4K   924   16   13
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.

Image 1

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:

JavaScript
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:

JavaScript
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.

JavaScript
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:

Image 2

Cards dealt from the Deck go to the Hand object which holds the player’s five active Card objects.

JavaScript
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:

JavaScript
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.:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
United States United States
Robert Welliever is a software developer living in the Pacific Northwest.

Comments and Discussions

 
QuestionSource Code Pin
Ehsan Sajjad16-Jun-17 7:42
professionalEhsan Sajjad16-Jun-17 7:42 
AnswerRe: Source Code Pin
Robert Welliever16-Jun-17 18:08
Robert Welliever16-Jun-17 18:08 
GeneralRe: Source Code Pin
Ehsan Sajjad17-Jun-17 2:33
professionalEhsan Sajjad17-Jun-17 2:33 
GeneralRe: Source Code Pin
Robert Welliever17-Jun-17 18:08
Robert Welliever17-Jun-17 18:08 
Questiondeal button function Pin
avisal13-Jun-17 4:36
professionalavisal13-Jun-17 4:36 
AnswerRe: deal button function Pin
Robert Welliever13-Jun-17 5:34
Robert Welliever13-Jun-17 5:34 
GeneralRe: deal button function Pin
avisal13-Jun-17 5:41
professionalavisal13-Jun-17 5:41 
QuestionHelpful Pin
AlexanderMurphy7924-May-17 23:14
AlexanderMurphy7924-May-17 23:14 
I find this really helpful, you got me into poker quickly!
AnswerRe: Helpful Pin
Robert Welliever13-Jun-17 5:36
Robert Welliever13-Jun-17 5:36 
QuestionGreat Pin
Charles8024-May-17 23:03
Charles8024-May-17 23:03 
AnswerRe: Great Pin
Robert Welliever13-Jun-17 5:35
Robert Welliever13-Jun-17 5:35 
Question4 out of 5 because ... Pin
DSAlCoda24-May-17 5:46
DSAlCoda24-May-17 5:46 
AnswerRe: 4 out of 5 because ... Pin
Robert Welliever24-May-17 6:56
Robert Welliever24-May-17 6:56 
GeneralRe: 4 out of 5 because ... Pin
DSAlCoda24-May-17 7:07
DSAlCoda24-May-17 7:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.