NashCoding Yet Another Artificial Intelligence Blog

3Oct/104

A C# Blackjack Simulation Framework

Since I haven't had time to sit down and write new code lately, I decided to dig into the closet and start pulling out code that I've worked on but haven't released yet. Today's gem is a framework for simulating Blackjack (also known as 21) strategies.

Blackjack is a popular casino game where each player is dealt a two-card hand (typically face-up) and the dealer is dealt a two-card hand with one card face up and the other face down. Your goal is to get your hand's value closer to 21 than the dealer's, without going over ("busting").

Creating a Blackjack Game

The basic game rules are well known, but each casino has its own minor variants that can affect the odds and correct plays. The NashCoding Blackjack Framework (NBF) enables simulating a wide variety of these variants via the BlackjackSettings class:

/// <summary>
/// A collection of various settings for a game of Blackjack.
/// </summary>
public class BlackjackSettings
{
    /// <summary>
    /// The number of 52-card decks contained in the dealer's shoe.
    /// </summary>
    public int DecksPerShoe { get; set; }

    /// <summary>
    /// The number of cards to deal before the shoe is reshuffled.
    /// </summary>
    public int MinCardsDealtBeforeReshuffle { get; set; }

    /// <summary>
    /// The minimum amount a player can bet.
    /// </summary>
    public decimal MinimumBet { get; set; }

    /// <summary>
    /// The maximum amount a player can bet.
    /// </summary>
    public decimal MaximumBet { get; set; }
                
    /// <summary>
    /// The threshold where the dealer stops hitting and 
    /// stands (typically 17).
    /// </summary>
    public int DealerHardStandThreshold { get; set; }

    /// <summary>
    /// The threshold where the dealer stops hitting and
    /// stands if one of their cards is a soft ace.
    /// </summary>
    public int DealerSoftStandThreshold { get; set; }
        
    /// <summary>
    /// The reward multiplier for Blackjack, default is
    /// 2.5 (i.e., pays 1.5:1).
    /// </summary>
    public decimal BlackjackPayoff { get; set; }
        
    /// <summary>
    /// Whether the player is offered insurance when the
    /// dealer shows a potential blackjack.
    /// </summary>
    public bool InsuranceOffered { get; set; }

    /// <summary>
    /// The cost to purchase insurance, as a fraction
    /// of the original bet.
    /// </summary>
    public decimal InsuranceCost { get; set; }

    /// <summary>
    /// The payoff for insurance, where 2 is 1:1 payoff.
    /// </summary>
    public decimal InsurancePayoff { get; set; }
        
    /// <summary>
    /// The maximum number of times a player can split
    /// in a hand.
    /// </summary>
    public int MaxSplitsAllowed { get; set; }

    /// <summary>
    /// Whether the player is allowed to resplit aces.
    /// Ex: Dealt AA, split to AA and Ax -- can the 
    /// first hand be resplit?
    /// </summary>
    public bool ResplitAcesAllowed { get; set; }

    /// <summary>
    /// Whether the player is allowed to split on 20.
    /// </summary>
    public bool SplitTensAllowed { get; set; }

    /// <summary>
    /// Whether the player is allowed to hit after
    /// splitting aces.
    /// </summary>
    public bool HittingSplitAcesAllowed { get; set; }
        
    /// <summary>
    /// Whether the player can double down only on ten or
    /// eleven. If false, the player can double down on
    /// any hand.
    /// </summary>
    public bool DoubleDownOnlyTenOrEleven { get; set; }

    /// <summary>
    /// Whether the player is allowed to double down on a
    /// soft hand (contains an ace).
    /// </summary>
    public bool SoftDoubleDownAllowed { get; set; }

    /// <summary>
    /// Whether the player is allowed to double down after
    /// splitting hands other than AA.
    /// </summary>
    public bool DoubleDownNonAceSplitsAllowed { get; set; }

    /// <summary>
    /// Whether the player is allowed to double down after
    /// splitting AA.
    /// </summary>
    public bool DoubleDownSplitAcesAllowed { get; set; }
        
    /// <summary>
    /// Whether the player is allowed to surrender their hand
    /// and receive a partial payoff back.
    /// </summary>
    public bool SurrenderAllowed { get; set; }

    /// <summary>
    /// The payoff for surrendering, as a fraction of the
    /// original bet. Typically this is 0.5.
    /// </summary>
    public decimal SurrenderPayoff { get; set; }
}

The defined game settings are passed to the constructor of the BlackjackGame engine class:

/// <summary>
/// A Blackjack game engine class. This class enables the user to simulate
/// a potentially-infinite number of blackjack hands played by a collection
/// of players.
/// </summary>
public class BlackjackGame
{
    /// <summary>
    /// The settings for this game.
    /// </summary>
    public BlackjackSettings Settings { get; set; }

    private const int SHUFFLES_PER_SHOE = 10;
    private Shoe _shoe;

