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

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

  




 

 

State

Objects have state changes. Often the processing that an object performs depends on the state. In non-object-oriented programming languages, this state-specific processing is accomplished with long, and sometimes complex series of if statements. The State design pattern gives us an alternative design.

As an example, the game of Craps has two states. A player's first dice roll is called a come out roll. Depending on the number rolled, the player immediately wins, immediately loses, or the game transitions to a point roll. The game stays in the point roll state until the player makes their point or crap out with a seven. The following table provides a complete picture of the state changes and the dice rolls that cause those changes.

Table 23.1. Craps Game States
State Roll Bet Resolution Next State
Point Off; the Come Out Roll; only Pass and Don't Pass bets allowed. 2, 3, 12 "Craps": Pass bets lose, Don't Pass bets win. Point Off
7, 11 "Winner": Pass bets win, Don't Pass bets lose. Point Off
4, 5, 6, 8, 9, 10 No Resolution Point On the number rolled, p .
Point On; any additional bets may be placed. 2, 3, 12 No Resolution Point still on
11 No Resolution Point still on
7 "Loser": all bets lose. The table is cleared. Point Off
Point, p "Winner": point is made, Pass bets win, Don't Pass bets lose. Point Off
Non- p number Nothing; Come bets are activated Point still on

The State design pattern is essential to almost all kinds of programs. The root cause of the hideous complexity that characterizes many programs is the failure to properly use the State design pattern.

The Craps Class. The overall game of craps can be represented in an object of class Craps. A Craps object would have a play1Round function to initialize the game in the come out roll state, roll dice, pay off bets, and possibly change states.

Following the State design pattern, we will delegate state-specific processing to an object that represents just attributes and behaviors unique to each state of the game. We pan to create a CrapsState class with two subclasses: CrapsStateComeOutRoll and CrapsStatePointRoll.

The overall Craps object will pass the dice roll to the CrapsState object for evaluation. The CrapsState object calls methods in the original Craps object to pay or collect when there is a win or loss. The CrapsState object can also return an object for the next state. Additionally, the CrapsState object will have to indicate then the game actually ends.

We'll look at the Craps object to see the context in which the various subclasses of CrapsState must operate.

Example 23.1. craps.py

import dice
class Craps( object ):
    """Simple game of craps."""
    def __init__( self ):
        self.state= None
        self.dice= dice.Dice()
        self.playing= False
    def play1Round( self ):
        """Play one round of craps until win or lose."""
        self.state= CrapsStateComeOutRoll()
        self.playing= True
        while self.playing:
           self.dice.roll()
           self.state= self.state.evaluate( self, self.dice )
    def win( self ):
        """Used by CrapsState when the roll was a winner."""
        print "winner"
        self.playing= False
    def lose( self ):
        """Used by CrapsState when the roll was a loser."""
        print "loser"
        self.playing= False
1

The Craps class constructor, __init__, creates three instance variables: state, dice and playing. The state variable will contain an instance of CrapsState, either a CrapsStateComeOutRoll or a CrapsStatePointRoll. The dice variable contains an instance of the class Dice, defined in the section called “Class Definition: the class Statement”. The playing variable is a simple switch that is True while we the game is playing and False when the game is over.

2

The play1Round method sets the state to CrapsStateComeOutRoll, and sets the playing variable to indicate that the game is in progress. The basic loop is to roll the dice and the evaluate the dice.

This method calls the state-specific evaluate function of the current CrapsState object. We give this method a reference to overall game, via the Craps object. That reference allows the CrapsState to call the win or lose method in the Craps object. The evaluate function of CrapsState is also given the Dice object, so it can get the number rolled from the dice. Some propositions (called “hardways”) require that both dice be equal; for this reason we pass the actual dice to evaluate, not just the total.

3 4

When the win or lose method is called, the game ends. These methods can be called by the the evaluate function of the current CrapsState. The playing variable is set to False so that the game's loop will end.

The CrapsState Class Hierarchy. Each subclass of CrapsState has a different version of the evaluate operation. Each version embodies one specific set of rules. This generally leads to a nice simplification of those rules; the rules can be stripped down to simple if statements that evaluate the dice in one state only. No additional if statements are required to determine what state the game is in.

class CrapsState( object ):
    """Superclass for states of a craps game."""
    def evaluate( self, crapsGame, dice ):
        raise NotImplementedError 
    def __str__( self ):
        return self.__doc__

The CrapsState superclass defines any features that are common to all the states. One common feature is the definition of the evaluate method. The body of the method is uniquely defined by each subclass. We provide a definition here as a formal place-holder for each subclass to override. In Java, we would declare the class and this function as abstract. Python lacks this formalism, but it is still good practice to include a placeholder.

Subclasses for Each State. The following two classes define the unique evaluation rules for the two game states. These are subclasses of CrapsState and inherit the common operations from the superclass.

class CrapsStateComeOutRoll ( CrapsState ):
    """Come out roll rules."""
    def evaluate( self, crapsGame, dice ):
        if dice.total() in [ 7, 11 ]:
            crapsGame.win()
            return self
        elif dice.total() in [ 2, 3, 12 ]:
            crapsGame.lose()
            return self
        return CrapsStatePointRoll( dice.total() )

The CrapsStateComeOutRoll provides an evaluate function that defines the come out roll rules. If the roll is an immediate win (7 or 11), it calls back to the Craps object to use the win method. If the roll is an immediate loss (2, 3 or 12), it calls back to the Craps object to use the lose method. In all cases, it returns an object which is the next state; this might be the same instance of CrapsStateComeOutRoll or a new instance of CrapsStatePointRoll.

class CrapsStatePointRoll ( CrapsState ):
    """Point roll rules."""
    def __init__( self, point ):
        self.point= point
    def evaluate( self, crapsGame, dice ):
        if dice.total() == 7:
            crapsGame.lose()
            return None
        if dice.total() == self.point:
            crapsGame.win()
            return None
        return self

The CrapsStatePointRoll provides an evaluate function that defines the point roll rules. If a seven was rolled, the game is a loss, and this method calls back to the Craps object to use the lose method, which end the game. If the point was rolled, the game is a winner, and this method calls back to the Craps object to use the win method. In all cases, it returns an object which is the next state. This might be the same instance of CrapsStatePointRoll or a new instance of CrapsStateComeOutRoll.

Extending the State Design. While the game of craps doesn't have any more states, we can see how additional states are added. First, a new state subclass is defined. Then, the main object class and the other states are updated to use the new state.

An additional feature of the state pattern is its ability to handle state-specific conditions as well as state-specific processing. Continuing the example of craps, the only bets allowed on the come out roll are pass and don't pass bets. All other bets are allowed on the point rolls.

We can implement this state-specific condition by adding a validBet method to the Craps class. This will return True if the bet is valid for the given game state. It will return False if the bet is not valid. Since this is a state-specific condition, the actual processing must be delegated to the CrapsState subclasses.


 
 
  Published under the terms of the Open Publication License Design by Interspire