Blackjack Game Class

The game offers a large number of decisions to a player. This is different from Roulette, where the player’s choices are limited to a list of bets to place.

In Blackjack Game Analysis we’ll look at the game and how the state of play is going to be managed.

In Blackjack Collaboration we’ll look at the various player interactions. In Insurance Collaboration we’ll look at the insurance bet. In Filling the Hands we’ll look at the hit vs. stand decision. There are some additional decisions – like splitting – which require more interaction. We’ll look at this in Hand-Specific Decisions.

We’d like to segregate dealer rules from the rest of the game. This allows us to alter how a dealer fills their hand without breaking anything else. We’ll look at this in Dealer Rules.

This will require a stub class for a Blackjack Player. We’ll look at this design in BlackjackPlayer Class.

We’ll tweak the design for Card in order to determine if insurance should be offered. This covered in Card Rework.

We’ll also revisit the fundamental relationship between Game, Hand and Player. We’ll invert our viewpoint from the Player containing a number of Hands to the Hands sharing a common Player. This is the subject of Hand Rework.

We’ll provide the design for the game in BlackjackGame Class. In Blackjack Game Deliverables we’ll enumerate all of the deliverables.

Blackjack Game Analysis

The sequence of operations in the game of Blackjack is quite complex. We can describe the game in either of two modes: as a sequential procedure or as a series of state changes.

  • A sequential description means that the state is identified by the step that is next in the sequence.

  • The state change description involves a stream of events that change the sateof the game. We used this type of approach to describe Craps, see Craps Game Overview.

Additionally, we need to look at the various collaborations of the Game. We also need to address the question of handling the dealer’s rules.

Maintaining State. The sequential description of state, where the current state is defined by the step that is next, is the default description of state in many programming languages. While it seems obvious beyond repeating, it is important to note that each statement in a method changes the state of the application; either by changing state of the object that contains the method, or invoking methods of other, collaborating objects. In the case of an active class, this description of state as next statement is adequate. In the case of a passive class, this description of state doesn’t work out well because passive classes have their state changed by collaborating objects. For passive objects, instance variables and state objects are a useful way to track state changes.

In the case of Roulette, the cycle of betting and the game procedure were a simple sequence of actions. In the case of Craps, however, the game was only loosely tied to each the cycle of betting and throwing the dice. For Craps, the game state a passive part of the cycle of play. In the case of Blackjack, the cycle of betting and game procedure seem more like Roulette.

Most of a game of Blackjack is sequential in nature: the initial offers of even money, insurance and splitting the hands are optional steps that happen in a defined order. When filling the player’s Hand instances, there are some minor sub-states and state changes. When all of the player’s Hand instances are bust or standing pat, the dealer fills their hand. Finally, the hands are resolved with no more player intervention. There is a sequence to these steps that doesn’t seem to benefit from the :strong:State design pattern.

Most of the game appears to be a sequence of offers from the Game instance to the BlackjackPlayer instance; these are offers to place bets, or accept cards, or a combination of the two, for each of the player’s Hand instances.

Blackjack Collaboration

In Craps and Roulette, the Player object was the primary collaborator with the game. In Blackjack, however, focus shifts from the Player intance to the individual Hand objects. This changes the responsibilities of a BlackjackPlayer: the Hand object can delegate certain offers to the BlackjackPlayer instance for a response. The BlackjackPlayer object become a plug-in strategy to the Hand instances, providing responses to offers of insurance, even money, splitting, doubling-down, hitting and standing pat. The BlackjackPlayer object’s response will change the states of the the various Hand instances in play. Some state changes involve getting a card, and others involve placing a bet, and some involve a combination of the two.

Most of the time, there is a one-to-one relationship between the BlackjackPlayer instance and the Hand instance in play. This changes where there is a split and multiple Hand instances are shared by a single BlackjackPlayer instance.

We’ll use the procedure definition in A Walkthrough. Following this procedure, we see the following methods that a Hand object and a BlackjackPlayer object will need to respond to. These are the various offers from the BlackjackGame class. The first portion of the game involves the BlackjackPlayer object responding, the second portion invovles one or more Hand instances responding.

The collaboration is so intensive, it seems helpful to depict it in a swimlane table. This table shows the operations each object must perform. This will allow us to expand on the responsibiltiies of the Hand and BlackjackTable clases as well as define the interface for BlackjackPlayer class.

Blackjack Overall Collaboration

BlackjackGame class

Hand class

BlackjackPlayer class

BlackjackTable class

calls BlackjackPlayer placeBet

creates empty Hand, creates initial Ante Bet

accepts the ante bet, associates with the Hand

