State Maze Part 2: Play

maze2PHP Game Mechanics

In Part I of this “State Maze” series, you see that each cell in the matrix is a coordinate on a grid, and using the alphanumeric coordinate designation, each implementation of a state interface (class) is named with a grid coordinate. (If you have not looked at Part I, do so now.)

The problem with using an HTML UI (See Part I) is that each time the player makes a move, it generates a new instance of the client that makes the move in the State pattern. As a result, I had to create a Json file to store each move. This solution still does not allow the same instance to be re-used and keep a running record of where the player is, but I haven’t found a satisfactory solution elsewhere. (I’m looking at Ajax and RESTful APIs, but nothing yet.) If you’ve developed games with ActionScript (of Flash fame) or Python, you can easily keep a running record in a class property without re-instantiaing the class in a variable. Ironically, by placing the HTML code in a PHP heredoc string, the class with the HTML in it does not have to be re-instantiated, but the client it launches does. To get started, go ahead and play the maze-game and explore the different OOP and Design Pattern principles and languages that use OOP. You will be asked to provide a “seeker” name. The default name is “chump.” Don’t use that name! (Don’t be a chump…) Use a 5-letter name of your own. It will be used to track your progress through the maze.

PlayDownload

This is not an easy maze (nor does it follow the route of the maze in Part I.) So, keep track of your moves, and if you fall into a sequential trap, you have to start over.

State Overview

If you review the State design pattern, especially the class diagram in Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson and Vlissides (AKA “The Gang of Four” or GoF) you will see that the Sate pattern consists of Context, State Interface and Concrete States implementing the State Interface. In other words, it’s one of the least complex-looking patterns among design patterns.

Figure 1 shows a file diagram of the current implementation; however, the additional files beyond the basic pattern implementation are files with helper elements for CSS and Json.

Figure 1: File Diagram

Figure 1: File Diagram

With a maze, the State design does require a lot of files — one for each state, and some would prefer a table look-up for dealing with a maze-type application. However, a table look-up has its own issues, and making changes and adding actions can tie a table in a knot. Besides, it’s much easier to re-use a state pattern by changing the method calls within each state without even having to change the context or client at all. Further, since all of the states implement the same interface, once one implementation is completed, it can be copied and pasted, changing only the name of the class and the behavior of the implemented methods defined by the interface. As can be seen in Figure 2, the State pattern used in this implementation adheres to the fundamentals of the State Design Pattern as proposed by GoF.

Figure 2: State Class Diagram

Figure 2: State Class Diagram

Each of the state implementations are designated A1State to E4State. (See the labeled grid in Figure 2 in Part I). Of course, while the State design pattern diagram is relatively simple, the Context can be challenging, especially when using a Json file for recording moves. However, to get started with the code, we’ll start at the beginning with the UI and the Client that makes requests to the State pattern.

The UI and Client

The UI is an HTML5 document embedded in a PHP class and is more of an HTML document than a PHP one. A heredoc string (EXPLORE) is placed in a PHP private variable, $explorerUI. An echo statement displays the HTML on the screen when the $worker variable instantiates the PHP class.

<?php
class ExplorerUI
{
    private $explorerUI;
    public function __construct()
    {
        //Use the Security object to encode table
        $this->explorerUI=<<<EXPLORE
        <!DOCTYPE html>
        <html>
        <head>
            <link rel="stylesheet" type="text/css" href="explorer.css">
            <meta charset="UTF-8">
            <title>OOP Cavern</title>
        </head>
 
        <body>
            <h2>OOP Explorer</h2>
        <h3>Explore Next Direction</h3>
        <fieldset>
        <legend>Move Options</legend>
        <form action="ExplorerClient.php" method="post" target="cavestate">
        <table>
            <tr><td></td><td><input type="radio" name="move" value="northMove">&nbsp;Move North</td><td></td></tr>
            <tr><td><input type="radio" name="move" value="westMove" checked="checked">&nbsp;Move West</td><td></td><td><td><input type="radio" name="move" value="eastMove">&nbsp;Move East</td></tr>
            <tr><td></td><td><input type="radio" name="move" value="southMove">&nbsp;Move South</td><td></td></tr>
        </table>
        </fieldset><p />
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type = "text" name="seeker" maxlength="5" size="6" value ="chump">&nbsp Your seeker name: Five characters; no spaces<p />
        <input type="submit" class="submit" name ="makemove" value ="Make your move">
        </form>
        <p />
        <iframe seamless name="cavestate" width="500" height="450">CaveState</iframe>
        </body>
        </html>
EXPLORE;
        echo $this->explorerUI;
    }
}
$worker=new ExplorerUI();
?>

