[ Introduction
              ]  
            In this tutorial we will learn how to add support for spectators
              in game rooms. Spectators are a particular class of users that
              can join a game room but can't interact with the game. When one
              of the players in the room leaves the game one of the spectators
              can take its place. 
  To demonstrate how SmartFoxServer handles spectators we will
  use the previous "SmartFoxTris" board game and we'll
  add spectators to it. 
  By the end of this tutorial you will have learned how to create a full turn-based
  games with spectator support all done on the client side. Also using the previous
  tutorials you will be able to add extra features like a buddy list or multi-room
  capabilities. 
            [ Requirements ]  
                   
              Before proceeding with this tutorial it is necessary that
              you're already familiar with the basic SmartFoxServer concepts
              and that you've already studied the "SmartFoxTris" board
              game tutorial. 
    
   [ Objectives ]  
            We will enhance the previous "SmartFoxTris" board
              game by adding the following features: 
    
» new options in the "create room" dialogue box: you will be
able to specify the maximum amount of spectators for the game room 
» new options in the "join" dialogue box: the user will have
to choose if joining as a spectator or player 
» the ability to switch from spectator to player when a player slot is
free 
              
            [ Creating rooms with spectators
                and handling user count updates ]  
            Before we dive in the game code we'd like to have a look at the createRoom(roomObj)
              command. 
  The roomObj argument is an object with the following properties: 
            
              
                | name | 
                the room name | 
               
              
                | password | 
                a password for the room (optional) | 
               
              
                | maxUsers | 
                the max. number of users for that room | 
               
              
                | maxSpectators | 
                the max. number of spectator slots (only for game rooms ) | 
               
              
                | isGame | 
                a boolean, true if the game is a game room | 
               
              
                | variables | 
                an array of room variables (see below) | 
               
             
            As you can see, not only you can specify the maximum number of
              users but also how many spectators you want for each game room.
              When you have created a game room with players and spectators you
              will receive user count updates not only for players but for spectators
              too.  
  As you may recall we can handle user count updates through the onUserCountChange(roomId) event
  handler where the roomId parameter tells us in which room
  the update occured. 
            Here follows the code used in this new version of "SmartFoxTris": 
            function updateRoomStatus(roomId:Number)
{
        var room:Room = smartfox.getRoom(roomId)
        var newLabel:String
        
        if (!room.isGame())
        newLabel = room.getName() + " (" + room.getUserCount() + "/" + room.getMaxUsers() + ")"
        else
        {
                newLabel = room.getName() + " (" + room.getUserCount() + "/" + room.getMaxUsers()
                newLabel += ")-(" + room.getSpectatorCount() + "/" + room.getMaxSpectators() + ")"
        }
        
        
        for (var i:Number = 0; i < roomList_lb.getLength(); i++)
        {
                var item:Object = roomList_lb.getItemAt(i)
                
                if (roomId == item.data)
                {
                        roomList_lb.replaceItemAt(i, newLabel, item.data)
                        break;
                }
        }
}
            
             In the first line we get the room object, then we check if the
              room is a game and we dynamically create a label for the game list
              component we have on screen. 
            The label will be formatted like this: "roomName (currUsers
              / maxUsers) - (currSpectators / maxSpectators)" and we get
              the updated values by calling the following room methods: 
            
              
                | room.getUserCount() | 
                returns the number of users in the room | 
               
              
                | room.getMaxUsers() | 
                returns the max. amount of users for that room | 
               
              
                | room.getSpectatorCount() | 
                returns the number of spectators currently in the room | 
               
              
                | room.getMaxSpectators() | 
                returns the max. number of spectators for that room | 
               
             
            [ Handling Spectators ]  
            Adding spectators to the game introduces a few difficulties which
              we'll overcome by using Room Variables. The first
              problem is how to keep an "history" of the game in progress
              so that when a spectator joins a game room he's immediately updated
              to the current status of the game. 
            If you go back to the previous version of this board game you
              will notice that we've been using the sendObject() command
              to send moves from one client to the other. For the purpose of
              that game it was very easy to send game moves. Unfortunately in
              this new scenario using the sendObject is not
              going to work because moves are sent only between clients: if a
              spectator enters the room in the middle of a game we wouldn't be
              able to synch him with the current game status. 
            The solution to the problem is to keep the game status on the
              server side by storing the game board data in a Room Variable that
              we will call "board". By doing so each
              move is stored in the server side and a spectator joining in the
              middle of the game can easily read the current game status and
              get in synch with the other clients. In order to optimize the Room
              Variable as much as possible we will make our "board" variable
              a string of 9 characters, each one representing one cell of the
              3x3 board.  
            We'll use a dot (.) for empty cells a "G" for
              green balls and an "R" for red balls:
              this way we send a very small amount of data each time we make
              a move. 
            Also we need another Room Variable to indicate which cell the
              player clicked and who sent the move: the new variable will be
              called "move" and it will be a string
              with 3 comma separated parameters: p, x, y 
            p = playerId 
                x = x pos of the cell 
                y = y pos of the cell 
            In other words this move: "1,2,1" will mean that player
              1 has clicked on the cell at x=2 and y=1 
            Each time a move is done we will send the new status of the board
              plus the move variable to the other clients. 
  Summing up we will have four room variables in each game room: player1, player2, move, board. As
  you remember "player1" and "player2" are
  the names of the users playing in the room. These variables tell us how many
  players are inside and if we can start/stop the game. 
            [ Advanced Room Variables features ]  
                 
  If you look at the documentation of the onRoomVariablesUpdate() event
  you will notice that it sends two arguments: roomObj and changedVars. We already
  know the roomObj argument but whe should introduce the second
  one: changedVars is an associative array with the names of
  the variables that were updated as keys. 
            In other words if you want to know if a variable called "test" was
              changed in the last update, you can just use this code: 
            smartfox.onRoomVariablesUpdate = function(roomObj:Room, changedVars:Object)
{
        // Get variables
        var rVars:Object = roomObj.getVariables()
        
        if (changedVars["test"])
        {
                // variable was updated, do something cool here...
        }
}
            
            This feature may not seem particularly interesting at the moment,
              however it will become very useful as soon as we progress with
              the analysis of the code. 
  The actionscript code located in the frame labeled "chat" is
  very similar to the one in the previous version of the game, however we have
  added an important new flag called "iAmSpectator" which
  will indicate if the current player is a spectator or not. 
            Let's see how this flag is handled in the onJoinRoom event
              handler: 
            smartfox.onJoinRoom = function(roomObj:Room)
{
        if (roomObj.isGame())
        {
                _global.myID = this.playerId;
                
                if (_global.myID == -1)
                	iAmSpectator = true
                
                if (_global.myID == 1)
                	_global.myColor = "green"
                else if (_global.myID == 2)
                	_global.myColor = "red"
                
                // let's move in the "game" label
                gotoAndStop("game")
        }
        else
        {
                var roomId:Number 		= roomObj.getId()
                var userList:Object 	= roomObj.getUserList()
                
                resetRoomSelected(roomId)
                
                _global.currentRoom = roomObj
                
                // Clear current list
                userList_lb.removeAll()
                
                for (var i:String in userList)
                {
                        var user:User 		= userList[i]
                        var uName:String 	= user.getName()
                        var uId:Number		= user.getId()
                        
                        userList_lb.addItem(uName, uId)
                }
                
                // Sort names
                userList_lb.sortItemsBy("label", "ASC")
                
                chat_txt.htmlText += "<font color='#cc0000'>>> Room [ " 
				+ roomObj.getName() + " ] joined</font>";
        }
}
            
            In the past tutorials you have learned that every player in a
              game room is automatically assigned a playerId,
              which will help us recognize player numbers. When a spectator joins
              a game room you will be able to recognize him because his/her playerId
              is set to -1. In other words all players will have their own unique
              playerId while the spectator will be identified with a playerId
              = -1 
            In the first lines of the code, after checking if the currently
              joined room is a game, we check the playerId to see if we'll be
              acting as a regular player or as a spectator. The rest of the code
              is just the same as the previous version so we can move on the
              next frame, labeled "game". 
            [ The game code ]  
            The first part of the code inside this frame sets up the player
              based on the "iAmSpectator" flag. 
            var vObj:Array = new Array()