gets the initial Hand; deal 2 cards to Hand

add cards

returns the initial Hand

deal 2 cards to dealer’s Hand

add cards

gets up card from dealer’s Hand; if this card requires insurance, do the insurance procedure

return up card

iterate through all Hand instances; is the given Hand splittable?

return true if two cards of the same rank

returns a list iterator

if the Hand is splittable, offer a split bet

get the BlackjackPlayer response; return it to the game

to split: create a split bet, and an empty Hand; return the new Hand

accept the split bet for the new Hand

if splitting, move card and deal cards; continue looking for split offers

take a card out; add a card

iterate through all Hand; if the Hand is less than 21, do the fill-hand procedure

returns a list iterator

while the dealer’s point value is 16 or less, deal another card

return point value of the Hand; add a card

if the dealer busts, iterate through all Hand instances resolving the ante as a winner

return point value of the Hand

returns a list iterator

resolve bet as winner and remove

if the dealer does not bust, iterate through all Hand instances comparing against the dealer’s points, determining win, loss or push

return point value of the hand

returns a list iterator

resolve bet and remove

There are a few common variation in this procedure for play. We’ll set them aside for now, but will visit them in Variant Game Rules.

Insurance Collaboration

The insurance procedure involves additional interaction between Game and the the Player’s initial Hand. The following is done only if the dealer is showing an ace.

Blackjack Insurance Collaboration

BlackjackGame class

Hand class

BlackjackPlayer class

BlackjackTable class

if BlackjackPlayer Hand imstamce is blackjack: offer even money

return true if 2 cards, soft 21

to accept, return true

if BlackjackPlayer accepted even money offer: change bet, resolve; end of game

update bet; resolve and remove bet

offer insurance

to accept, create new bet; return true

accept insurance bet

if BlackjackPlayer accepted insurance offer: check dealer’s hand; if blackjack, insurance wins, ante loses, game over; otherwise insurance loses

return point value

resolve and remove bet

Filling the Hands

The procedure for filling each Hand involves additional interaction between Game and the the Player’s initial Hand. An Iterator used for perform the following procedure for each individual player Hand.

Blackjack Fill-Hand Collaboration

BlackjackGame class

Hand class

BlackjackPlayer class

BlackjackTable class

if BlackjackPlayer’s Hand instance is blackjack: resolve ante bet

return true if 2 cards, soft 21

resolve and remove bet

while points less than 21, offer play options of double or hit; rejecting both offers is a stand.

return point value; pass offers to player

to double, increase the bet for this hand and return true; to hit, return true

update bet

if over 21, the hand is a bust

return point value

resolve the ante as a loss

There is some variation in this procedure for filling Hand instances. The most common variation only allows a double-down when the Hand has two cards.

Hand-Specific Decisions

Some of the offers are directly to the BlackjackPlayer instance, while others require informing the BlackjackPlayer object which of the player’s Hand instances is being referenced.

How do we identify a specific hand?

While the difference is minor, it seems slightly more sensible for the BlackjackGame object to make offers directly to the BlackjackPlayer object, including a reference to the relevant Hand instance.

Dealer Rules

In a sense, the dealer can be viewed as a special player. They have a fixed set of rules for hitting and standing. They are not actually offered an insurance bet, nor can they split or double down.

However, the dealer does participate in the hand-filling phase of the game, deciding to hit or stand pat.

The dealer’s rules are quite simple. Should the Dealer be a special subclass of the BlackjackPlayer class; one that implements only the dealer’s rules?

