Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Design Pattern Exercises

Alternate Counting Strategy

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.

For guidance on designing the Wheel used by the RouletteGame, see the section called “Class Variables” and the section called “Function Definition: The def and return Statements”.

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

  1. 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.

  2. For Each Strategy. Perform the following evaluation for an instance of each Shuffle class, s.

    1. Create Deck. Set the Deck's current shuffle strategy to s.

    2. Shuffle. Shuffle the Deck.

    3. Record Positions. For each card in the deck.

      1. 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.

    4. 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

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 Design by Interspire