Accessibility
 
Home / Developer Center / Flash Communication Server Developer Center /

Flash Communication Server Article

Other Classes

Of course, other classes are required in our design, which also follow the MVC pattern where possible. Player objects are created for each player connected to the game and are stored in a player list. The application broadcasts changes in the player list to all clients so that all players can see changes in player status immediately, such as players dropping out of the game. A separate player controller object handles player connections, requests to start the first game, restart a game, or disconnect from a game in progress. The player controller object can easily check the state of the game and respond to user requests accordingly. For example, once the game starts, the player controller will not allow any new players to join the game. The player view is a visible panel in the client that allows the user to connect to a game, leave a game, and see information about other players.

Finally, our game has an audio chat system to allow players to discuss the game as it progresses. Players can decide who will hear their remarks and can mute sound from any player. We built the audio chat on top of information in the player and player list; the player controller controls it.

The following section provides a more detailed description of some of the classes that make up the game and how they work.

The GameModelClass
The game model maintains all the state information about the game and provides methods that correctly manipulate the squares in the board. It contains information about the size of the board, an array of square objects, and anything else it needs to initialize, play, and end a game. The game logic is also contained within this object. The game model contains an instance of the BoardClass where it processes most of the game logic. For example, when it receives a request to uncover a square, the game model will first check if the square is already uncovered. If it isn't uncovered, it will mark who has uncovered it (by color) and then uncover the square. If the square is blank it will recursively uncover all the neighboring squares until no adjacent blank squares remain. When the uncovering process finishes, the application broadcasts a message listing the results. There are a number of ways to implement the game board and recursive uncovering of squares. In this game, the board is a simple one-dimensional array. As an example of how this works, you can represent a simple three-by-three board with an array with 9 slots numbered 0 through 8 that map to a board as illustrated here:

0 1 2
3 4 5
6 7 8

To begin a game, you must randomly place bombs on the board. There are a number of ways to do this; here, we created a temporary array containing the number of each square:

Temporary array:
0 1 2 3 4 5 6 7 8
Index:
0 1 2 3 4 5 6 7 8

As positions in the temporary array are randomly selected as bomb locations, two things happen. First, you add the bomb location to another array called the map array by placing an x in the slot and placing the number of bombs in the surrounding squares. In this example slot number 5 was randomly selected to hold a bomb and the surrounding squares given the value 1:

Map array:
   1 1    1 x    1  1 

Which represents this board layout:

   1 1
   1 x
   1 1

Second, you remove slot number five from the temporary array using the Array.splice() method:

tempArray.splice(randomCell, 1);
Temporary array:
0 1 2 3 4 6 7 8
Index:
0 1 2 3 4 5 6 7

Reducing the size of the temporary array this way makes it easy to choose another random cell without a bomb in it:

randomCell = Math.floor(Math.random()* tempArray.length);

After you repeat this process and place all the bombs, users could begin the game. However, there is a problem with converting minesweeper to a multi-player game. In the single-user version, the first square uncovered cannot contain a mine. One way to implement this is to secretly move the mine to another square if the user clicks on a mine. In a multi-user version it didn't seem fair to let everyone try to click on a square with the possible result that after one square was uncovered others would contain a mine and end the game for those users. Instead we chose to uncover at least one square when the game begins. That way each player has a clear starting position to asses. Describing the entire game is beyond the scope of this tutorial. However, some methods of interest in the BoardClass are:

  • populateBomb - uses a temporary array to randomly choose bomb locations
  • getNeighbors - given a square location returns a list of neighboring squares
  • preOpen - opens a square and its neighbors so that all players have somewhere to start
  • openSpaces - recursively opens spaces

The source for the GameModelClass is in the GameModelClass.asc file while the source for BoardClass is in the BoardClass.asc file. Also see SquareClass.asc.

The GameModelClass coordinates user interactions and updates the views; it bases the updates on the current state of the game within the board object. The most complex part of this process is what the GameModelClass does when it receives an openSquare message from a movie. (See the openSquare method of the GameModel Class.) This process is complicated because many users may click on an uncovered square within a few milliseconds of each other. However, the first openSquare message that arrives at the server may not represent the first click in real-time. To understand this scenario, imagine two players playing the game. Player A connects to the server from outside Ryerson's campus and has an average network latency of 120 milliseconds, or just longer than one tenth of a second. Player B is on campus and experiences an average latency of only 10 milliseconds or one hundredth of a second. The following sequence of events illustrates how an unfair situation could happen:

Event Sequence Time of Event (milliseconds) Description
1 1000 Player A clicks on an unopened square one second into the game.
2 1100 Player B clicks on the same square one tenth of a second later.
3 1110 Player B's openSquare request arrives at the server 10 milliseconds later, which uncovers the square.
4 1120 Player A's openSquare request arrives at the server 120 milliseconds after it was sent but the square was already uncovered by Player B.

 

