Chapter 13. Symmetric Proxy Pattern

Now an allegory is but a translation of abstract notions into a picture-language, which is itself nothing but an abstraction from objects of the senses; the principal being more worthless even than its phantom proxy, both alike unsubstantial, and the former shapeless to boot. On the other hand, a symbol is characterized by a translucence of the special in the individual, or of the general in the special, or of the universal in the general; above all though the translucence of the eternal through and in the temporal. It always partakes of the reality which it renders intelligible; and while it enunciates the whole, abides itself as a living part in that unity of which it is the representative.

Samuel Taylor Coleridge

There is no country in the world where machinery is so lovely as in America. It was not until I had seen the water-works at Chicago that I realised the wonders of machinery; the rise and fall of the steel rods, the Symmetric motion of the great wheels is the most beautiful rhythmic thing I have ever seen.

Oscar Wilde

Computers that are commercially available are symmetric or non-handed but it is possible that some existing software and algorithms are left- or right-handed.

Philip Emeagwali

Simultaneous Game Moves and Outcomes

One of the more interesting problems for programmers is dealing with interaction over the Internet. This is especially true in the case of games where the developer must work out how two or more players can interact in the context of a set of rules that describe the game. This process gets more challenging when the players are making simultaneous (parallel) moves, and both players won’t know the outcome of a turn until both have completed their moves.