Or, should the Dealer be a feature of the :class:BlackjackGame` class? In this case, the Game would maintain the dealer’s Hand and execute the card-filling algorithm.

Using an subclass of the BlackjackPlayer class is an example of Very Large Hammer design pattern. We only want a few features of the BlackjackPlayer class, why drive a small nail with a huge hammer?

Refactoring. To avoid over-engineering these classes, we could refactor the BlackjackPlayer class into two components. One component is an object that handles hand-filling, and the other component is an object that handles betting strategies.

The dealer would only use the hand-filling component of a player.

Mutability. We can look at features that are likely to change. The dealer hand-filling rules seem well-established throughout the industry.

Further, a change to the hand-filling rules of the dealer would change the nature of the game enough that we would be hard-pressed to call in Blackjack. A different hand-filling rule would constitute a new kind of game.

We’re confident, then, that the dealer’s hand can be a feature of the BlackjackGame class.

BlackjackPlayer Class

class BlackjackPlayer

The BlackjackPlayer class is a subclass of Player that responds to the various queries and interactions with the game of Blackjack.

Fields

BlackjackPlayer.hand

Some kind of List which contains the initial Hand and any split hands that may be created.

Constructors

BlackjackPlayer.__init__(self, table: Table) → None
Parameters

table (BlackjackTable) – The table on which bets are placed

Uses the superclass to construct a basic Player. Uses the newGame() to create an empty List fot the hands.

Methods

BlackjackPlayer.newGame(self) → None

Creates a new, empty list in which to keep Hand instances.

BlackjackPlayer.placeBets(self) → None

Creates an empty Hand and adds it to the List of Hand instances.

Creates a new ante Bet. Updates the Table with this Bet on the initial Hand.

BlackjackPlayer.getFirstHand(self) → None

Returns the initial Hand. This is used by the pre-split parts of the Blackjack game, where the player only has a single Hand.

BlackjackPlayer.__iter__(self) → Iterator[Hand]

Returns an iterator over the List of Hand instances this player is currently holding.

BlackjackPlayer.evenMoney(self, hand: Hand) → bool
Parameters

hand (Hand) – the hand which is offered even money

Returns True if this Player accepts the even money offer. The superclass always rejects this offer.

BlackjackPlayer.insurance(self, hand: Hand) → bool
Parameters

hand (Hand) – the hand which is offered insurance

Returns True if this Player accepts the insurance offer. In addition to returning true, the Player must also create the Insurance Bet and place it on the BlackjackTable. The superclass always rejects this offer.

BlackjackPlayer.split(self, hand: Hand) → Hand
Parameters

hand (Hand) – the hand which is offered an opportunity to split

If the hand has two cards of the same rank, it can be split. Different players will have different rules for determine if the hand should be split ot not.

If the player’s rules determine that it wil accepting the split offer for the given Hand, hand, then the player will

  1. Create a new Ante bet for this hand.

  2. Create a new one-card Hand from the given hand and return that new hand.

If the player’s rules determine that it will not accept the split offer, then None is returned.

If the hand is split, adding cards to each of the resulting hands is the responsibility of the Game. Each hand will be played out independently.

BlackjackPlayer.doubleDown(self, hand: Hand) → bool
Parameters

hand (Hand) – the hand which is offered an opportunity to double down

Returns True if this Player accepts the double offer for this Hand. The Player must also update the Bet associated with this Hand. This superclass always rejects this offer.

BlackjackPlayer.hit(self, hand) → bool
Parameters

hand (Hand) – the hand which is offered an opportunity to hit

Returns True if this Player accepts the hit offer for this Hand. The superclass accepts this offer if the hand is 16 or less, and rejects this offer if the hand is 17 more more. This mimics the dealer’s rules.

Failing to hit and failing to double down means the player is standing pat.

BlackjackPlayer.__str__(self) → str

Displays the current state of the player, and the various hands.

Card Rework

The Card class must provide the :class:BlackjackGame` class some information required to offer insurance bets.

We’ll need to add an offerInsurance() method on the Card class. The Card superclass must respond with False. This means that the FaceCard subclass can inherit this and also respond with False.

The AceCard subclass, however, must respond with True to this method.

Hand Rework

The Hand class should retain some additional hand-specific information. Since some games allow resplitting of split hands, it’s helpful to record whether or not a player has declined or accepted the offer of a split.

Fields

Hand.player

Holds a reference to the Player who owns this hand. Each of the various offers from the Game are delegated to the Player.

Hand.splitDeclined

Set to True if split was declined for a splittable hand. Also set to True if the hand is not splittable. The split procedure will be done when all hands return True for split declined.

Methods

Hand.splittable(self) → bool

Returns True if this hand has a size of two and both Card instances have the same rank. Also sets Hand.splitDeclined to True if the hand is not splittable.

Hand.getUpCard(self) → Card

Returns the first Card from the list of cards, the up card.

BlackjackGame Class

class BlackjackGame

BlackjackGame is a subclass of Game that manages the sequence of actions that define the game of Blackjack.

Note that a single cycle of play is one complete Blackjack game from the initial ante to the final resolution of all bets. Shuffling is implied before the first game and performed as needed.

Fields

BlackjackGame.shoe

This is the dealer’s Shoe with the available pool of cards.

BlackjackGame.dealer

This is the dealer’s Hand.

Constructors

BlackjackGame.__init__(self, shoe: Shoe, table: BlackjackTable) → None
Parameters
  • shoe (Shoe) – The dealer’s shoe, populated with the proper number of decks

  • table (BlackjackTable) – The table on which bets are placed

Constructs a new BlackjackGame, using a given Shoe for dealing Card instances and a BlackjackTable for recording Bet instances that are associated with specific Hand instances.