In this unfair situation, player B gets credit for opening a square even though player A clicked on it first in real-time. Off-campus users may experience much higher network latencies, which introduces an unfair disadvantage due to the system. To reduce unfairness, the application measures each client's network latency and estimates the real-time for each user's click on an unopened square. If user A's request to open a square arrives after the square has already been opened, user A is assigned credit for opening the square. In the current version of the game, we only measure network latency by making a remote method call after getting a current date/time. The server simply returns from the method call and compares a new date/time to the old one. The application determines an approximate latency by dividing the round-trip delay in half. We describe a better way of doing this later in a related latency problem. In the case where we need to correct who will get credit for uncovering a square, we send a "correctGame" message to all the movies:

application.playerList_so.send("correctGame",
                               this.gameBoard.mapArray[square], 
                               square, removeFromPlayer);

All GameModelClass methods that communicate with views use the send method of the playerList_so shared object. For example the restart method contains this statement:

application.playerList_so.send("reStartGame");

And here is another example from the openSquare method:

application.playerList_so.send("openSpaces", str);

In this case, the openSpaces method of the player list shared object will call the openSpaces method of the game view. The string passed will contain a comma separated lists of square locations and the number to display in each of them.

GameControllerClass
An application may contain many MVC triads. Each controller does not maintain state or store data. It simply manages messages as they arrive. When there are multiple MVC triads at work, the controllers cooperate by passing messages to each other in order to ensure that they update the correct model and views from user events. In this game, using this process turned out to be unnecessary. The game controller simply passes all its messages directly to the game model. In more complex applications cooperating controllers may have some value. Here is a typical game controller method:
GameControllerClass.prototype.requestToPreOpenBoard = function() {
gameModel.preOpenBoard();
}

The source for the GameControllerClass is in the GameControllerClass.asc file.

GameViewClass
The GameViewClass is a movie clip subclass that shows the user what is happening in the game. It is responsible for drawing the board and dealing with messages sent from the game model. In turn the GameViewClass contains another movie clip subclass: the ClientSquareClass. A ClientSquareClass instance draws each board square and contains the usual graphics in its timeline for each state the square can appear: covered, opened, number, bomb, and disappear.

When the openSquares method of this class is called, it receives a string of squares to open and the values to place in each square. It splits the string into an array and alters the contents of each square accordingly.

Each square is only responsible for reporting when the user clicks on it. Here is the onRelease handler of the ClientSquareClass:

square.onRelease = function(){

//start the clicking sound.
click_sound.start(0, 1);
//send the info to the server.
game_nc.call("openSquare", null, (this._parent._name).substring(3));
}

The source for the GameViewClass is in the GameViewClass.as file. Also, see the ClientSquareClass.as file.

The Player List, Controller, and View Classes
Aside from the client object, which represents each movie's network connection to the game, we use a separate player object to keep track of each player's name, score, network latency, display color, and status on the server. (See the PlayerClass.asc file.) The player objects are stored in the PlayerList shared object. When players arrive or leave, the application adds or deletes the entry in the PlayerList through the PlayerListClass. Also, when a player's score changes, the class updates the player objects in the player list.

When the PlayerListClass adds or deletes a player to the PlayerList shared object, each movie automatically receives an onSync event. This is a feature of remote shared objects and provides another way that information can be broadcast to all the movies connected to an application. For each movie to receive and properly handle each onSync event, you must define an onSync event handler method for the shared object. The listing below shows the handler for the PlayerList. It receives a list of items that have changed whenever players add or remove themselves. Lastly, the class examines each item's code property to see what has happened to the item and uses the item's name field to identify the name of the shared object property that was, in this case, added or deleted.

playerList_so.onSync = function(list) {
   for (var i = 0; i<list.length; i++) {
   
      // on change Add the new player.
      if (list[i].code == "change") {
         if (playerList_so.data[list[i].name].name == playerName) {
            player = playerList_so.data[list[i].name];
         }
         var Tplayer = playerList_so.data[list[i].name];
         playerView.addPlayer(list[i].name, Tplayer.color);
      }
      
      // on delete then remove the player
      if (list[i].code == "delete") {
         trace("Deleting: "+list[i].name);
         playerView.info.info_txt.text += list[i].name + " Has left the game\n";
         playerView.removePlayer(list[i].name);

      }
   }// end for
}

Whenever anything visible must change, the process calls a method of the playerView object and in turn the playerView object hides or shows the movie clip that represents each player.

As was the case with the GameControllerClass, the PlayerListControllerClass just passes messages it receives to the player list which acts as the model in the player list MVC triad. See the PlayerListClass.asc, Player.asc, PlayerListController.asc and PlayerViewClass.as files.

The Lobby and Games
If you have tried the game, it placed you in a lobby before you could create or join a game. The lobby is a separate and very simple communication application. It lives in the flashcom/applications/mmsLobby folder. When the app creates a game in a lobby, nothing more than a name is created. The game is created and lives in the flashcom/application/OOMS folder when users attempt to connect to it. Each game is a separate instance of an OOMS game. For example players who play a game they name "challenge" actually connect to a game instance through an RTMP request: rtmp://hostname/OOMS/challenge. The main timeline of the SWF file contains a lobby segment and a game segment and all the code required to connect to the lobby and the game.

In the last section, we'll discuss our conclusions and some of the possible enhancements to the game.

 
 
Previous Contents Next