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.
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.
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
.
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?
One choice is to have the
BlackjackGame
object make the offer to theHand
object. TheHand
instance can pass the offer to theBlackjackPlayer
object; theHand
includes a reference to itself.An alternative is to have the
BlackjackGame
object make the offer directly to theBlackjackPlayer
object, including a reference to the relevantHand
instance.
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 ofPlayer
that responds to the various queries and interactions with the game of Blackjack.
Fields¶
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 thenewGame()
to create an empty List fot the hands.
Methods¶
-
BlackjackPlayer.
placeBets
(self) → None¶ Creates an empty
Hand
and adds it to the List ofHand
instances.Creates a new ante Bet. Updates the
Table
with thisBet
on the initialHand
.
-
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 singleHand
.
-
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 InsuranceBet
and place it on theBlackjackTable
. 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 willCreate a new Ante bet for this hand.
Create a new one-card
Hand
from the givenhand
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 thisHand
. The Player must also update theBet
associated with thisHand
. 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 thisHand
. 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 theGame
are delegated to thePlayer
.
-
Hand.
splitDeclined
¶ Set to
True
if split was declined for a splittable hand. Also set toTrue
if the hand is not splittable. The split procedure will be done when all hands returnTrue
for split declined.
BlackjackGame Class¶
-
class
BlackjackGame
¶ BlackjackGame
is a subclass ofGame
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¶
Constructors¶
-
BlackjackGame.
__init__
(self, shoe: Shoe, table: BlackjackTable) → None¶ - Parameters
shoe (
Shoe
) – The dealer’s shoe, populated with the proper number of deckstable (
BlackjackTable
) – The table on which bets are placed
Constructs a new
BlackjackGame
, using a givenShoe
for dealingCard
instances and aBlackjackTable
for recordingBet
instances that are associated with specificHand
instances.
Methods¶
-
BlackjackGame.
cycle
(self) → None¶ A single game of Blackjack. This steps through the following sequence of operations.
Call
BlackjackPlayer.newGame()
to reset the player. CallBlackjackPlayer.getFirstHand()
to get the initial, emptyHand
. CallHand.add()
to deal two cards into the player’s initial hand.Reset the dealer’s hand and deal two cards.
Call
BlackjackGame.hand.getUpCard()
to get the dealer’s up card. If this card returnsTrue
for theCard.offerInsurance()
, then use theinsurance()
method.Only an instance fo the subclass
AceCard
will return true forofferInstance()
. All otherCard
classes will return false.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 theHand
’ssplit()
method.If the
split()
method returns a new hand, deal an additional Card to the original hand and the new split hand.Iterate through all
Hand
instances calling thefillHand()
method to check for blackjack, deal cards and check for a bust. This loop will finish with the hand either busted or standing pat.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.
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 givenHand
compared against the dealer’s bust.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.
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.
Call
BlackjackPlayer.insurance()
. If insurance declined, this method is done.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.
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.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 ofHand
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 ofCard
,FaceCard
andAceCard
, and add these to instances ofHand
, to create various point totals. Since this version ofHand
interacts with aBlackjackPlayer
, additional offers of split, double, and hit can be made to theHand
.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 aShoe
instance that produces cards in a known sequence, as well asBlackjackPlayer
. Thecycle()
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.