[ READY TO PLAY? ]
Now we can analyze the code in the "game"
label. Please open the actionscript panel (F9).
The first lines of code are mostly initialization stuff:
- inGame = true
- moveCount = 0
- _global.gameStarted = false
- _global.whoseTurn = 1
-
The inGame flag is finally set, then the moveCount
counter is initialized together with two more _global vars:
_global.gameStarted: a flag that tells us if the
game is started
_global.whoseTurn: a number telling whose player
turn is
The code below shows the player name on screen and the set the opponentID
variable:
- _root["player" + _global.myID].name.text = _global.myName
- opponentID = (_global.myID == 1) ? 2 : 1
As you may have noticed the application can dynamically behave
like if it was player one or player two.
This is achieved using the _global.myID variable
which in turn is a copy of the smartfox.playerId
variable: by knowing which player is currently playing we can easily
play both sides.
The line where we set the opponentID may look
a little tricky if you're not used to this type of syntax, however
the same line
could have been written like this:
- if (_global.myID == 1)
- opponentID = 2
- else
- opponentID = 1
-
It's a 4 line block of code compressed into a single line!
We've already commented the following lines: here the room variable
for the player is created.
- vObj = new Array()
- vObj.push({name:"player" + _global.myID, val:_global.myName})
-
- smartfox.setRoomVariables(vObj)
This is where we notify our arrival to the room and this is also
the mechanism that will enable us to control the
game flow.
To see why, please check the onRoomVariablesUpdate
handler:
- smartfox.onRoomVariablesUpdate = function(roomObj)
- {
- if (inGame)
- {
- var rVars
= roomObj.variables
- if (rVars["player1"].length
> 0
&& rVars["player2"].length
> 0)
- {
- if (!_global.gameStarted)
- {
- _global.gameStarted
= true
- hideWindow("gameMessage")
- _root["player"
+ opponentID].name.text
= rVars["player"
+ opponentID]
- _global.whoseTurn
= 1
- waitMove()
- }
- }
- else
- {
- // Reset game status
- _global.gameStarted
= false
- resetGameBoard()
- moveCount =
0
- var win
= showWindow("gameMessage")
- win.message_txt.text
= "Waiting
for player " + ((_global.myID
== 1)
? "2"
: "1")
+ newline
+ newline
+ "press
[cancel] to leave the game"
- }
- }
- }
The code above handles three possible cases:
1) We are the first user to enter the room, so we need to wait
for our opponent
2) We are playing the game but our opponent leaves the room or gets
disconnected
3) The second player has entered and we can start the game.
If only one player variable exists in the room we will show a small
dialog box that will tell the user to wait his/her opponent.
This will also hanlde the case in which one of the two players exits
from the room during the game. Also the game board is cleared and
the moveCount is set back to zero.
If both player variables exist in the room we can set the _global.gameStarted
to true, remove the dialog box and initialize the game. Player one
will always move first in the first game, then the first move will
be done by the client who won the previous game.
The control is then passed to the waitMove() method:
- function waitMove()
- {
- var msg = (_global.whoseTurn == _global.myID) ? "It's your turn" : "It's your opponent turn"
- _root.turn.text = msg
- }
The simple code above shows the turn message based on the player
Id and then the application will wait for user interaction.
When a user clicks on one of the squares in the game board this
code is executed:
- on (release)
- {
- if (gameStarted)
- {
- if (_global.myID == _global.whoseTurn)
- {
- if (this.status != "R" && this.status != "G")
- {
- var stat
= (_global.myColor
== "red")
? "R"
: "G"
- this.status
= stat
- this.ball.gotoAndStop(_global.myColor)
- _root.moveDone(this)
- }
- }
- }
- }
-
We have three nested if(s) to see if the game is started, if it's
the user's turn and finally if the clicked item was not yet taken.
Each square has a "status" property that can have 3 different
values:
undefined : if never clicked before
"R" : if it contains a red ball
"G" : if it contains a green ball
If the board cell is free we set it to the user color and invoke
the moveDone() method in the main timeline passing
a reference
to the square movieclip.
The moveDone function uses the sendObject
API method to send the move data to our opponent:
- var x = tile._name.substr(3,1)
- var y = tile._name.substr(5,1)
-
- smartfox.sendObject({type:"move", x:x, y:y})
-
- moveCount++
-
- checkBoard()
- nextTurn()
-
The x and y vars are extracted from
the _name String property: as you may remember each
square has an instance name like "sq_x_y" By respectively
taking the 3rd and 5th char from the instance name we obtain the x
and y values.
These values are then passed to the sendObject
function together with a property called "type"
that can have two values:
"move", when we're sending a game board
move
"restart" when we're restarting the game
at the end of it.
After each move is peformed the checkBoard() function
will loop through the board to see if there's a winner and in such
case it will
stop the game showing the win/lose message. We'll take a closer
look to it later.
If no winner is found the nextTurn() function
is called:
- function nextTurn()
- {
- _global.whoseTurn = (_global.whoseTurn == _global.myID) ? ((_global.myID == 1) ? 2:1) : _global.myID
- waitMove()
- }
-
What this does is pretty strightforward: the whoseTurn
variable is inverted and then we go back to the waitMove()
At this point the game flow should be clear:
1) Wait for player move
2) Check if there's a winner
3) If no winner is found switch the active player and go back to
1 else the game is over
Now that we've seen how the player move is sent to the opponent
is time to check the code that handles the reception
of a move from the other player(s):
- smartfox.onObjectReceived = function(o)
- {
- if (inGame)
- {
- // we received a move from the opponent
- if (o.type == "move")
- {
- // Visualize opponent move
- var tile = "sq_" + o.x + "_" + o.y
- var ballColor = (_global.myID == 1) ? "red" : "green"
-
- board[tile].ball.gotoAndStop(ballColor)
- board[tile].status = ballColor.substr(0,1).toUpperCase()
-
- moveCount++
-
- checkBoard()
- nextTurn()
- }
- else if (o.type == "restart")
- {
- restartGame()
- }
- }
- }
If the "type" property is set to "move"
we have to display the opponent move in our board to keep all client's
boards in sync.
The "tile" variable represents the movieclip
name of the cell in the board where the client clicked and the "ballColor"
is found
by assigning the opposite color to the one we're using.
The status property is set by taking the first uppercase char in
the ballColor variable ("R" or "G")
then the moveCount is incremented
and the checkBoard() and nextTurn()
methods are called, just like we did previously.
You will be able to send a "restart"
command when the game finishes and you will be presented a dialog
box were you can return
in the main chat area or continue playing.
The restartGame() method will clear all current
game values and start a new game:
- function restartGame()
- {
- hideWindow("gameEnd")
-
- resetGameBoard()
- moveCount = 0
-
- _global.gameStarted = true
-
- nextTurn()
- }
Now that we've described the flow of the application, we can take
a closer look to the checkBoard() function:
- function checkBoard()
- {
-
- var solution
= []
- // All Rows
- for (var
i =
1; i
< 4;
i++)
- {
- solution.push(board["sq_1_"
+ i].status
+ board["sq_2_"
+ i].status
+ board["sq_3_"
+ i].status)
- }
- // All Columns
- for (var
i =
1; i
< 4;
i++)
- {
- solution.push(board["sq_"
+ i
+ "_1"].status
+ board["sq_"
+ i
+ "_2"].status
+ board["sq_"
+ i
+ "_3"].status)
- }
- // Diagonals
- solution.push(board["sq_1_1"].status
+ board["sq_2_2"].status
+ board["sq_3_3"].status)
- solution.push(board["sq_1_3"].status
+ board["sq_2_2"].status
+ board["sq_3_1"].status)
-
- var winner
= null
-
- for (var
i in
solution)
- {
- var st
= solution.pop()
- if (st
== "RRR")
- {
- winner =
"red"
- break
- }
- else if
(st
== "GGG")
- {
- winner =
"green"
- break
- }
- }
- // TIE !!!
- if (winner
== null
&& moveCount
== 9)
- {
- var win
= showWindow("gameEnd")
- opaqueCover._visible
= true
- win.message_txt.text
= "Tie !"
- }
- else if
(winner
!= null)
- {
- // There is a winner !
- _global.gameStarted
= false
- var win
= showWindow("gameEnd")
- opaqueCover._visible
= true
- if (_global.myColor
== winner)
- {
- // I WON! In the next match, it
will be my turn first
- var message
= "You
WIN !"
- _global.whoseTurn
= _global.myID
- }
- else
- {
- // I LOST! Next match i will not
move first
- var message
= "You
LOOSE !"
- _global.whoseTurn
= (_global.myID
== 1)
? 2
: 1
- }
- win.message_txt.text
= message
- }
-
- }
Even if there is a lot of code the function works in a very simple
way: it creates an empty array called "solutions"
and fills it with
all possible rows and columns where you can put three items in a
row.
The available solutions are 8 in total: 3 columns + 3 rows + 2 diagonals.
When the array is populated we loop through it and if one combinantion
of three is found then we have a winner! Also we check if
there's no more moves available. In that case we'll have a tie. When
the game ends the "gameEnd" window is shown and you will
be able to start a new game or just leave the room.
Leaving the room is done by calling the quitGame()
method which in turn will call the "leaveGameRoom"
function
- function leaveGameRoom()
- {
- inGame =
false
- gotoAndStop("chat")
- smartfox.getRoomList()
- }
The inGame flag is put back to "false"
and we're sent back to the chat label. The last line of code will
refresh the roomList in the current zone and automatically join
us in the main chat room called "The Entrance"
[ CONCLUSIONS ]
We have analyzed some of the techniques for building a simple multiplayer
turn-based game and what you have learned so far can be applied
to many different types of games, not just board ones.
Also the limit of two users in very game room can be expanded for
games with 4, 6, 8 or more players.
As usual we'd like to suggest you to analyze the code samples and
experiment your own variations, and then in a few time you'll be
able to create your own multiplayer games from scratch!
Have fun! :-)
Lapo
|