A simple card counting strategy in Blackjack is to score +1 for
cards of rank 3 to 7, 0 for cards of rank 2, 8 and 9 and -1 for cards
10 to King and Ace. The updates to the Card
class hierarchy are shown in the text.
Create a subclass of CardFactory, which
replaces newCard with a version that creates the
correct subclass of Card, based on the
rank.
Six Reds
A common strategy in Roulette is to wait until six reds in a row
are spun and then start betting on only black. There are three player
betting states: waiting, counting and betting.
A full simulation will require a
RouletteGame class to spin the wheel and
resolve bets, a Wheel object to produce a
sequence of random spins, and a Table to hold
the individual bets. We'd also need a class to represent the
Bets. We'll skim over the full game and try to
focus on the player and player states.
A Player has a
stake which is their current pool of money. The
RouletteGame offers the
Player an opportunity to bet, and informs the
player of the resulting spin of the wheel. The
Player uses a
SixRedsState to count reds and bet on
black.
The various SixRedsState classes have two
methods, a bet method decides to bet or not bet,
and an outcome method that records the outcome of
the previous spin. Each class implements these methods differently,
because each class represents a different state of the player's
betting policy.
counting
In the counting state, the player is counting the number
of reds in a row. If a red was spun and the count is < 6, add
one to a red counter and stay in this state. If a red is spun
and the count is = 6, add one to a red counter and transition to
the betting state. If black or green is spun, reset the count to
zero.
betting
In the betting state, the player is betting on black. In a
more advanced version, the player would also increase their bet
for each red count over six. If a red was spun, add one to a red
counter and stay in the betting state. If black was spun,
transition to the counting state. If green was spun, transition
to the counting state.
Caution
We'll focus on the SixRedsState design.
We won't spend time on the actual betting or payouts. For now, we
can simply log wins and losses with a print
statement.
First, build a simple Player class, that
has the following methods.
__init__( self, stake )
Sets the player's initial stake. For now, we won't do much
with this. In other player strategies, however, this may
influence the betting.
More importantly, this will set the initial betting state
of Counting.
__str__( self )
Returns a string that includes the current stake and state
information for the player.
getBet( self )
This will use the current state to determine what bet (if
any) to place.
outcome( self, spin )
This will provide the color information to the current
state. It will also update the player's betting state with a
state object returned the current state. Generally, each state
will simple return a copy of itself. However, the counting state
object will return a betting state object when six reds have
been seen in a row.
Second, create a rudimentary RouletteGame
that looks something like the following.
__init__( self, player )
A RouletteGame is given a
Player instance when it is
constructed.
__str__( self )
It's not clear what we'd display. Maybe the player?
play1Round( self )
The play1Round method gets a bet from
the Player object, spins the wheel, and
reports the spin back to the Player
object. A more complete simulation would also resolve the bets,
and increase the player's stake with any winnings.
Note that calling the Player's
outcome method does two things. First,
it provides the spin to the player
playRounds( self, rounds=12 )
The playRounds is a simple loop that
calls self.play1Round as
many times as required.
State Class Hierarchy. The best approach is to get the essential features of
RouletteGame, Wheel
and Player to work. Rather than write a
complete version of the player's getBet and
outcome methods, we can use simple
place-holder methods that simply print out the status information.
Once we have these objects collaborating, then the three states can
be introduced.
The superclass, SixRedsState, as well as
the two subclasses, would all be similar to the following.
__init__( self, player )
The superclass initialization saves the player object.
Some subclasses will initialize a count to zero.
__str__( self )
The superclass __str__ method
should return a NotImplemented
value, to indicate that the superclass was used
improperly.
getBet( self )
The getBet either returns
None in the waiting and counting states, or
returns a bet on red in the betting state. The superclass can
return None, since that's a handy default
behavior.
outcome( self, spin )
The outcome method is given a tuple
with a number and a color. Based on the rules given above, each
subclass of SixRedsState will do slightly
different things. The superclass can return
NotImplemented.
We need to create two subclasses of
SixRedState:
SixRedCounting
In this state, the getBet method
returns None; this behavior is defined by the
superclass, so we don't need to implement this method. The
outcome method checks the spin. If it
is Red, this object increments the count by one. If it is black
it resets the count to zero. If the count is six, return an
instance of SixRedBetting. Otherwise,
return self as the next state.
SixRedBetting
In this state, the getBet method
returns a bet on Black; for now, this can be the string
"Black". The outcome
method checks the spin. If it is Red, this object increments the
count by one and returns self. If the spin is
black it returns an instance of
SixRedCounting. This will stop the
betting and start counting.
Once we have the various states designed, the
Player can then be revised to initialize itself
with an instance of the wating class, and then delegate the
getBet request from the game to the state object,
and delegate the outcome information from the
game to the state object.
Roulette Wheel Alternatives
There are several possible implementations of the basic Roulette
wheel. One variation simply uses random.randrange
to generate numbers in the range of 0 to 37, and treats 37 as double
zero. To separate double zero from zero, it's best to use character
string results.
Another strategy is to create a sequence of 38 strings ('00',
'0', '1', etc.) and use random.choice to pick a
number from the sequence.
Create a superclass for WheelStrategy and
two subclasses with these variant algorithms.
Create a class for Wheel which uses an
instance of WheelStrategy to get the basic
number. This class for Wheel should also
determine whether the number is red, black or green. The
Wheel class spin function
should return a tuple with the number and the color.
Create a simple test program to create an instance of
Wheel with an instance of
WheelStrategy. Collect 1000 spins and print the
frequency distribution.
Shuffling Alternatives
Shuffling rearranges a deck or shoe of multiple decks; there are
many possible algorithms. First, you will need a
Card class to keep basic rank and suit
information. Next, you will need a basic Deck
class to hold cards. See the section called “Playing Cards and Decks”
for additional details.
We looked at shuffling in an earlier exercise, but packaged it
as part of the Deck class, not as a separate
strategy. See the section called “Advanced Class Definition Exercises”. We can rework
those exercises to separate shuffing from
Deck.
The Deck class must have a
shuffle function; but this should simply call a
method of the shuffle strategy object. Because the
Deck is a collection of
Cards, the Deck object
should be passed to the shuffle strategy. The call would like
something like this:
class Deck( object ):
Other parts of Deck
def shuffle( self ):
self.shuffleStrategy.shuffle( self )
Create a superclass for shuffle strategies. Create a subclass
for each of the following algorithms:
For each card position in the deck, exchange it with a card
position selected randomly
For even-numbered card position (positions 0, 2, 4, 6, etc.)
exchange it with an odd-numbered card position, selected randomly
(random.choice from 1, 3, 5, 7, 9, etc.)
Swap two randomly-selected positions; do this 52
times
Create a simple test program that creates a
Deck with each of the available a
Shuffle strategy objects.
Shuffling Quality
An open issue in the shuffling exercise is the statistical
quality of the shuffling actually performed. Statistical tests of
random sequences are subtle, and more advanced than we can cover in
this set of exercises. What we want to test is that each card is
equally likely to land in each position of the deck.
We can create a dictionary, with the key of each card, and the
item associated with that key is a list of positions in which the card
occured. We can evaluate a shuffle algorithm as follows.
Procedure 23.1. Test A Shuffle
Setup
Create a Deck.
Create an empty dictionary, positions for
recording card positions.
For each Card in the
Deck, insert the
Card in the positions
dictionary; the value associated with the
Card is a unique empty
list used to record the positions at which
this Card is found.
For Each Strategy. Perform the following evaluation for an instance of each
Shuffle class,
s.
Create Deck. Set the Deck's current shuffle
strategy to s.
Shuffle. Shuffle the Deck.
Record Positions. For each card in the deck.
Record Position. Locate the Card's position
list in the positions dictionary;
append the position of this Card
to the list in the positions
dictionary.
Chi-Squared. The chi-squared statistical test can be used to
compare the actual frequency histogram to the expected
frequency histogram. If you shuffle each deck 520 times, a
given card should appear in each of the positions
approximately 10 times. Ideally, the distribution is close
to flat, but not exactly.
The chi-squared test compares sequence of actual frequencies,
a, and a sequence of expected frequencies,
e. It returns the chi-squared metric for the
comparison of these two sequences. Both sequences must be the same
length and represent frequencies in the same order.
Equation 23.1. Chi-Squared
We can use the built-in zip function to
interleave the two lists, creating a sequence of tuples of (
actual, expected
). This sequence of tuples can be used with the
multiple-assignment for loop to assign a value from
actual to one variable, and a value from expected to another variable.
This allows a simple, elegant for statement to
drive the basic comparison function.
Published under the terms of the Open Publication License