Methods

BlackjackGame.cycle(self) → None

A single game of Blackjack. This steps through the following sequence of operations.

  1. Call BlackjackPlayer.newGame() to reset the player. Call BlackjackPlayer.getFirstHand() to get the initial, empty Hand. Call Hand.add() to deal two cards into the player’s initial hand.

  2. Reset the dealer’s hand and deal two cards.

  3. Call BlackjackGame.hand.getUpCard() to get the dealer’s up card. If this card returns True for the Card.offerInsurance(), then use the insurance() method.

    Only an instance fo the subclass AceCard will return true for offerInstance(). All other Card classes will return false.

  4. Iterate through all Hand instances, assuring that no hand it splittable, or split has been declined for all hands. If a hand is splittable and split has not previously been declined, call the Hand’s split() method.

    If the split() method returns a new hand, deal an additional Card to the original hand and the new split hand.

  5. Iterate through all Hand instances calling the fillHand() method to check for blackjack, deal cards and check for a bust. This loop will finish with the hand either busted or standing pat.

  6. While the dealer’s hand value is 16 or less, deal another card. This loop will finish with the dealer either busted or standing pat.

  7. If the dealer’s hand value is bust, resolve all ante bets as winners. The OutcomeAnte should be able to do this evaluation for a given Hand compared against the dealer’s bust.

  8. Iterate through all hands with unresolved bets, and compare the hand total against the dealer’s total. The OutcomeAnte should be able to handle comparing the player’s hand and dealer’s total to determine the correct odds.

Note that there can be some variations to the steps in this cycle. A single, very long method body is a bad design. One of the key places where new functionality can be inserted is the final step determining of winning hands after the player and dealer’s hands have been filled and neither player has gone bust.

BlackjackGame.insurance(self) → None

Offers even money or insurance for a single game of blackjack. This steps through the following sequence of operations.

  1. Get the player’s BlackjackPlayer.getFirstHand(). Is it blackjack?

    If the player holds blackjack, then call BlackjackPlayer.evenMoney().

    If the even money offer is accepted, then move the ante bet to even money at 1:1. Resolve the bet as a winner. The bet will be removed, and the game will be over.

  2. Call BlackjackPlayer.insurance(). If insurance declined, this method is done.

  3. If insurance was accepted by the player, then check the dealer’s hand. Is it blackjack?

    If the dealer hold blackjack, the insurance bet is resolved as a winner, and the ante is a loser; the bets are removed and the game will be over.

    If the dealer does not have blackjack, the insurance bet is resolved as a loser, and the ante remains.

    If insurance was declined by the player, nothing is done.

BlackjackGame.fillHand(self, hand: Hand) → None
Parameters

hand (Hand) – the hand which is being filled

Fills one of the player’s hands in a single game of Blackjack. This steps through the following sequence of operations.

  1. While points are less than 21, call BlackjackPlayer.doubleDown() to offer doubling down. If accepted, deal one card, filling is done.

    If double down is declined, call BlackjackPlayer.hit() to offer a hit. If accepted, deal one card. If both double down and hit are declined, filling is done, the player is standing pat.

  2. If the points are over 21, the hand is bust, and is immediately resolved as a loser. The game is over.

BlackjackGame.__str__(self) → str

Displays the current state of the game, including the player, and the various hands.

Blackjack Game Deliverables

There are eight deliverables for this exercise.

  • The stub BlackjackPlayer class.

  • A class which performs a unit test of the BlackjackPlayer class. Since this player will mimic the dealer, hitting a 16 and standing on a 17, the unit test can provide a variety of Hand s and confirm which offers are accepted and rejected.

  • The revised Hand class.

  • A class which performs a unit tests of the Hand class. The unit test should create several instances of Card, FaceCard and AceCard, and add these to instances of Hand, to create various point totals. Since this version of Hand interacts with a BlackjackPlayer, additional offers of split, double, and hit can be made to the Hand.

  • The revised Card class.

  • Revised unit tests to exercise the Card.offerInsurance() method.

  • The revised BlackjackGame class.

  • A class which performs a unit tests of the BlackjackGame class. The unit test will have to create a Shoe instance that produces cards in a known sequence, as well as BlackjackPlayer. The cycle() method, as described in the design, is too complex for unit testing, and needs to be decomposed into a number of simpler procedures.

Looking Forward

We have all of the components in place to start looking at player strategies. The player has a number of decisions during the game, plus a betting strategy decision based on the outcome of each game. We’ll set the betting aside for a moment and focus on the Blackjack rules. In the next chapter, we’ll build a simple player that is able to work with the game and table.