One such game is Rock, Paper, Scissors (RPS). In this game, two players simultaneously throw hand signals for a rock (fist), paper (a flat hand) or scissors (a horizontal V-sign with the index and middle fingers). Rock defeats scissors, scissors defeat paper, and paper defeats rock. (See http://www.worldrps.com for details). With such clear, simple and universal rules, the game is an ideal way to discover design patterns that accommodate the role of parallel Internet interaction.

As a point of reference, the concept of the Symmetric Proxy design pattern grew out of a paper, a Pattern for Distributing Turn-Based Games, by James Heliotis and Axel Schreiner, both of Rochester Institute of Technology, and a presentation based on that paper at the 2006 OOPSLA conference in Portland, Oregon by Axel Schreiner.

Note

Just about any background work in design patterns will sooner or later lead you to OOPSLA. The acronym stands for Object-Oriented Programming, Systems, Languages & Applications. It’s an annual conference that focuses on different OOP topics, mixing in both academic and practical materials. In 1991, the first part of the Design Pattern catalog was presented at an OOPSLA conference, and ever since, design patterns have played a role at OOPSLA conferences. The European sister conference is ECOOP (European Conference on Object-Oriented Programming).

What we developed for this book is based on both the paper and conference presentation, but the example for this book took on a life of its own, and we in no way hold Drs. Heliotis or Schreiner responsible for what has been developed here. In part, this is because we’re using ActionScript 3.0 and Flash Media Server 2, and in part because we deviated from the specifics. Nevertheless, we’ve strived to keep the original concepts and reasoning intact. They deserve credit for what we did right, but we take responsibility for anything gone wrong.

The Player

At the very base of the idea of a symmetric proxy is the concept of a player interface containing methods for playing a game, any game, not just RPS, that can be conducted over the Internet. Our list is slightly different from that presented by Heliotis and Schreiner, but the core concepts are borrowed from their work.

  • Make a move (makeMove). Each player needs a method for translating user input through a user interface (UI).

  • Indicate that a local player has made a move (localMove). Once a move has been made, a game requires that the state (local player has moved) be recorded. It also prevents the same user from making another move until the next turn.

  • Indicate that a remote player has made a move (onProxyMove). Records the move of the remote player, and prevents a further move by the remote player until the next turn.

  • Send the local player move to the remote player (doMove). The move will be picked up by the opponent’s onProxyMove method.

  • Conclude the turn after both players have moved (takeTurn). Once both players have moved, the moves are turned over to the Referee to determine who’s won and reset the values for the next turn.

  • Prevents play until both players are connected (numConnect). This method has the dual function of letting players know of each other’s presence, and prevents a move if only one player’s present.

By first working out the basics of a move and a turn, we’ve laid the basis for turn-based games. The next step is putting the moves into a game context.

The Referee

In order to keep everything fair and fun, we need a referee. The class diagram for the referee in relationship to the player can be seen in Figure 13-1:

Referee and Player class diagram
Figure 13-1. Referee and Player class diagram

Essentially, the referee will be a class containing the rules of the game encased in a template method. As a design pattern, the methods need to be fairly general, in case we want to reuse the same Referee class for another game. Using a template method, we can order the operations in the methods to launch in sequence, but we want flexibility for other games. So, we begin with a list of operations for the referee that applies to all games:

  • Evaluate the moves and determine who won. All games need a set of winning conditions and the ability to determine which player won, which lost or whether they tied.

  • Display the results of the game. Once the results have been calculated, they must be communicated to all players.

  • Reset the game variables to the beginning conditions. Because the players have been keeping track of who has moved, which moves were selected, and which player was declared the winner, all the variables keeping track of these different states have to be cleared.

Each operation will be cast as a method and then arranged into an order that constitutes the template method. Following the Hollywood Principle (see Chapter 9), the Referee instance will make calls on the Player objects.

Now that we have a referee, we need to look at the relationship between the players and the referee. Figure 13-2 is an object diagram for a two-player game with a referee:

Referee and players
Figure 13-2. Referee and players

Just like a non-virtual game, the single referee makes decisions about the game outcome for two or more players. At this point, the game is immaterial—it could be any game. Looking at the object diagram, it’s not difficult to imagine two or more players interacting on a single host (computer). In fact, if the goal were to create a game where one of the players is the computer and the other’s a user interacting through a UI, our job would be done. In a game of Rock, Paper, Scissors, a random move generator could easily be one of the two players. However, because we want to play the game over the Internet, we have a few more steps.

The Internet, Proxies, and Players

As soon as we introduce remote players over the Internet, everything changes. Each player makes a move, that move is sent over the Internet, and the referee decides the outcome, displays it, and then cleans everything up. With serial turn taking, the problem may not be as daunting because after each move, both players can see the other’s move. The referee can wait until the first win condition is met and then send messages to all players. However, this can get messy because you have to decide where the referee’s going to reside. The referee could be placed on the host with the first player to start. The second player would be referenced through a proxy.

Another solution would be to place the referee on its own server, and, while workable, this requires that the moves travel over the Internet twice. Also, depending on the server, the basic game design may have to be changed to accommodate what the referee looks like.

Even though both of the solutions are workable, they may lack the flexibility and reusability desired in good design patterns. We need to look further.

The Symmetric Proxy Pattern

The Symmetric Proxy pattern is as much a discovery as an invention. Heliotis and Schreiner found that they could establish instances of two player objects arranged as peers over the Internet. Each side has both a proxy and a “real” player. Likewise, the referees on each side have the same information. Whatever move a player makes is treated the same by each of the referees. The referee object has no idea where a move comes from—it’s clueless, as Heliotis and Schreiner note. All that the referee has to do is to call the players when the game is over and let them know the outcome. The referees are perfectly synchronized because they’re reacting to the same state information, without caring whether the information is local or from a proxy; as a result, when the game-over conditions are met, both inform the players in exactly the same way, resetting all variables, and prepare for the next game.

Figure 13-3 shows the object diagram of the Symmetric Proxy Pattern:

Symmetric Proxy object diagram
Figure 13-3. Symmetric Proxy object diagram

Keeping in mind that Figure 13-3 is an object diagram and not a class diagram, we can clearly see that the Symmetric Proxy pattern has each player’s proxy in the opposition’s camp, so to speak. The referees are treating each side of the Symmetric Proxy as a complete game. The referee simply takes the moves, evaluates them as being from one side or the other, and makes game decisions as though only a single host is in use. In other words, the proxies are treated as the computer playing the live player, or two live players taking turns on the same keyboard.

Key Features

Most of the key features of the Symmetric Proxy pattern have been described, but there are a few more. In this section we’ll summarize both.

  • Player interface that includes move-taking, move display, and outcome methods

  • A referee class based on a Template Method for determining game outcome

  • Synchronizing cell between the UI and player

  • Mirrored referee, proxy and player objects

Having discussed the player interface and referee concepts, we are left with two key elements to discuss before going on to look at key OOP concepts in this design pattern. First, we need to take a closer look at the synchronizing cell, and where the cell goes in the overall scheme of things. Second, we need to look more closely at the concept of a proxy object.

The cell

Heliotis and Schreiner refer to the cell in their design as a monitored single-element queue placed between the player object and the graphical elements. In most cases, the graphical elements would be the UI. In looking at the sequence diagram for parallel turn taking, the cells are placed between the proxy players, one for each player. For simultaneous turns, two cells are used; one for each player making a move at the same time.

Looking at the code used by Heliotis and Schreiner, an alternative implementation came to mind in the form of a remote shared object available through Flash Media Server 2 (FMS2). The shared object could act as the synchronizing mechanism, ensuring that both sides had the same information at the same time. As soon as either player entered a move, it would immediately be available to the proxy object as a move. The “cell” itself would be in the player in a function waiting for both sides to move. In effect, the synchronizing role of the cell has been taken over by the shared object. Setting the move in a shared object would fall to a FMS2 server-side script.

The proxy

Because using FMS2 takes care of the synchronizing problems and is immediately available to connected clients through a client-side method, we end up with a situation where we have to ask whether we really need a separate object for the proxies. Instead of creating four player objects, would it be possible to create two? Each player object, based on a common interface, would contain both the real player and the proxy player. This is an androgynous variation of the Symmetric Proxy design pattern, but conceptually, it’s virtually identical. Figure 13-4 shows an object diagram of this variation.

Androgynous variation of Symmetric Proxy
Figure 13-4. Androgynous variation of Symmetric Proxy

The only real difference between Figure 13-4 and Figure 13-3 is that the proxy and players are placed into single objects and the details of the Internet are spelled out a bit more in the latter. The same model could be used another way to send the proxy’s move such as PHP, ColdFusion, or any other mechanism for sending information over the Internet. (Axel Schreiner commented that you could use smoke signals—the method of sending information over the network is immaterial.)

The one thing we don’t particularly like about the androgynous variation on the Symmetric Proxy is the loss of granularity in the model. By placing the proxy move method in the same object as the real player, we collapse the real and proxy in a way that may not be easily adaptable to other games. At this point, though, no clear disadvantage is apparent. Both players are perfectly symmetrical, and the variation maintains the role of the referee.

Key OOP Concepts Used with the Symmetric Proxy

For a compound design, all the OOP concepts we have discussed for the different design patterns collected in a single compound design apply. So, to avoid repeating the same key concepts discussed in the chapters where the compounding elements were reviewed, we’d like to focus on a more general key OOP concept for the Symmetric Proxy design pattern—flexibility.

Because the Symmetric Proxy design focuses on interaction over the Internet, we need to look at two different kinds of flexibility. First, we need to look at communication flexibility. That is, can the model be used with different communication technologies over the Internet? Second, we must consider game flexibility. To what extent can different games be employed with the design?

Communication Flexibility

The first question to address is whether or not any trans-Internet communication can work with the Symmetric Proxy pattern. Because of its ease of use and built-in features like remote shared objects, using an application such as Flash Media Server 2 has a built-in disadvantage as a communication testing and development platform for a general communication design pattern. The server-side script automatically informs all players of the current game state in the form of one or both players having moved. Once both players have moved, the referee determines who has won and resets the game for another round.

In the original model of the Symmetric Proxy and the Androgynous variation, as long as the proxy player makes the same move as the original player, whether FMS, another open socket technology, or middleware such as PHP, C#, Perl, ColdFusion, or VB.NET, is not important. The move for the proxy is the only state that really needs to be sent over the Internet. The proxy plays the move, and the referee takes care of deciding whether the proxy or its player opponent wins, displaying the outcome and resetting the variables for a new game. So any communication system that can send the player’s state to its proxy is acceptable, meeting the criteria of communication flexibility.

Game Flexibility

In the implementation you will see in this chapter, the referee’s not quite as active as the one envisioned by Heliotis and Schreiner. The primary difference is that the referee only inspects the moves after both moves have been made in a game. Because each round in a game of RPS is a complete game, each game is over as soon as both players have moved. At this point, the referee kicks in with its template method, decides who won, displays the outcome, and resets the values to the start states.

One way of reducing the referee’s work is in the UI. Players can only enter one of three different moves displayed on the buttons. The real player blocks any attempt to make any other move, such as one where the player has not made a move selection. In a more complex game with a wider selection of moves, either more responsibilities would be delegated to the referee or the UI would take care of allowing a wider but still limited move set.

With a single template method, and three methods that make up the template method within an abstract class, the Referee class has both flexibility and utility. In the example application in this chapter, the implementation of the Referee class subclasses RPS. The RPS class provides the specific details for the RPS game. The measure of flexibility is whether a different game could be subclassed from the Referee class to create a whole different game.

Of the three functions that make up the template method, flexibility is most important in the function that determines who won or if the play results in a tie. The following shows that function in the Referee class:

function doWinner(p1Move:String,p2Move:String):String
{
    return winner;
}

Because the method resides in an abstract class, we can expect it to be overridden for different games. It takes the move of players 1 and 2, having no idea whether the move is by the proxy or not, and returns a string with the winner or a tie result. Because it is abstract in a literal sense, it simply waits until one of the two moves results in a win or tie condition. With the RPS game, this is relatively easy because each round of moves is a complete game.

However, what about games like Tic Tac Toe? It’s a game of several different rounds and a wider range of outcomes. Because the doWinner method includes parameters for both players, all moves can be calculated. Each move can be described in terms of the 3-by-3 matrix as C1R1 to C3R3 (Column#/Row#). Once either player has met the win conditions or the play has reached a point where neither can win (a draw), the entire template method can launch.

Alternatively, each move can be delegated to a Referee class, including a Referee subclass. This alternative is the original intention of Heliotis and Schreiner, and the flexibility of the Symmetric Proxy design allows either alternative. In games with more than two players, teams, or some other combination of individual players or teams, only a few changes in the doWinner method parameters could set it up for more complex alternatives.

The key to game flexibility in the Symmetric Proxy class lies in the ability to override the methods in the original template method in the Referee class. Creating multiplayer games beyond two players is quite easy as long as each has a “home” referee, immediately sends all moves to all players and/or referees, but does not have to make adjustments to the basic design pattern.

The Player Interface

The player interface is the starting point for creating a Symmetric Proxy design pattern. The player interface contains six methods. As with all interfaces, it contains the abstract methods and their parameters. Example 13-1 shows the ISymPlayer interface.

Example 13-1. ISymPlayer.as
package
{
    //Symetrical Proxy Interface
    import flash.events.Event;

    interface ISymPlayer
    {
        function numConnect(cl:uint):void;
        function makeMove(event:Event):void;
        function doMove(s:String):void;
        function localMove(locMove:String):void;
        function onProxyMove(proxMove:String):void;
        function takeTurn():void;
    }
}

At this point, we’ll provide a quick overview of what each method does. In order to get a mental image of what occurs, the functions are placed in the approximate order of their launch, except for the last one, which is part of the housekeeping chore of making sure that two players are connected to FMS prior to any move by either side.

  1. First, the numConnect() method checks to see how many clients are connected, and, if two are connected, it allows moves to be made by both players.

  2. The players select a move by pressing one of the three possible move buttons (Rock, Paper, or Scissors). Once a player selects a move, he presses a move button that fires the makeMove() method.

  3. The makeMove() function fires both the doMove() and localMove() methods.

  4. The doMove() operation calls the server to pass on the move to its proxy.

  5. The localMove() method first stores the move in a variable, and then sets a Boolean indicating the fact that the player has moved.

  6. Next, the onProxyMove() function responds to the server call of a shared object and acts like the localMove() method, except it’s on the player’s proxy.

  7. When either the localMove() or onProxyMove() indicates that both players have moved, the takeTurn() method acts to force a call from the Referee to determine the winner and reset the values for a new game.

Some variation in the implementation of these methods determines whether moves can be taken simultaneously or serially.

The Referee

Because the Referee class is abstract, it’s relatively small. The methods are fairly general with the idea that they can be overridden; however, they must be purposely developed. Further, because they’ll be placed in a template method, they have to be developed with an eye to the order in which they’ll be placed. We’ll begin with a look at the Referee class to get an overview and then look at the methods for the class. Example 13-2 shows the Referee class.

Example 13-2. Referee.as
package
{
    //Abstract Class
    public class Referee
    {
        //Move
        private var p1Move:String;//Player 1's move
        private var p2Move:String;//Player 2's move
        private var winner:String;//Value for winner
        private var outcome:String;//Describe winnder
        private var displayWindow:DynamicText;
        private var movecheck:Array;//Array to keep track of moves

        //Template Method
        final function moveComplete(p1Move:String,p2Move:String,
             displayWindow:DynamicText,movecheck:Array):void
        {
            outcome=doWinner(p1Move,p2Move);
            displayResults(displayWindow,outcome);
            resetGame(movecheck);
        }

        //Abstract methods

        protected function doWinner(p1Move:String,p2Move:String):String
        {
            return winner;
        }
        function displayResults(displayWindow:DynamicText,
             outcome:String):void
        {
            displayWindow.setMove(outcome);
        }
        protected function resetGame(movecheck:Array):void
        {
            for (var r:uint =0; r< movecheck.length; r++)
            {
                movecheck[r]=false;
            }
        }
    }
}

The comment at the top of the class indicating that the class is an abstract one is simply a comment. It stands as a reminder that ActionScript 3.0 has no real abstract classes, and we need to remind ourselves to use overrides where needed. Likewise, the comment line (//Abstract methods) indicating abstract methods is a similar reminder that the abstract methods are not real abstract methods, because they’re not supported in ActionScript 3.0.

Methods

Of the three methods in the Referee class, the first needs to be very flexible because it will return the winner of the game. The real implementation of the method will lie in any subclass that describes a game. The doWinner method is most likely to be part of a subclass specifying the rules of the game.

function doWinner(p1Move:String,p2Move:String):String
{
    return winner;
}

It includes parameters for moves by both players. In the context of RPS, where the game has only a single move by each player, the moves represent the endgame conditions. In other games, though, the Referee may need to call for moves every round to accumulate information about win conditions.

The next method is designed to display outcomes, requesting both a reference to a text field and a string.

function displayResults(displayWindow:DynamicText,outcome:String):void
{
    displayWindow.setMove(outcome);
}

In the context of RPS and most games, this method has two different roles. On one hand, as part of the template method, it displays who has won. However, it can also be used to display information independent of the template method. Keeping in mind that moves in RPS are simultaneous, neither player can see the other player’s move until both have made their moves. So this method can also be used to display other information such as the opponent’s move any time it’s appropriate to do so.

The third method in the Referee class is to reset all of the values to the start conditions—setting up the chess pieces in their original positions, so to speak. This method has a housekeeping character, but it’s essential if you’re going to play the game more than once without reloading it.

function resetGame(movecheck:Array):void
{
    for (var r:uint =0; r< movecheck.length; r++)
    {
        movecheck[r]=false;
    }
}

By keeping track of the moves in an array, resetting a game is made both easier and more flexible. With only two moves, the function could be written to reset two Boolean variables to false. (The false state means that the move has not been made.) Because the method uses an array, it doesn’t care how many moves have to be reset. It’s far more flexible and reusable than using non-array variables.

Template Method

The final method is constructed from the three methods that currently exist in the Referee class. As a template method, it is locked using the final statement. As a reminder, the final statement disallows any overrides of the method; however, the methods that make up the template method can be overridden, and we generally expect that at least some methods in the template method will be.

final function moveComplete(p1Move,p2Move,displayWindow,movecheck):void
{
    outcome=doWinner(p1Move,p2Move);
    displayResults(displayWindow,outcome);
    resetGame(movecheck);
}

In this particular template method, the first method passes the game outcome, a String variable. This variable is then used as a parameter in the second method to display the outcome to a specified output object. Finally, the template method resets the game to the start state. Simplified, the template does the following:

  1. Determine who won or if it’s a tie, and place that information in a variable.

  2. Display the game outcome.

  3. Reset the game to play again.

By invoking the Referee, all the information is neatly packaged and ready to resolve the game outcome, display the results, and reset the game.

RPS Subclass

The Referee class is set up to be subclassed and its methods overridden so that developers can reuse the design pattern for more than a single type of game. Example 13-3 shows the Referee subclass, RPS, designed to determine the outcome of a Rock, Paper, Scissors game.

Example 13-3. RPS.as
package
{
    //Rock, Paper, Scissors
    public class RPS extends Referee
    {
        private var winner:uint;
        private var gameOver:Array;
        private var winNow:String;
        //
        override protected function doWinner(p1Move:String,
             p2Move:String):String
        {
            if (p1Move=="rock")
            {
                switch (p2Move)
                {
                    case "rock" :
                        winner=2;
                        break;

                    case "paper" :
                        winner=1;
                        break;

                    default :
                        winner=0;
                }
            }
            else if (p1Move=="paper")
            {
                switch (p2Move)
                {
                    case "rock" :
                        winner=0;
                        break;

                    case "paper" :
                        winner=2;
                        break;

                    default :
                        winner=1;
                }
            }
            else
            {
                switch (p2Move)
                {
                    case "rock" :
                        winner=1;
                        break;

                    case "paper" :
                        winner=0;
                        break;

                    default :
                        winner=2;
                }
            }
            gameOver=new Array("p1 Wins!","p2 Wins!","Tie!");
            winNow=gameOver[winner];
            return winNow;
        }
    }
}

The RPS class overrides the doWinner method developed in the Referee class. The other two methods in the Referee class are usable without any changes. A single algorithm determines which side has won or if a tie occurred, and then transfers the information into an array element that is then stored in a String variable and returned.

Note

If you’re wondering whether it would be easier simply to make the winner variable a string and return it, you’re absolutely right. However, we liked the idea of being able to have a single array in one place where you could add your own “smack” or “trash talk” to be displayed. So instead of simply displaying, “p1 Wins!” you could have something like, “The Mighty Player 1 Conquers All!” Of course you can be more creative than that.

When employing the RPS class, we will observe the dictum to program to the interface and not the implementation. So, in typing any instance where we would use the RPS class, we will type it as Referee and instantiate it as RPS. (Look for this instantiation of the RPS class in the SymPlayer1 and SymPlayer2 classes.)

Information Shared over the Internet

At this point we need to take a little detour to discuss the techniques we used to send data over the Internet for proxy work. As noted earlier in this chapter, we have chosen to use Flash Media Server 2 to pass the move information from a player to its proxy over the Internet. In order to see the relationships involved, we will show the purely FMS2 server-side script and those portions of the player/proxy class that use it.

To begin, Example 13-4 shows the proxygame.asc file (all lowercase). It is written in ActionScript 1.0 because, at the time of this writing, all the code written for the server-side could only be written in Server Side Communication ActionScript (SSCA). The client-side counterpart is all ActionScript 3.0. This file sits in the server-side location of the FMS2 host server. Generally, the host for the SWF files containing the compiled application and the server-side .asc file are on the same host, but not always. The connection to the server is through an RTMP protocol that’s part of the client-side script.

Example 13-4. proxygame.asc
application.onAppStart=function()
{
    trace(this.name + " is reloaded");
    this.ss_so = SharedObject.get("proxmove",false);
    this.dup1=false;
    this.dup2=false;
};
//------------------------
application.onConnect=function(currentClient,username)
{

    currentClient.name=username;
    if(currentClient.moveNow==null)
    {
        currentClient.moveNow="ready";
    }

    var cl=application.clients.length;

    //Check username and see if there are no
          duplications or more than two players
    if(((username == "player1" && !this.dup1) || (
         username == "player2" && !this.dup2)) && cl<=1)
    {
     this.acceptConnection(currentClient);
     this.ss_so.setProperty(currentClient.name,username);
        if(username=="player1")
        {
            this.dup1=true;
        }
        else
        {
            this.dup2=true;
        }

    }
    else
    {
     this.rejectConnection(currentClient);
     trace("Connection rejected");
    }

    currentClient.makeMove=function(moveit)
    {
        application.ss_so.setProperty(currentClient.moveNow,moveit)
        someMove=application.ss_so.getProperty(currentClient.moveNow);
        playerNow=application.ss_so.getProperty(currentClient.name);
        someMove+="~"+playerNow;
        application.ss_so.send("onProxyMove",someMove);
    };

    currentClient.checkPlayNum=function()
    {
        var c2=application.clients.length;
        application.ss_so.send("numConnect",c2);
    }
};

//------------------------
application.onDisconnect = function(currentClient)
{
    dupeName=currentClient.name;
    if(dupeName=="player1")
        {
            this.dup1=false;
        }
        else
        {
            this.dup2=false;
        }
    trace("disconnect: "+currentClient.name);
    this.ss_so.setProperty(currentClient.name,null);
    c2=application.clients.length;
    application.ss_so.send("numConnect",c2);
};

The program is broken down into three main parts. The first part launches only when the application first starts (application.onAppStart). Either player can initially launch the server-side script, and, once launched, it won’t launch again until both players have quit the application, and it’s not launched again until about 20 minutes after the last player has quit.

Note

You may be wondering if the comment about ActionScript being version 1.0 when everything else in this book is written in ActionScript 3.0 is a typographical error. No, there’s no typo. The subset of ActionScript build for FMS2 changed little from the original Server Side Communication ActionScript (SSCA) released with Flash Communication Server (FCS). When FMS2 was released, ActionScript 2.0 was part of the Flash package, but couldn’t be used for server-side coding. So, while SSCA is slightly different from standard ActionScript in any format, it’s also a different version.

The second part of the script launches during the time the application is being used by either player beginning with the attempt to connect to the application (application.onConnect). It generates a default value (“ready”) for the moveNow variable, and checks to see if the correct names are used, and the number of players connected. The function to assign a move to the shared object (currentClient.makeMove) serves to record both the move and which player made the move. A string separated by a tilde (~) character is then sent to all players (application.ss_so.send). The tilde is used to identify the line of demarcation between the player and the player’s move. The client-side script checks which player is making the move and assigns the move to the proxy player. The rest of the connection function deals with housekeeping.

The third part (application.onDisconnect) takes care of further housekeeping by resetting the Boolean variables. It also lets the player know the number of players currently connected. When working with applications over the Internet, letting remotely connected players know whether anyone’s connected is an essential ingredient.

Player-Proxy Classes

The main implementation of the ISymPlayer is in two classes, each representing one of two players. The classes are virtually identical except for identifying themselves as either “player1” or “player2” to the media server. Also, each contains a proxy for its opponent, giving it the androgynous character described previously in this chapter.

Example 13-5 shows the script for the entire class. It can be broken down into six parts:

  1. Imports the necessary name spaces and classes.

  2. Establishes variables required for the different methods.

  3. Contains the Constructor function. This includes all of the necessary connection statements, calls to the text field and button functions, and event listeners for the four buttons.

  4. Implements the six methods from the interface in the order in which they appear in the interface. This is the core of the class.

  5. Checks to see if connection has been made and if so, turns on the connection light and sets up the client-side shared object. Also, this function establishes the shared object and connects the shared object through the NetConnection instance (nc). Finally, it calls the server-side script function checkPlayNum(). This call is a “bounce” in that it triggers the server-side function that sends the number of connected users right back to the client-side function numConnect().

  6. Sets up the text fields and buttons.

By placing Example 13-4 and Example 13-5 side by side, you can better see the interaction between the client-side class and the server-side application. (The .asc files are not actually classes, but they have much in common with a class.) Wherever you see nc.call("functionName",p1,p2) in the SymPlayer1 class, it’s a reference to a function in the server-side script. Likewise, in the server-side script, any application.ss_so.send("functionName",p1) is a reference to a function in the SymPlayer1 or SymPlayer2 classes.

Example 13-5. SymPlayer1.as
package
{
    //Symmetric Player 1/Proxy 2

    import flash.net.NetConnection;
    import flash.net.ObjectEncoding;
    import flash.display.Sprite;
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.NetStatusEvent;
    import flash.net.SharedObject;
    import flash.events.MouseEvent;

    public class SymPlayer1 extends Sprite implements ISymPlayer
    {
        private var nc:NetConnection;
        private var rtmpNow:String;
        private var playerNow:String;
        private var cs_so:SharedObject;
        private var playerText:DynamicText;
        private var showText:DynamicText;
        private var oppText:DynamicText;
        private var moveText:DynamicText;
        private var rockBtn:MoveButton;
        private var paperBtn:MoveButton;
        private var scissorsBtn:MoveButton;
        private var moveBtn:MoveButton;
        private var connect:Connect;
        private var moveVal:String;
        private var p1move:String="ready";
        private var p2move:String="ready";
        private var rps:Referee;
        private var winner:uint;
        private var monitor:Array=new Array(false,false,false);
        private var cl:uint;
        private var mcheck:Boolean=false;
        private var connected:String;

        public function SymPlayer1()
        {
            NetConnection.defaultObjectEncoding =
                flash.net.ObjectEncoding.AMF0;
            SharedObject.defaultObjectEncoding =
                flash.net.ObjectEncoding.AMF0;
            setDynamic();
            setButton();
            rockBtn.addEventListener(MouseEvent.CLICK, makeMove);
            paperBtn.addEventListener(MouseEvent.CLICK, makeMove);
            scissorsBtn.addEventListener(MouseEvent.CLICK, makeMove);
            moveBtn.addEventListener(MouseEvent.CLICK, makeMove);
            //rtmpNow="rtmp://192.168.0.11/proxygame/";
            rtmpNow="rtmp://mojo.iit.hartford.edu/proxygame/";
            //rtmpNow="rtmp:/proxygame/";
            nc = new NetConnection();
            nc.connect(rtmpNow,"player1");
            nc.addEventListener(NetStatusEvent.NET_STATUS,
                checkHookupStatus);
        }
        //
        //makeMove Make Move
        //
        public function makeMove(event:Event):void
        {
            moveVal=event.currentTarget.name;
            if (!monitor[0] && mcheck)
            {
                switch (moveVal)
                {
                    case "scissors" :
                        showText.setMove("scissors");
                        moveText.setMove("scissors");
                        break;

                    case "rock" :
                        showText.setMove("rock");
                        moveText.setMove("rock");
                        break;

                    case "paper" :
                        showText.setMove("paper");
                        moveText.setMove("paper");
                        break;

                    case "move" :
                        var m:String=showText.getMove();
                        if (m != "ready" && m != "Error!")
                        {
                            //Proxy 1 move
                            doMove(m);
                            //Player 1 move
                            localMove(m);
                        }
                        else
                        {
                            showText.setMove("Error!");
                        }
                }
            }
        }
        //
        //localMove Player Move
        //
        public function localMove(locMove:String):void
        {
            playerText.setMove("player1");
            if (!monitor[0])
            {
                p1move=locMove;
                monitor[0]=true;
            }
            //Check to see if both have moved
            monitor[2]=(monitor[0] && monitor[1]);
            if (monitor[2])
            {
                takeTurn();
            }
        }
        //
        //onProxyMove: Info from server
        //
        public function onProxyMove(proxMove:String):void
        {
            playerNow=proxMove.substring(proxMove.indexOf("~")+1);
            proxMove=proxMove.substring(0, proxMove.indexOf("~"));
            if (playerNow=="player2" && !monitor[1])
            {
                playerText.setMove(playerNow);
                p2move=proxMove;
                monitor[1]=true;
            }
            //Check to see if both have moved
            monitor[2]=(monitor[0] && monitor[1]);
            if (monitor[2])
            {
                takeTurn();
            }
        }
        //
        //doMove: Call server
        //
        public function doMove(m:String):void
        {
            nc.call("makeMove",null,m);
        }
        //
        //takeTurn Complete the turn
        //
        public function takeTurn():void
        {
            rps=new RPS();
            rps.moveComplete(p1move,p2move,playerText,monitor);
            rps.displayResults(oppText,p2move);
            showText.setMove("ready");
        }
        //Get number connected
        public function numConnect(cl:uint):void
        {
            if (cl==2)
            {
                mcheck=true;
            }
            else
            {
                mcheck=false;
            }
            connected=String(cl+ " connected");
            playerText.setMove(connected);
        }
        //
        //Connect Check and Set Up Shared Objects
        //
        public function checkHookupStatus(event:NetStatusEvent):void
        {
            if (event.info.code == "NetConnection.Connect.Success")
            {
                connect.gotoAndStop(2);
                cs_so=SharedObject.getRemote("proxmove",nc.uri,false);
                cs_so.client=this;
                cs_so.connect(nc);
                nc.call("checkPlayNum",null,null);
            }
        }
        //Text
        public function setDynamic():void
        {
            playerText=new DynamicText();
            addChild(playerText);
            playerText.x=180;
            playerText.y=50;

            showText=new DynamicText();
            addChild(showText);
            showText.x=260;
            showText.y=50;
            showText.setMove("ready");

            moveText=new DynamicText();
            addChild(moveText);
            moveText.x=180;
            moveText.y=140;

            oppText=new DynamicText();
            addChild(oppText);
            oppText.x=100;
            oppText.y=140;
            oppText.setMove("opponent");
        }
        //Button
        public function setButton():void
        {
            moveBtn=new MoveButton("Make Move",0xcccccc);
            moveBtn.name="move";
            addChild(moveBtn);
            moveBtn.x=100;
            moveBtn.y=50;

            rockBtn=new MoveButton("Rock",0xcccccc);
            rockBtn.name="rock";
            addChild(rockBtn);
            rockBtn.x=100;
            rockBtn.y=80;

            paperBtn=new MoveButton("Paper",0xcccccc);
            paperBtn.name="paper";
            addChild(paperBtn);
            paperBtn.x=150;
            paperBtn.y=80;

            scissorsBtn=new MoveButton("Scissors",0xcccccc);
            scissorsBtn.name="scissors";
            addChild(scissorsBtn);
            scissorsBtn.x=200;
            scissorsBtn.y=80;

            connect=new Connect();
            addChild(connect);
            connect.x=175;
            connect.y=250;
        }
    }
}

All the text fields and buttons are user classes and they need to be built prior to testing the class. In the section “Supporting Classes and Document Files” later in the chapter, you will find the necessary classes for including the buttons and dynamic text fields.

Move Making

While the player class may seem fairly long and unwieldy, its key elements are quite simple. Everything is focused on making one of three moves—rock, paper, or scissors. Each move is nothing more than a string.

Event to move

The first step is encapsulated in the makeMove() function that’s launched by a button event. Using the name of the button (not the label), an algorithm finds which of the three moves has been selected, or if the Move button is clicked. To simplify matters, all name properties are lowercase while the label properties begin with an uppercase letter. (e.g., Rock=label, rock=name). The Move button is included to let the user change her mind after selecting one of the three moves. By clicking the Move button, the player commits to the selected move by launching two methods, doMove() and localMove().

Dual moves

The function to actually make a move once its been selected by clicking the Move button sends the move to the proxy using doMove(), and stores it locally in a variable with localMove(). This is the heart of the Symmetric Proxy. The symmetry lies in the fact that all moves are sent to both the local and proxy players.

The local move checks to be sure that the local player has not moved yet, and then checks to see if the other side has moved. If the other side has moved, then the information about the moves is turned over to the Referee to determine and display the outcome.

Now the doMove() is really nothing more than a call to the server-side script. It passes the move to the server and does no more.

Proxy move

The onProxyMove() method is the mirror image of the localMove(). Because the method is fired no matter which player moves, the algorithm in the method first filters out the local move by splitting the string returned from the server into a move and a player name. It looks at the player name, and if it’s the local’s name, it ignores it and the move associated with it. (The move is handled by the localMove() method.) However, once the filtering has been completed, the algorithm is essentially the same as the local move operation. It does display the fact that the opponent has moved by displaying the name of the player but not the move. In this way, the application maintains the simultaneous nature of the play.

Referee object

The end game conditions are delegated to the Referee object. In this case, the Referee object is the RPS subclass contained within the takeTurn() method. By passing the moves, output text field, and name of the array used to keep track of who took a turn to the RPS object’s moveComplete() template method, everything can be neatly wrapped up. The single line,

rps.moveComplete(p1move,p2move,playerText,monitor);

takes advantage of the template method in the Referee class and launches the operations that make up the template method—doWinner(), displayResults(), and resetGame().

In addition to using the Referee to show the winner and tidy ready the game for a new round, one of the methods, displayResults(), is used to show each player the other’s move. So, in addition to the using the template method, it uses one of the methods that make up the template method separately.

Player 2 Changes

The second player class also implements the ISymPlayer interface, but it’s slightly different, and a few parameter values have changed. All you need to do is make a few changes in SymPlayer1. Begin by saving the SymPlayer1.as file as SymPlayer2.as, and then make the changes in bold in the following segments:

    public class SymPlayer2 extends Sprite implements ISymPlayer
....
    public function SymPlayer2()
....
        //
        //makeMove: Make Move
        //
        public function makeMove(event:Event):void
        ....
            moveVal=event.currentTarget.name;
            if (!monitor[1] && mcheck)
            ....
                        var m:String=showText.getMove();
                        if (m != "ready" && m != "Error!")
                        {
                            //Proxy 2 move
                            doMove(m);
                            //Player 2 move
                            localMove(m);
                        }
                        ....

        //
        //localMove Player Move
        //
        public function localMove(locMove:String):void
        {
            playerText.setMove("player2");
            if (!monitor[1])
            {
                p2move=locMove;
                monitor[1]=true;
            }
....
        //
        //onProxyMove: Info from server
        //
        public function onProxyMove(proxMove:String):void
        {
            playerNow=proxMove.substring(proxMove.indexOf("~")+1);
            proxMove=proxMove.substring(0, proxMove.indexOf("~"));
            if (playerNow=="player1" && !monitor[0])
            {
                playerText.setMove(playerNow);
                p1move=proxMove;
                monitor[0]=true;
....
public function takeTurn():void
        {
            rps=new RPS();
            rps.moveComplete(p1move,p2move,playerText,monitor);
            rps.displayResults(oppText,p2move);
            showText.setMove("ready");
        }
....

Simply change a few 1s to 2s and some 0s to 1s in the key methods in the SymPlayer1 class, and you’re good to go.

Classes and Document Files Support

As big as the two player class implementations are, they would be much bigger if not for the work done by the supporting classes that create the buttons and dynamic text fields.

Dynamic Output Text Fields

Because all the data input is done with button entries, the text fields for displaying data is all typed as DYNAMIC. Example 13-6 shows the class for the text fields.

Example 13-6. DynamicText.as
package
{
    import flash.text.TextField;
    import flash.text.TextFieldType;
    import flash.text.TextFormat;
    import flash.display.Sprite;

    public class DynamicText extends Sprite
    {
        private var gameInfo:TextField;
        private var gameFormat:TextFormat;

        public function DynamicText():void
        {
            gameInfo=new TextField();
            addChild(gameInfo);
            gameInfo.border=false;
            gameInfo.background=true;
            gameInfo.type=TextFieldType.DYNAMIC;
            gameInfo.width=70;
            gameInfo.height=20;
            gameFormat=new TextFormat();
            gameFormat.color=0xcc0000;
            gameFormat.font="Verdana";
            gameFormat.size=10;
            gameInfo.defaultTextFormat=gameFormat;
        }
        public function setMove(pMove:String)
        {
            gameInfo.text=pMove;
        }
        public function getMove():String
        {
            return gameInfo.text;
        }
    }
}

The getter/setter functions make it far easier to access and display the available moves. At the same time, the text fields act as temporary storage of values that can be placed into variables or parameters.

Button Controls

The class for buttons is made up of Sprite, TextField and TextFieldType classes. Example 13-7 shows the script for the class.

Example 13-7. MoveButton.as
package
{
    import flash.text.TextField;
    import flash.text.TextFieldType;
    import flash.display.Sprite;

    public class MoveButton extends Sprite
    {
        private var mvBtn:Sprite;
        private var id:String;

        public function MoveButton(mover:String,btncolor:int)
        {
            mvBtn=new Sprite();
            this.id=id;
            addChild(mvBtn);
            mvBtn.buttonMode=true;
            mvBtn.useHandCursor=true;
            mvBtn.graphics.beginFill(btncolor);
            mvBtn.graphics.drawRoundRect(0,0,(mover.length*8),20,5,5);
            var mvLbl:TextField=new TextField();
            mvBtn.addChild(mvLbl);
            mvLbl.text=mover;
            mvLbl.selectable=false;
        }
    }
}

The combined sprite and text provide a bit more information than just using a SimpleButton class would. Because we wanted the information in the text to be the information sent as moves, and the interactive aspects to be displayed in the dynamic text field rather than changes in the button’s appearance, the choice was not a difficult one. Also, we experimented with different colors for the buttons, decided to let the designer pass his own color scheme, and so included a color parameter (btncolor).

The Flash File and Connection Movie Clip

The final aspects of the application include static text placed directly on the stage and a movie clip created to serve as a connection light. Figure 13-5 shows the static text placement in the right portion on the stage, and what the application looks like when running on the left.

Stage and SWF file views
Figure 13-5. Stage and SWF file views

The following steps show how to create the last part of the application:

  1. Open a new Flash File (ActionScript 3.0). Add a layer. Name the top layer “Connect” and the bottom layer “Text.”

  2. In the Document class window, type in SymPlayer1. Save the file as SymPlayer1.fla.

  3. Click the Text layer to select it. Using the Text Tool, select Static Text, and set the font to Arial Black, size to18 points and color #666666. Type in Player 1 and position it at X=158, Y=1.

  4. Still using the Text Tool, change the size to 11, and first type in Proxy 2 and position it at X=100, Y=120. Then type in Your Move and position it at X=180, Y=120. Lock the Text layer.

  5. Click on the Connect layer to select it. Using the Oval Tool, select a red fill color and black stroke color. Set the stroke width to .25. Draw a W=10, H=10 circle.

  6. Select the circle and press the F8 key to open the Convert to Symbol dialog box. In the Name window, type in Connect, and select Movie clip as the Type. If you see the Advanced button, click it to open the Linkage and Source views. Click the Export for ActionScript Linkage checkbox. You should now see “Connect” in the Class window and flash.display.MovieClip in the Base class window. Click OK.

  7. Click in Frame 1 and open the Actions panel (Press F9 Windows, Option + F9 Macintosh). Type in stop() in the Actions panel. Close the Actions panel.

  8. Click Frame 2 and press F6 to add a second keyframe. Change the fill color in the second frame from red to green. Click the Scene1 icon to exit the Symbol edit mode.

  9. You should see the movie clip on the stage. Delete it from the stage. Open your Library panel (Window→Library from the menu bar or Ctrl+L Windows or Command+L Macintosh). You should see the movie clip with the name Connect in the Library. Be sure that it’s spelled exactly that way, with an uppercase “C.” This is the class name you use in your SymPlayer1 and SymPlayer2 classes.

  10. Select File→Publish to generate SWF and HTML files.

This application is designed to have two different players accessed from two different Flash files. To create the second file, use the following steps.

  1. Open SymPlayer1.fla, and, using File→Save As, save the file as SymPlayer2.fla.

  2. Change the Document class to SymPlayer2.

  3. Change the static text from “Player 1” to “Player 2,” and “Proxy 2” to “Proxy 1.” Save the file.

  4. Select File→Publish to generate SWF and HTML files.

That’s it. Place both HTML and SWF files on a web server, one player selects Player 1 and the other Player 2, and you’re good to go. You might want to set up a little HTML file that allows the user to choose either Player 1 or Player 2, and then links to the players through the HTML file. If one is in use, indicated by a red connect button, then the user can just switch back and use the other.

Summary

The Symmetrical Proxy design pattern represents the kinds of experiments with design patterns envisioned by Gamma and his associates when they wrote Design Patterns: Elements of Reusable Object-Oriented Software. James Heliotis and Axel Schreiner’s key insight is that two sides of a game over the Internet can be played on a client using symmetrical referees and each side pitting a local player against the proxy of an opponent. This is not an isolated solution to any single game, but rather any games played over a network.

Our particular implementation of the design pattern used Flash Media Server 2 to send the moves of each side to the other, and it’s doubtful whether Heliotis or Schreiner had a clue about FMS2. Therein lies the strength of the design pattern. Design patterns should represent general solutions to problems and not specific implementations of a solution. Because of the flexibility of the Symmetrical Proxy pattern, we were able to apply it using the Internet communication program of our choosing, rather than one specified by the original designers.

By building in a proxy method into the interface and its implementations in the two players, we were able to make changes to the general design that did not violate its flexibility. This “androgynous” feature may have reduced the granularity of the design and slightly enlarged the size of the main classes, but it remained within both the structure and the spirit of the design pattern. Design patterns are general solutions, and as long as the patterns meet the key criteria of flexibility and reusability while maintaining good OOP practices, they can be implemented in an unlimited number of ways.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.22.169