I used a table for setting up the UI “move center” to make it easy for the player to select the next move. (A CSS form for the move center certainly would be more elegant, but the table worked ok; so I used it after testing it on a desktop, tablet and smartphone.) You can see how the UI looks in Figure 1 in Part I of the State maze).

The client class (ExplorerClient) has a few different features compared to other client classes on this blog. First of all, I turned off the error checking. The reason I did this is admittedly a hack–it throws an error when you first create a new “seeker name,” but after the initial error, it’s happy and does not throw more Json-related errors. Second, I “registered” the fact that autoload is on. Sometime when rusing another class, it will also have autoload and result in an error. By using the spell_autoload_register() function, it accepts the added outlaid without a grumble.

<?php
//Remove comment lines to turn on error checking
//error_reporting(E_ALL | E_STRICT);
//ini_set("display_errors", 1);
 
//Errors = off (comment them out if you want error checking back on.)
error_reporting(0);
ini_set("display_errors", 0);
 
// Autoload given function name.
function includeAll($className)
{
    include_once($className . '.php');
}
//Register
spl_autoload_register('includeAll');
class ExplorerClient
{
    private static $stateMove;
    private static $stateContext;
    //client request
    public static function request()
    {
      //Passes method name from UI & creates new context instance
      self::$stateContext=new StateContext();
      self::$stateMove=$_POST['move'];
      self::stateMachine();
    }
 
    private static function stateMachine()
    {
        //$stateMove = context method
        echo self::$stateContext->{self::$stateMove}();
    }
}
ExplorerClient::request();
?>

The UI passes a single super global ($_POST[‘move’]). Each move generates one of the following strings:

  • northMove
  • eastMove
  • southMove
  • westMove

Passed to a private static variable, $stateMove, the value calls the appropriate method in the StateContext class. Remember, in PHP, if you want to turn a string into a method you use the format, { $string }(). (Note that the double parentheses go outside the curly braces.) It saves tons of time in coding and gets the job done with little muss or fuss.

The Context Takes Over

The most important (and least understood) participant in the State pattern is the Context.

Essentially, the Context keeps track of the current state.

As the state changes from one state to another, the change depends on the current state and the next state. If it helps to think in terms of immutable states (see this post) you can envision the difference in going from the U.S. south to Mexico or north to Canada. If the U.S. is the current state (in an immutable sense), you can cross directly into Canada or Mexico, but you’d have to cross several states to cross into Brazil. So, if my current state is the U.S., a move north takes me to Canada and a move south to Mexico. If I were in Brazil, a move north takes me to Venezuela and a move south to Uruguay. So, not only is it important to know your direction, you must know your current state. That’s what the Context does.

<?php
class StateContext
{
    private $cave;
    private $storedState,$currentState,$seeker;
 
    public function __construct()
    {
	$this->seeker = $_POST['seeker'];
	$rawContents = file_get_contents("$this->seeker.json");
	if ($rawContents=="")
	{
		$rawContents='{"store":["E3State"]}';    
	}
	$jsonState = json_decode($rawContents,true);
	$this->storedState= $jsonState["store"][0];
        $this->setState($this->storedState);
    }
    //Call State methods
    public function northMove()
    {
        return $this->currentState->moveNorth();
    }
    public function eastMove()
    {
        return $this->currentState->moveEast();
    }
    public function southMove()
    {
        return $this->currentState->moveSouth();
    }
    public function westMove()
    {
        return $this->currentState->moveWest();
    }
 
    //Set current State
    public function setState($state)
    {
        $this->seeker = $_POST['seeker'];  
	//Json Array Element with stored value $jsonState
        $jsonState["store"][0] = $state;
        //Json File name $stash
	$stash=$this->seeker . ".json";
        //Put in contents
	file_put_contents($stash,json_encode($jsonState,JSON_UNESCAPED_UNICODE));
        $this->getState($state);
    }
 
    //Get state
    public function getState($state)
    {
        $this->currentState=new $state($this);
    }
}
?>