// If user is a player saves his name in the user variables
if (!iAmSpectator)
{
        vObj.push({name:"player" + _global.myID, val:_global.myName})
        smartfox.setRoomVariables(vObj)
}
// If I am a spectator we analyze the current status of the game
else
{
        // Get the current board server variable
        var rVars:Object = smartfox.getActiveRoom().getVariables()
        var serverBoard:String = rVars["board"]
        
        // If both players are in the room the game is currently active
        if (rVars["player1"].length > 0 && rVars["player2"].length > 0)
        {
                _global.gameStarted = true
                
                // Show names of the players
                showPlayerNames(rVars)
                
                // Draw the current game board
                redrawBoard(serverBoard)
                
                // Check if some has won or it's a tie
                checkBoard()
        }
        
        // ... the game is idle waiting for players. We show a dialog box asking 
				// the spectator to join the game
        else
        {
                win = showWindow("gameSpecMessage")
                win.message_txt.text = "Waiting for game to start!" 
				+ newline + newline + "press [join game] to play"
        }
}
            
             As you will notice in each action we will take, we'll check if
              the current user is a player or not and behave appropriately. In
              this case we save the user name in a room variable if the client
              is a player. On the contrary, if we are handling a spectators,
              we have to check the status of the game. 
            In the first "else" statement we first verify if the
              game is currently running or not. If the game is not ready yet
              (i.e. there's only one player in the room) a dialogue box will
              be shown on screen with a button allowing the user to join the
              game and become a player. If the game is running we set the _global.gameStarted flag,
              show the player names on screen and call the redrawBoard method
              passing the "board" room variable (which represents the
              game status). 
            Also the checkBoard() method is invoked to verify
              if there's a winner in the current game: this covers the case in
              which the spectator enters the room when a match has just finished
              with a winner or a tie. Now it's time to analyze the onRoomVariablesUpdate handler
              which represents the core of the whole game logic. Don't be scared
              by the length of this function, we'll dissect it in all its sections: 
            
smartfox.onRoomVariablesUpdate = function(roomObj:Room, changedVars:Object)
{
        // Is the game started?
        if (inGame)
        {
                // Get the room variables
                var rVars:Object = roomObj.getVariables()
                
                // Player status changed!
                if (changedVars["player1"] || changedVars["player2"])
                {
                        // Check if both players are logged in ...
                        if (rVars["player1"].length > 0 && rVars["player2"].length > 0)
                        {
                                // If game is not yet started it's time to start it now!
                                if (!_global.gameStarted)
                                {
                                        _global.gameStarted = true
                                        
                                        if (!iAmSpectator)
                                        {
                                                hideWindow("gameMessage")
                                                _root["player" + opponentID].name.text 
						= rVars["player" + opponentID]
                                        }
                                        else
                                        {
                                                hideWindow("gameSpecMessage")
                                                showPlayerNames(rVars)
                                        }
                                        
                                        // It's player one turn
                                        _global.whoseTurn = 1
                                        
                                        // Let's wait for the player move
                                        waitMove()
                                }
                        }
                        
                        // If we don't have two players in the room we have to wait for them!
                        else
                        {
                                // Reset game status
                                _global.gameStarted = false
                                
                                // Clear the game board
                                resetGameBoard()
                                
                                // Reset the moves counter
                                moveCount = 0
                                
                                // movieclip reference used for showing a dialog box on screen
                                var win:MovieClip
                                
                                // If I am a the only player in the room I will get a 
				// dialogue box saying we're waiting
                                // for the opponent to join the game.
                                if (!iAmSpectator)
                                {
                                        win = showWindow("gameMessage")
                                        win.message_txt.text = "Waiting for player " 
					+ ((_global.myID == 1) ? "2" : "1") 
					+ newline + newline + "press [cancel] to leave the game"
                                        
                                        // Here we reset the server variable called "board"
                                        // It represents the status of the game board 
					// on the server side
                                        // Each dot (.) is an empty cell of the board (3x3)
                                        var vv:Array = []
                                        vv.push({name:"board", val:".........", persistent: true})
                                        
                                        smartfox.setRoomVariables(vv)
                                }
                                
                                // The spectator will be shown a slightly different dialogue box, 
				// with a button for becoming a player
                                else
                                {                                      
                                        win = showWindow("gameSpecMessage")
                                        win.message_txt.text = "Waiting for game to start!" 
					+ newline + newline + "press [join game] to play"
                                }
                        }
                }
                
                // The game restart was received
                else if (changedVars["move"] && rVars["move"] == "restart")
                {
                        restartGame()
                }
                
                // A move was received
                else if (changedVars["move"])
                {
                        // A move was done
                        // the MOVE room var is a string of 3 comma separated elements
                        // p,x,y
                        // p = player who did the move
                        // x = pos x of the tile
                        // y = pos y of the tile
                        
                        // Get an array from the splitted room var
                        var moveData:Array = rVars["move"].split(",")
                        var who:Number = moveData[0]
                        
                        var tile:String = "sq_" + moveData[1] + "_" + moveData[2]
                        var color:String = (moveData[0] == 1) ? "green" : "red"
                        
                        // Draw move on player board
                        if (!iAmSpectator)
                        {
                                // Ignore my moves
                                if (who != _global.myID)
                                {
                                        // Visualize opponent move
                                        setTile(tile, color)
                                        moveCount++
                                        
                                        checkBoard()
                                        
                                        nextTurn()
                                }
                        }
                        
                        // Draw move on spectator board
                        else
                        {
                                redrawBoard(rVars["board"])
                                
                                checkBoard()
                                
                                nextTurn()
                        }
                }
        }
}
			
            Before we start commenting each section of the code it would be
              better to isolate the most important things that this method does.  
  Basically the code checks three different conditions: 
            1) If there's been a change in the player room
              variables, called player1 and player2.
              When one of these vars changes, the game must be started or stopped,
              based on their values. The code related with this condition starts
              with this line:  
            if (changedVars["player1"] || changedVars["player2"]) 
            2) If the "move" variable
              was set to "restart". This is a special case and it's
              the signal that one of the players has clicked on the "restart" button
              to start a new game. The code related to this section start with
              this line:  
            else if (changedVars["move"] && rVars["move"] == "restart") 
            3) If the "move" variable
              was updated with a new player move. In this case we'll update the
              game board, check for a winner and switch the player turn. The
              code related to this section start with this line:  
            else if (changedVars["move"]) 
            Let's start by analyzing section one: the code
              should look familiar as it is very similar to the one used in the
              first "SmartFoxTris" game. 
  If one of the two player variables was changed then a change in the game status
  will occur: if the game was already started (_global.gameStarted = true) and
  one of the player left, we have to stop the current game showing a message
  window. The message is going to be slightly different if you are a player or
  a spectator. The latter will be shown a button to join the game and become
  a player. 
      
  Please also note that the player that remains in the game will clear both his
  board game and the "board" room variable which in
  turn will update the other spectators. On the contrary if the game was idle
  and now the two player variables are ready, we can start a new game. 
      
  The second section of the code is much simpler: when the "move" variable
  is set to "restart" the restartGame() method is called which will
  clear the game board making it ready for a new match. 
      
  Finally the third section is responsible of handling the moves
  sent by the opponent. 
  As we said before in this article we have used a comma separated string to
  define a single player move. By using the split() String method
  we obtain an array of 3 items containing the playerId followed
  by the coordinates of the board cell that was clicked. 
            [ Turning spectators into players ]  
            As we have mentioned before when one of the player slots is free,
              spectators will be able to join the game and become players. 
            The message box showed to spectators is called "gameSpecMessage" and
              you can find it in the library under the "_windows" folder.
              By opening the symbol you will notice a button called "Join
              Game" that calls the switchSpectator() function
              in the main game code: 
            function switchSpectator()
{
        smartfox.switchSpectator(smartfox.activeRoomId)
}
            
            This very simple function invokes the switchSpectator() command
              of the SmartFoxServer client API which will try
              to join the spectator as player 
  in the game room. There's no guarantee that the request will succeed as another
  spectator might have sent this request before, filling the empty slot before
  our request gets to the server. In any case the server will respond with a onSpectatorSwitched event: 
            smartfox.onSpectatorSwitched = function(success:Boolean, newId:Number, room:Room)
{
        if (success)
        {
                // turn off the flag
                iAmSpectator = false
                
                // hide the previous dialogue box
                hideWindow("gameSpecMessage")
                
                // setup the new player id, received from the server
                _global.myID = newId
                
                // Setup the player color
                _global.myColor = (_global.myID == 2) ? "red" : "green"
                
                // Setup player name
                _root["player" + _global.myID].name.text = _global.myName
                opponentID = (_global.myID == 1) ? 2 : 1
                
                // Store my new player id in the room variables
                var vObj:Array = []
                vObj.push({name:"player" + _global.myID, val:_global.myName})
                
                smartfox.setRoomVariables(vObj)
        }
        
        // The switch from spectator to player failed. Show an error message
        else
        {
                var win:MovieClip = showWindow("gameMessage")
                win.message_txt.text = "Sorry, another player has entered"
        }
}
            
            As you can see we get a "success" boolean argument which
              will tell us if the operation was successfull or not. If it was
              we can turn the user into a player by assigning him the newId paramater
              as playerId, then setting the appropriate player color and finally
              we update the room variables with the new player name. 
            [  Conclusions ] 
            In this tutorial you have learned how to use server-side variables
              to keep the game status and handle spectators in a turn-based game.
              We reccomend to examine the full source code of this multiplayer
              game to better understand every part of it. Once you will be confident
              with this code you will be able to create an almost unlimited number
              of multiplayer turn-based games and applications by combining the
              many features we have discussed in the the other tutorials. 
            Also you if you have any questions or doubts, post them in our
              forums.  
           |