    /// <summary>
    /// Creates a new instance of a game of Blackjack.
    /// </summary>
    public BlackjackGame(BlackjackSettings settings)
    {
        Settings = settings;
        _shoe = new Shoe(settings.DecksPerShoe);
    }

    /// <summary>
    /// Plays a game of Blackjack with the collection of players.
    /// </summary>
    public void Play(IEnumerable<IBlackjackPlayer> players)
    {
        ...
    }
}

Blackjack Strategies

Once you've constructed a game, you can then simulate a Blackjack session with any collection of player strategies. The NBF comes with a predefined set of strategies:

  • Basic Strategy - This is the standard strategy you find in Las Vegas gift shops on those little playing card-sized action tables. It's a losing strategy, but without card counting it's about as close to a fair bet as you're likely to get.
  • Wizard of Odds Simple Strategy - For most people, basic strategy is too complicated to memorize. The Wizard of Odds gives a simplified version of basic strategy that sacrifices a small percentage of expected value compared to basic strategy but is concise enough to memorize.
  • Simple Five Count Strategy - The more fives that have been played, the more likely it is the dealer will bust when it hits. This strategy plays basic strategy but counts the number of fives that have been played, increasing the bet size as more fives are dealt.

The three above strategies are just a jumping off point. It's simple enough to create your own strategy that does a more complex counting system, just implement the IBlackjackPlayer interface:

/// <summary>
/// An interface that all Blackjack players and strategies must implement.
/// </summary>
public interface IBlackjackPlayer
{
    /// <summary>
    /// The total amount that the player has won or lost in the current game.
    /// </summary>
    decimal Profit { get; set; }

    /// <summary>
    /// The number of splits this player has done in the current game.
    /// </summary>
    int Splits { get; set; }

    /// <summary>
    /// Returns true if the player will play the next hand.
    /// False means they're done and not sitting back down.
    /// </summary>
    bool PlayAnotherHand();

    /// <summary>
    /// Returns the amount the player is wagering on this hand.
    /// </summary>
    decimal GetBet(decimal min, decimal max);

    /// <summary>
    /// Returns true if the player wants to split the hand.
    /// </summary>
    bool Split(HandInfo info);

    /// <summary>
    /// Returns true if the player wants to double down on the hand.
    /// </summary>
    bool DoubleDown(HandInfo info);

    /// <summary>
    /// Returns true if the player wants to hit the hand.
    /// </summary>
    bool Hit(HandInfo info);

    /// <summary>
    /// Returns true if the player wants to buy insurance.
    /// </summary>
    bool BuyInsurance(HandInfo info);

    /// <summary>
    /// Returns true if the player wants to surrender this hand.
    /// </summary>
    bool Surrender(HandInfo info);

    /// <summary>
    /// Updates the player with the full info on the hand and how 
    /// much they won or lost on it. Counting strategies can update
    /// here.
    /// </summary>
    void HandOver(HandInfo info);

    /// <summary>
    /// Called when the dealer shuffles the deck
    /// </summary>
    void Reshuffle();
}

Running the Simulator

The ConsoleBlackjack project provides the skeleton needed to run the project:

class Program
{
    static void Main(string[] args)
    {
        BlackjackSettings settings = LoadSettingsFromFile("settings.xml");
        BlackjackGame game = new BlackjackGame(settings);

        var handsToPlay = 100000000L;
        var player = new SimpleFiveCountPlayer(handsToPlay);

        game.Play(new [] { player });
        Console.WriteLine("Profit: {0:N2}%", 
                player.Profit / settings.MinimumBet / (decimal)handsToPlay * 100m);
    }

    ...
}

Note that this project also provides a ConsoleBlackjackPlayer that enables you to play blackjack yourself via the command line. Just create a new instance of ConsoleBlackjackPlayer the same way you would any other strategy, and set the Game property to the Blackjack game instance.

Conclusion

The NashCoding Blackjack Framework is designed to facilitate statistics and AI research in the game of Blackjack. It should be able to simulate about 100,000 hands per second on a modern machine, and typically you need at least 100 million hands to get a stable expected value for a strategy. Although the game has been researched heavily, there are still a lot of questions to be answered particularly regarding the interaction of multiple strategies. If you find the framework useful or if you discover a bug, please let me know. :)

Source code for this post is available here.

Filed under: Blackjack, C# Leave a comment
Comments (4) Trackbacks (0)
  1. Are you modeling nash equilibrium, payoff matrices and replicator dynamics in your framework?

  2. In this framework, no. I was working on that kind of stuff using this framework to simulate the hands.

  3. Thanks for the code, works great. I added a high-low card counting strategy to it, information can be found here:
    http://financecoding.wordpress.com/2011/03/20/high-low-card-counting-strategy/

  4. Cool Adam! If you want to make a pull request, I’ll add your code to the main branch on github: https://github.com/tansey/blackjack


Leave a comment

 

Trackbacks are disabled.