Welcome back to our Deep Dive series on building the casino game Blackjack in C# and Blazor WebAssembly!

In this second part, we're going to use the modeling ideas we wrote down in the previous post to build a complete C# model for a Blackjack game.

In Poker: amazing hand! In Blackjack: also an amazing hand!

You might want to read the previous post in this series before reading this one. Here it is:

Blackjack in Blazor Part 1 - Rules and Modeling the Game
Let’s practice our modeling skills by breaking down the parts of Blackjack into definable, usable components.

Also, there's a sample GitHub repository that has all of the code used in this series.

exceptionnotfound/BlazorGames
Contribute to exceptionnotfound/BlazorGames development by creating an account on GitHub.

All caught up? Good! Let's jump right in.

Basic Enumerations

Let's begin our C# modeling with the most basic type we can model: enumerations for the card's suit and value.

public enum CardSuit
{
    Hearts,
    Clubs,
    Diamonds,
    Spades
}

public enum CardValue
{
    Ace = 1,
    Two = 2,
    Three = 3,
    Four = 4,
    Five = 5,
    Six = 6,
    Seven = 7,
    Eight = 8,
    Nine = 9,
    Ten = 10,
    Jack = 11,
    Queen = 12,
    King = 13
}

We will deal with the score later in this post.

Playing Cards

The next-most-simple object we can model is the individual playing cards. You may recall from the previous post that we decided that the Card object should have the following attributes:

  • A suit
  • A value
  • A score

Suits and values are already defined as enumerations, and by making them properties of the Card object we can calculate a score.

public class Card
{
    public CardSuit Suit { get; set; }
    public CardValue Value { get; set; }

    public int Score
    {
        get
        {
            if (Value == CardValue.King 
            	|| Value == CardValue.Queen 
                || Value == CardValue.Jack)
            {
                return 10;
            }
            if(Value == CardValue.Ace)
            {
                return 11;
            }
            else
            {
                return (int)Value;
            }
        }
            
    }
}
If you're wondering about the Aces, I will explain that more in a later section on score calculations.

Visibility

We need to consider one additional situation for this model, and that is this: the dealer has one card that is not visible to the player. So what model should that property (visibility) be a part of?

There are several ways to answer this question; the one we are going with is that we will make a new property IsVisible on the Card object. This property will be settable outside of the object so that the Dealer can mark their cards as not visible.

public class Card
{
	//... Other properties and methods

    public bool IsVisible { get; set; } = true;
}
Note that by default, cards ARE visible.

IsTenCard

Lastly, and as a convenience, we can define the property IsTenCard to identify if the given Card instance is a Ten, Jack, Queen, or King, and thus worth ten points.

public class Card
{
	//... Other properties and methods

    public bool IsTenCard
    {
        get
        {
            return Value == CardValue.Ten
                    || Value == CardValue.Jack
                    || Value == CardValue.Queen
                    || Value == CardValue.King;
        }
    }
}

Image URLs

We are implementing the display for our cards as individual images, with names like cardClubsJack.png, cardHeartsSix.png, cardSpadesAce.png, etc.

The images look like this:

Our Card object will need a property which stores the name of the image to display for that Card.

public class Card
{
    //... Other properties and methods
    
    public string ImageName { get; set; }
}

The Deck

The next object we can build is the deck of cards.

A giant fan with the slight of hand
Are these monochromatic cards? Photo by Sheri Hooley / Unsplash

A Collection of Cards

The deck, at its core, is very simple: it's a collection of Card objects. But what kind of collection?

One of the properties of a real-world deck of cards is that, when drawing a card, we always draw from the top of the deck. There is a collection class in .NET that implements similar functionality: the Stack<T> class. Stack<T> defines a Pop() method, which removes and returns the "topmost" object in the collection.

Our new CardDeck object will need a property of type Stack<Card> which stores the individual Card objects.

public class CardDeck
{
    protected Stack<Card> Cards { get; set; } = new Stack<Card>();
}
Note that the collection is protected; it is not accessible outside of the CardDeck class.

Count, Add, and Draw

We must now consider a few properties. First, we need to keep track of how many cards remain in the deck; if the number of cards gets below a certain value, we need to reshuffle the deck.

public class CardDeck
{
    //... Other methods and properties
    public int Count
    {
        get
        {
            return Cards.Count;
        }
    }
}