In working through the context, the first action is taken when a class instantiates: the current (before the move) state stored in a Json file is passed to a private variable, $storedState. It is then used as a parameter in the setState() method.

In the setState($state) method the $storedState (passed as $state) is assigned to be stored in $jasonState as a value. In turn, $jsonState is placed in the appropriate user-name file. (e.g., if the seeker value is “flash”, the file is named “flash.son”.) Using the same $state value, the getState() method is called which creates a new instance of the desired concrete state class with the current context as a parameter (i.e., $this).

This is a good example of an aggregate relationship between the context and state classes. A state class needs a context class for its instantiation. In the class diagram (Figure 2) the aggregation is between the StateContext class and IOopState interface, which is used to implement the concrete states.

The State Interface and Concrete State Implementations

Finally, we come to the IOopState interface and the 25 concrete state implementations. The interface has four abstract methods and two constants. (In PHP, interfaces are allowed to have constants.) As is usual for an interface, the code is minimal and pretty simple:

<?php
//State Interface
interface IOopState
{
    const STYLE ="<p style='color:#fff; font-size: 20px; font-family:fantasy;'>";
    const IMGSTYLE ="<div style='margin-left: 125px;'>";
 
    function moveNorth();
    function moveEast();
    function moveSouth();
    function moveWest();   
}
?>

The constants contain a little inline CSS for adding some formatting to the returned messages and images. The methods return the information to the ExplorerUI class where they are processed and displayed in the iframe. So by now, it should be pretty clear what the methods must do. Since all of the states contain implement the state interface, each must implement each of the four methods. Depending on where the class is in the matrix, different conditions apply. For example, looking at the B5State class, you can see that depending of the direction selected by the user, the move from that class will have four different results.

<?php
class B5State implements IOopState
{
    private $stateContext;
    private $imageStyle=IOopState::IMGSTYLE;
    private $style=IOopState::STYLE;
 
    public function __construct(StateContext $contextNow)
    {
        $this->stateContext=$contextNow;
    }
    function moveNorth()
    {
        $this->stateContext->setState("E3State");
        $msg="You have fallen into a Sequential Trap! Start Over!</p>";
        return $this->style . $msg;
    }
    function moveEast()
    {
        $msg= "Off the grid!</p>";
        return $this->style . $msg;
    }
    function moveSouth()
    {
        $this->stateContext->setState("C5State");
        $msg="<strong>Python -- OOP Learning Language</strong></p>";
        $img="<img src='images/python.jpg' width='250' height='378'></img></div>";
        return $this->style . $msg . $this->imageStyle . $img;
    }
    function moveWest()
    {
        $this->stateContext->setState("B4State");
        $msg="<strong>Swift -- Program your iPhone!</strong></p>";
        $img="<img src='images/swift.jpg' width='250' height='378'></img></div>";
        return $this->style . $msg . $this->imageStyle . $img;
    }
}
?>

Figure 3: Possible moves from B5

Figure 3: Possible moves from B5

In order to best understand what the code in the B5State methods mean, look at Figure 3. The player cannot move to the east because it is off the grid; so it can only move north, south or west. By looking at the content of the different methods, you can see what will happen with each move. A move to the north the player enters a sequential trap and sends the player back to the beginning (E3State). A west move takes the user to B4State and the OOP language, Swift. A move south, takes the player to C5State and the OOP language Python. Notice that the first line in every method sets the context object ($stateContext) to the state the player is moving into. All of the 25 states have the same movement options, but depending on where they are, different things happen with different moves. By having all of the possible outcomes in the four methods, you can make any kind of maze you want with an easy way to keep track of each possibility and to change them by changing the code. As a result, this 5 x 5 maze could be re-used with wholly different outcomes.

Look Ma! No Conditional Statements!

One of my favorite features of the State design pattern is that it requires no conditional statements. (The single conditional in the context class is due to the need for establishing a Json file in cases where there is none for a given player and is not part of the State pattern proper.) Otherwise, in navigating the maze, you do not need conditionals. A selection of a move from the UI is passed to the context class via a client and that move is made relative to the current state. As a result, even large mazes or other programs using the State pattern are easy to work with cooperatively with other developers without getting tangled up. If a change is made, as long as the developer adheres to the interface, the program won’t crash and burn.

Share

Copyright © 2015 William Sanders. All Rights Reserved.

0 Responses to “State Maze Part 2: Play”


  • No Comments

Leave a Reply