PHP Game Making Part III: State Design Pattern

FenrirA Viking Tale

To wrap up this final post on the State Design Pattern, I thought we might make the game of R-P-S-L-S a little more interesting. So naturally I turned to the ancient Norse Gods and their stories. This one involves a giant wolf named Fenrir (pictured to the left at a PHP conference), and two gods, Tyr and Vidar. Tyr had his right hand eaten by Fenrir as he distracted the wolf with a handy snack while others tied up the wolf. Because of his bravery, Tyr became the god of war. (He also became a lefty—everyone loves a southpaw.) Later, Fenrir got loose from his bindings and went on a rampage. Eventually, he was killed by Vidar, who went into Fenrir’s giant jaws and stabbed him in the heart. As it turned out, the two Norse gods loved to play R-P-S-L-S; so naturally, they became the gods in this revised game.

In Part II of the PHP Game Making series, you saw how a state machine works. In this final part of the series, I’d like to see how we can make a two-player PHP game of R-P-S-L-S and play it over the Internet. First, though, take a look at the class diagram for the State design pattern in Figure 1:

Figure 1: State Design Pattern class diagram

Figure 1: State Design Pattern class diagram

The State Design Pattern is made up of a Context and a State Machine. In Figure 1, the state machine is everything to the right of the Context. As you saw in Part II, a state machine moves from state to state, and depending on the state, different outcomes occur. Statecharts show the states, their transitions and outcomes. The Context class keeps track of the current state. That’s it for the State Design Pattern. To get started, download the files, and set up the game (You’ll need a MySQL database for this version.)
Download

After you create the table with the CreateTable.php program, you must use the Initialize.php program to set it up for play.(Caution: Only use the Initialize program once!) Then after each game, you need to run the Reset.php program before you can play the next game.

The New UI

The new UI is like the old one, but both players must click on the Referee button to get the results. This allows two players to play remotely—one player can be in Brazil and the other in France, and they can play. Figure 2 shows the new UI:

Figure 2: The Tyr and Vidar User Interfaces

Figure 2: The Tyr and Vidar User Interfaces

As you can see, the UI is pretty similar to the original in that the player selects one of five moves from the available radio buttons. In this version, though, the moves are stored in a database table. Once both players have moved, a Referee class (the Client in this version) sends the moves to the Context and the State pattern works out which of the two players have won and stores the outcomes in a referee field in the table. The table only has a single row, and that row is updated as moves are made and the game is reset. Figure 3 is a class diagram of this revised game. (The actual participants in the State design pattern is outlined in dashed lines.)

Figure 3: Class diagram of state design pattern

Figure 3: Class diagram of state design pattern

In order to further clarify how this implementation of the State design pattern works, look at the following steps:

  1. Tyr or Vidar makes a move. The move is recorded in either the tyr or vidar field of a table. A counter keeps track of whether it is the first (0) or second (1) move. If it is the second move, no more moves are allowed.
  2. Once both players have moved, the Referee class (the client) is called by the players. It requests the State pattern to resolve the outcome.
  3. It first sends the Tyr move to the Context. The default state of the Context is Chant and it does nothing, but it sets the next state to whatever move Tyr makes.
  4. Then the Referee sends the Vidar move, and since the Context has been set to Tyr move, any return value resolves the game.
  5. The return value appears on the screen, resolving the game.

To see the difference between a State machine and a State design pattern, all you have to do is to look at the Context class. It is the key to the State pattern.

<?php
//Context.php
class Context
{
    private $outcome;
    //Beginning state
    private $chantState;
    //Moves
    private $rockState;
    private $spockState;
    private $paperState;
    private $lizardState;
    private $scissorState;
    //Current state
    private $currentState;
 
    public function __construct()
    {
        $this->rockState=new Rock($this);
        $this->spockState=new Spock($this);
        $this->paperState=new Paper($this);
        $this->lizardState=new Lizard($this);
        $this->scissorsState=new Scissors($this);
        $this->chantState=new Chant($this);
        //Beginning state is Chant
        $this->currentState=$this->chantState;
    }
 
    //Call State methods--triggers
    public function throwRock()
    {
        return $this->currentState->rockMove();
    }
    public function throwSpock()
    {
        return $this->currentState->spockMove();
    }
    public function throwPaper()
    {
        return $this->currentState->paperMove();
    }
    public function throwLizard()
    {
        return $this->currentState->lizardMove();
    }
    public function throwScissors()
    {
        return $this->currentState->scissorsMove();
    }
    public function throwChant()
    {
        return $this->currentState->chantMove();
    }
 
    //Set current state
    public function setState(IPlayer $state)
    {
        $this->currentState=$state;
    }
 
    //Get the states
    public function getRockState()
    {
        return $this->rockState;
    }
    public function getSpockState()
    {
        return $this->spockState;
    }
    public function getPaperState()
    {
        return $this->paperState;
    }
    public function getLizardState()
    {
        return $this->lizardState;
    }
    public function getScissorsState()
    {
        return $this->scissorsState;
    } 
}
?>

The Context class does the following:

  • Instantiates all of the concrete States and maintains an instance of each.
  • Keeps track of the current state.
  • Updates the current state when the state changes

This allows the State design pattern to keep up with situations of rapidly changing states, such as games. The five-move, two-player game R-P-S-L-S in this example is pretty simple, and it may look like using a canon to swat a fly, but the dynamics are the same. The bigger and more complex the moves in a game or dynamic situation simulation where states change with different responses, the State design pattern is idea.

Why No Conditional Statements?

If you look at the code, you’ll find plenty of conditional statements, but none are found within the part of the application that makes up the State design pattern. (See Figure 3 where the design pattern is enclosed in dashed lines.) As a general design pattern rule:

Avoid long conditional and case statements.

In order to maintain a program with long procedures or a lot of conditional statements you have a lot of work to do, and bugs creep up easily. Amateur programmers have what you might call “Fire and Forget” programs where they create something and have no expectation of maintaing it. With real live clients who have to deal with changing organizational and business situations, you have nothing but change. The State and Strategy design patterns have no conditional statements, and as a result are easy to maintain and update. Even though they do very different things, the class diagrams are identical. However, they do share the common feature of easy maintainability through lack of conditional and case statements. This is not to say that you cannot use conditional and case statements in conjunction with a State design pattern, but within the pattern itself, you have no need to.

Improvements

In Chapter 10 of Learning PHP Design Patterns, you’ll find a simple example of a two-state pattern (light on and light off) that uses all of the participants of a State design pattern, and you might want to take a quick peek at it for better understanding how these patterns work. Further, for a game this simple (albeit playable over the Internet), the State pattern may be more than it needs. However, it does stand as a legitimate instance of the pattern and how you may employ the pattern.

There are several tweaks you can add to better the game. If one player keeps sending more moves before the other player has moved, it will mess up the play, and after each play you have to use the “Reset.php” utility. You can probably smoothen things out in the game to make it easier to play. Once you have the game working on your server, let me know and we’ll test it online. (Just send a note through the “Comments” on this post.)

Share

Copyright © 2013 William Sanders. All Rights Reserved.

0 Responses to “PHP Game Making Part III: State Design Pattern”


  • No Comments

Leave a Reply