As we decided in the previous post, we need methods to add cards to the deck and to draw a card from the deck. These methods are fairly straightforward to implement:

public class CardDeck
{
	//... Other methods and properties

    public void Add(Card card)
    {
        Cards.Push(card);
    }

    public Card Draw()
    {
        return Cards.Pop();
    }
}
Push() and Pop() are defined by Stack<T>

Initialization and Shuffling

The most complex part of the CardDeck implementation is this: when we create a new instance of CardDeck, we need to first populate the cards with the correct number and suits, and then shuffle the cards thoroughly.

Let's start by creating a constructor for our CardDeck object, which first creates and inserts all of the cards needed.

public class CardDeck
{
    //... Other properties and methods

    public CardDeck()
    {
        List<Card> cards = new List<Card>();

        foreach (CardSuit suit 
                 in (CardSuit[])Enum.GetValues(typeof(CardSuit)))
        {
            foreach (CardValue value 
                     in (CardValue[])Enum.GetValues(typeof(CardValue)))
            {
                //For each suit and value, 
                //create and insert a new Card object.
                Card newCard = new Card()
                {
                    Suit = suit,
                    Value = value,
                    ImageName = "card" + suit.GetDisplayName() 
                                + value.GetDisplayName()
                };

                cards.Add(newCard);
            }
        }
    }
}
The GetDisplayName() function is a custom function; you can see the code for it in the GitHub repo.

We now need to extend this constructor to shuffle the cards. For this, we'll be using the venerable Fisher-Yates Shuffling Algorithm I blogged about a while back:

The Fisher-Yates Shuffling Algorithm in C# (And Why It Works So Well)
I’ve been working on an upcoming modeling practice [/tag/modelingpractice/] blogpost and I needed (once again[https://www.exceptionnotfound.net/simulating-candy-land-in-net-part-2-programming-the-game/]) to implement a card-shuffling algorithm. As always, I turned to the trusty Fisher-Yates shuf…

Here's that implementation:

public class CardDeck
{
    //... Other properties and methods

    public CardDeck()
    {
        //... Rest of constructor

            //First, convert the new Stack<T> of Card objects to an array.
            var array = cards.ToArray();

            Random rnd = new Random();

            //Step 1: For each unshuffled item in the collection
            for (int n = array.Count() - 1; n > 0; --n)
            {
                //Step 2: Randomly pick an element 
                //  which has not been shuffled
                int k = rnd.Next(n + 1);

                //Step 3: Swap the selected element with the 
                //  last "unstruck" element in the collection
                Card temp = array[n];
                array[n] = array[k];
                array[k] = temp;
            }

            //Finally, insert the now-shuffled cards into the Cards property.
            for (int n = 0; n < array.Count(); n++)
            {
                Cards.Push(array[n]);
            }
        }
    }
}

Our CardDeck object is now ready for use! We can move on to the more-complex objects, starting with Person.

The Person Object

Recall from the last post that we need a common Person object that both Player and Dealer will inherit from. That Person needs the following abilities:

  • Keep a hand of cards
  • Use the hand to calculate a score
  • Use the hand to determine if they are busted

The Visible Score Problem

First, though, we need to solve an unsolved problem from the previous post: how do we deal with the Dealer object's visible score (e.g. the score from only the face-up cards)?

Playing Cards
Visible score is 7+8=15, true score is unknown. Photo by Andrik Langfield / Unsplash

This is one of those places where the modeler (i.e. us) can make "executive decisions". There are a couple of ways to model the visible score, such as making it a property of the Dealer object, a property of the Person object, or an external method. For this post, I have chosen the second option: we will make a property VisibleScore that is part of the root Person object, since it is Person that holds the property for Cards. Please note that none of these options are inherently worse than the others, they just require different implementation details.

With all that in mind, we can build a Person object with a set of properties to represent those abilities. Here's the skeleton object:

public class Person
{
    public List<Card> Cards { get; set; } = new List<Card>();
}

We now need to fill in the other properties.

True Scores and Visible Scores

The most complex thing the Person object will do is calculate that person's current score. Because we want a property for the VisibleScore and a property Score for the true score, we will use a private method to calculate the score.

The algorithm to calculate the score goes like this:

  1. PASS IN a value that determines whether we calculate the visible score only, or the true score.
  2. IF the sum value of all cards is less than or equal to 21, return that sum.
  3. IF there are no Aces in the person's hand AND the sum is greater than 21, the person has bust, so return the score.
  4. IF there are Aces in the person's hand...
  5. WHILE there are Aces left that have not been converted
  6. CONVERT a single Ace to being worth 1 point.
  7. IF the score is now less than or equal to 21, return the score.
  8. END WHILE
  9. IF the score is STILL greater than 21, the person has bust, so return the score.

The resulting code looks something like this:

public class Person
{
    //... Other properties and methods

    //1. Pass in a value for the "only visible score" situation
    private int ScoreCalculation(bool onlyVisible = false)
    {
        var cards = Cards;

		//1. Filter the Cards to only include the visible cards
        //   if that parameter is set to true.
        if (onlyVisible)
        {
            cards = Cards.Where(x => x.IsVisible).ToList();
        }

        //2. If the sum total of all cards is <= 21, return that value
        var totalScore = cards.Sum(x => x.Score);
        if (totalScore <= 21) return totalScore;

        //3. If there are no Aces and the sum total is > 21, 
        //   the person has bust
        bool hasAces = cards.Any(x => x.Value == CardValue.Ace);
        if (!hasAces && totalScore > 21) return totalScore;

        //By this point, the sum will be greater than 21 
        //  if all the Aces are worth 11
        //So, make each Ace worth 1, until the sum is <= 21
        
        var acesCount = cards.Where(x => x.Value == CardValue.Ace).Count();
        var acesScore = cards.Sum(x => x.Score);

        //5. While there are Aces left...
        while (acesCount > 0)
        {
            //6. Make an Ace worth 1 point
            acesCount -= 1;
            acesScore -= 10;
            //7. If the score is now less than or equal to 21, 
            //   return the score.
            if (acesScore <= 21) return acesScore;
        } //8

        //9. If the score never gets returned, the person has bust
        return cards.Sum(x => x.Score);
    }
}

We can then make two properties; one for the true score, and one for the visible score.

public class Person
{
    //... Other properties and methods
    public int Score
    {
        get
        {
            return ScoreCalculation();
        }
    }

    public int VisibleScore
    {
        get
        {
            return ScoreCalculation(true);
        }
    }
}

Checking for Blackjack and Bust

In the previous post, we decided that we wanted a special display that normally displays the Person's score, but will also show when they get a blackjack and when they bust.

The literal meaning. Found on Wikimedia, used under license.

To do this, we first need a property that checks to see if the Person has a blackjack. A person has a blackjack if;

  • Their score is exactly 21 AND
  • They have exactly two cards AND
  • One of their cards is an Ace AND
  • The other card is a ten-card

Here's the property for this:

public class Person
{
    //... Other properties and methods
    
    public bool HasNaturalBlackjack =>
        Cards.Count == 2
        && Score == 21
        && Cards.Any(x => x.Value == CardValue.Ace)
        && Cards.Any(x => x.IsTenCard);
}
This syntax is called expression-bodied members. You'll see it quite a lot in this series.

We also want a convenience property that shows if the Person has bust:

public class Person
{
    //... Other properties and methods
    
    public bool IsBusted => Score > 21;
}
The correct term is not "is busted" but rather "has bust". I'm ignoring this in my code.

The Score Display

Using the properties HasNaturalBlackjack and IsBusted that we just defined, we can now create a property to display the Person object's score. This property uses all of the properties of Person that we have defined so far:

public class Person
{
    //... Other properties and methods
    
    public string ScoreDisplay
    {
        get
        {
            if (HasNaturalBlackjack && Cards.All(x => x.IsVisible))
                return "Blackjack!";

            //We use Visible Score because this display
            //should only show the Dealer's visible score,
            //and the Player's visible score and true score
            //will always be the same value.
            var score = VisibleScore;

            if (score > 21)
                return "Busted!";
            else return score.ToString();
        }
    }
}

Convenience Methods - Adding a Card and Clearing the Hand

There are two convenience methods we still need, though your implementation may forego them.

First we need a straightforward method to add cards to the Person's hand:

public class Person
{
    //...Other properties and methods
    
    public async Task AddCard(Card card)
    {
        Cards.Add(card);
        await Task.Delay(300);
    }
}
Why are we doing Task.Delay here? You'll find out in the next part of this series.

Finally, we need a method to clear the Person's hand:

public class Person
{
    //...Other properties and methods
    
    public void ClearHand()
    {
        Cards.Clear();
    }
}

With all of that, our root Person object is complete! Now we can move on to the Dealer and Player objects.

The Dealer Object

Let's start with the Dealer object.

A blackjack dealer shuffles at a blackjack table as three players smile for the camera.
All smiles, except the one guy. Image from Wikimedia, used under license.

The Dealer needs to inherit from the root Person object:

public class Dealer : Person { }

From there, we can add the properties unique to the Dealer

The Card Deck

Let's start with a property for the CardDeck object:

public class Dealer : Person 
{
    public CardDeck Deck { get; set; } = new CardDeck();
}
When CardDeck is instantiated, it creates and shuffles all the necessary cards.

Dealing

The Dealer object will also need three methods:

  • A method that draws a card from the deck.
  • A method that draws a card from the deck to give to the player.
  • A method that draws a card from the deck to give to the dealer.

These methods end up being pretty straightforward:

public class Dealer : Person
{
    //... Other properties and methods
   
    public Card Deal()
    {
        return Deck.Draw();
    }
   
    public async Task DealToSelf()
    {
        await AddCard(Deal());
    }

    public async Task DealToPlayer(Player player)
    {
        await player.AddCard(Deal());
    }
}

Has an Ace Showing

We also need one convenience property that is used for the Insurance special play: whether or not the Dealer has an Ace showing.

public class Dealer : Person
{
    //... Other properties and methods
    
     public bool HasAceShowing => Cards.Count == 2 
                                  && VisibleScore == 11 
                                  && Cards.Any(x => x.IsVisible == false);
}

With that, our Dealer is complete, and we can move on to implementing the Player object.

The Player Object

The Player object, like the Dealer, needs to inherit from Person.

public class Player : Person { }

Funds

The most distinguishing characteristic of the Player is that they have funds, i.e. the money they walked up to the table with. Let's create a property for those funds, and set an initial amount:

public class Player : Person 
{
     public decimal Funds { get; set; } = 200M; //$200
}

Bets

We also need properties to keep track of the Player's bets: their main bet they make before each hand, and the optional Insurance bet.

public class Player : Person
{
    //... Other properties and methods
    
    public decimal Bet { get; set; }

    public decimal InsuranceBet { get; set; }
}

We will also want a convenience property to show whether or not the Player has made an insurance bet:

public class Player : Person
{
    //... Other properties and methods
    
    public bool HasInsurance => InsuranceBet > 0M;
}

The Change Amount

After each hand, the player's funds may change based on whether they won or lost the bet. Let's write a property that keeps track of this change amount.

public class Player : Person
{
    //... Other properties and methods
    
    public decimal Change { get; set; }
}

Has Stood

We need a convenience property to show whether or not the player has decided to stand:

public class Player : Person
{
    //... Other properties and methods
    
    public bool HasStood { get; set; }
}

Payouts

Finally, we need a method for the player to adjust their Funds property based on whether they won or lost the bet after each hand.

public class Player : Person
{
    //... Other properties and methods
    
    public void Collect()
    {
        Funds += Change;
        Change = 0M;
        InsuranceBet = 0M;
    }
}

Now our Player object is complete and ready to gamble his/her chips!

A collection of poker chips in various colors.
Watch out, you tend to end up with less than you started with. Photo by Amanda Jones / Unsplash

Summary

Phew! That was a lot of work. But now our implementation of Blackjack in C# is ready to go, and we can start building the Blazor components which will make up the display area of the game.

We can now build components for:

  • The Player and Dealer hands
  • The Player and Dealer scores (visible and total)
  • The Player's funds and how they change after each hand
  • The current bet
  • The status of the Player (e.g. whether or not they have stood) AND
  • The result of the hand (e.g. whether the Player wins or loses)

Implementing these components and more is exactly what we will do in the next part of this series. Stick around!

Do you have a way you can improve on my design? Submit a pull request or let me know in the comments! I am always looking for input from my dear readers.

Happy Coding!


Hello Dear Reader! Want to get the best C#, ASP.NET, web tech, tips, and stories anywhere on the Web? Sign up to receive my premium newsletter The Catch Block! Each Wednesday, you'll get the best reads, job listings, stories, tips, and news from around the ASP.NET and C# worlds. All for only $5/month or $50/year! Become a subscriber today!