Click here to Skip to main content
13,254,366 members (56,776 online)
Click here to Skip to main content
Add your own
alternative version

Stats

8.6K views
283 downloads
13 bookmarked
Posted 20 May 2017

Video Poker

, 24 May 2017
Rate this:
Please Sign up or sign in to vote.
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 VideoPoker.zip 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 <divto 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

  • May 20, 2017 - Initial publish
  • May 21, 2017 - Fixed broken image links in article
  • May 24, 2017 - Updated Javascript file and article to accurately reflect poker conventions as suggested by DSAlCoda in comments below

 

License

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

Share

About the Author

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

You may also be interested in...

Comments and Discussions

 
QuestionSource Code Pin
Ehsan Sajjad16-Jun-17 8:42
professionalEhsan Sajjad16-Jun-17 8:42 
AnswerRe: Source Code Pin
Robert Welliever16-Jun-17 19:08
memberRobert Welliever16-Jun-17 19:08 
GeneralRe: Source Code Pin
Ehsan Sajjad17-Jun-17 3:33
professionalEhsan Sajjad17-Jun-17 3:33 
GeneralRe: Source Code Pin
Robert Welliever17-Jun-17 19:08
memberRobert Welliever17-Jun-17 19:08 
Questiondeal button function Pin
avisal13-Jun-17 5:36
professionalavisal13-Jun-17 5:36 
AnswerRe: deal button function Pin
Robert Welliever13-Jun-17 6:34
memberRobert Welliever13-Jun-17 6:34 
GeneralRe: deal button function Pin
avisal13-Jun-17 6:41
professionalavisal13-Jun-17 6:41 
QuestionHelpful Pin
AlexanderMurphy7925-May-17 0:14
memberAlexanderMurphy7925-May-17 0:14 
AnswerRe: Helpful Pin
Robert Welliever13-Jun-17 6:36
memberRobert Welliever13-Jun-17 6:36 
QuestionGreat Pin
Charles8025-May-17 0:03
memberCharles8025-May-17 0:03 
AnswerRe: Great Pin
Robert Welliever13-Jun-17 6:35
memberRobert Welliever13-Jun-17 6:35 
Question4 out of 5 because ... Pin
DSAlCoda24-May-17 6:46
memberDSAlCoda24-May-17 6:46 
AnswerRe: 4 out of 5 because ... Pin
Robert Welliever24-May-17 7:56
memberRobert Welliever24-May-17 7:56 
GeneralRe: 4 out of 5 because ... Pin
DSAlCoda24-May-17 8:07
memberDSAlCoda24-May-17 8: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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171114.1 | Last Updated 25 May 2017
Article Copyright 2017 by Robert Welliever
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid