Overview
This time we are building a simple game. If you have licensed SmartFoxServer Lite, this tutorial will show you how to build a basic game without extensions, feature only available in SmartFoxServer Pro.

Bulls and Cows Login
We are making Bulls and Cows, a classic paper and pencil game where you try to guess a four letter string code previously written by your opponent and vice versa. The game ends when either code is discovered. To help you break the code, in each attempt you will get the number of Bulls and Cows. A bull is a letter in the right order in the code and a cow a letter in the code but not in the right order. Four bulls and you win.
Also, we are extending the default InputBox component to AutoClearInput that basically implements an specific behavior desired for the game. The text in the control will be removed the first time it gains focus.
Finally we have created a custom zone and room in the SmartFoxApplication/Server/config.xml configuration file and the code is listed at the end of the tutorial. To learn more about zones and how create them follow this link.

Bulls and Cows Game
The Code Explained
The import statements.
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.events.ValidationResultEvent;
import it.gotoandplay.smartfoxserver.SmartFoxClient;
import it.gotoandplay.smartfoxserver.SFSEvent;
import it.gotoandplay.smartfoxserver.data.Room;
import it.gotoandplay.smartfoxserver.data.User;
We declare the properties to store our secret code and username. We need to store the username because when you exit the game you are logged out and we can’t use smartFox.myUserName anymore.
The rest of the code is the usual connect, login and room join schema.
private var smartFox:SmartFoxClient;
private var myUsername:String;
private var mySecretCode:String;
private function init(e:Event):void
{
connect();
}
private function connect():void
{
smartFox = new SmartFoxClient(true);
smartFox.addEventListener(SFSEvent.onConnection, smartFox_onConnection);
smartFox.addEventListener(SFSEvent.onLogin, smartFox_onLogin);
smartFox.addEventListener(SFSEvent.onRoomListUpdate, smartFox_onRoomListUpdate);
smartFox.addEventListener(SFSEvent.onJoinRoom, smartFox_onJoinRoom);
smartFox.addEventListener(SFSEvent.onJoinRoomError, smartFox_onJoinRoomError);
smartFox.addEventListener(SFSEvent.onPrivateMessage, smartFox_onPrivateMessage);
smartFox.connect("localhost");
}
private function smartFox_onConnection(e:SFSEvent):void
{
if (!e.params.success)
{
trace("The connection failed. " + e.params.error);
}
}
private function smartFox_onLogin(e:SFSEvent):void
{
if (!e.params.success)
{
Alert.show("Error: " + e.params.error);
}
}
private function smartFox_onRoomListUpdate(e:SFSEvent):void
{
smartFox.autoJoin();
}
This code handles a user joining the room. There is no need to check if the room is full because as previously mentioned, the zone and the room are custom made for this application and there is a limit of 2 users top. If the room is full and you try to login a joinRoomError event will trigger.
We are creating a room variable to store the current user id. Note the name of the variable uses the player id which is an incremental integer (1, 2 ,3 …) only available in game rooms. Room variables are accessible to all users in the room. This way we are able to retrieve the user id using the player id. The advantage is the ease of calculating the next player id based in the current player id, nextId = playerId % playersCount + 1. This technique works for a game of any count of players.
private function smartFox_onPrivateMessage(e:SFSEvent):void
{
if (e.params.sender.getId() == smartFox.myUserId)
{
return;
}
if (e.params.message == "$init")
{
currentState = "playState";
clearLists();
myCodeLabel.htmlText = "Shhh, your code is <b>" + mySecretCode + "</b>.";
}
else if (e.params.message == "$next")
{
guessButton.enabled = true;
whoseTurnLabel.htmlText = "It's <b>your</b> turn.";
sendMessage("$wait", getNextPlayerIndex());
}
else if (e.params.message == "$wait")
{
myCodeLabel.htmlText = "Shhh, your code is <b>" + mySecretCode + "</b>.";
guessButton.enabled = false;
whoseTurnLabel.htmlText = "It's <b>" + e.params.sender.getName() + "'s</b> turn.";
}
else if (e.params.message.substr(0,6) == "$guess")
{
var guess:String = e.params.message.substr(6);
var bulls:int = 0;
var cows:int = 0;
for (var i:int = 0; i < 4; i++)
{
guess.charAt(i) == mySecretCode.charAt(i) ? bulls++ : null;
}
for (i = 0; i < 4; i++)
{
for (var j:int = 0; j < 4; j++)
{
guess.charAt(i) == mySecretCode.charAt(j) ? cows++ : null;
}
if (cows == 4) break;
}
cows = cows - bulls; //don't count bulls as cows
var result:String = guess + " | " + bulls.toString() + "B | " + cows.toString() + "C";
if (playerGuessList.dataProvider == null)
{
playerGuessList.dataProvider = [];
}
playerGuessList.dataProvider.addItem({label: result});
sendMessage("$result" + result, getNextPlayerIndex());
if (bulls == 4)
{
sendMessage("$win", getNextPlayerIndex());
}
}
else if (e.params.message.substr(0,7) == "$result")
{
if (myGuessList.dataProvider == null)
{
myGuessList.dataProvider = [];
}
myGuessList.dataProvider.addItem({label: e.params.message.substr(7)})
}
else if (e.params.message == "$win")
{
currentState = "winState";
sendMessage("$lose", getNextPlayerIndex());
smartFox.logout();
}
else if (e.params.message == "$lose")
{
currentState = "loseState";
smartFox.logout();
}
}
This code handles the onJoinRoomError event and the basic input from the user. The guess and play button click events. In the guessButton_click handler we are implementing the core of the turn based functionality. We are sending application messages to the next player. Basically this code moves the turn around.
Finally note that when we exit we deliberately log the user out and change the state. This design choice allow us to reuse the onJoinRoom code when the users attempts replaying or enter coming from the wait state if the room was full.
private function smartFox_onJoinRoomError(e:SFSEvent):void
{
currentState = "roomFullState";
}
private function guessButton_click(e:MouseEvent):void
{
sendMessage("$guess" + guessText.text, getNextPlayerIndex());
sendMessage("$next", getNextPlayerIndex());
guessText.text = "";
}
private function playButton_click(e:MouseEvent):void
{
if (validateCode(codeText.text))
{
mySecretCode = codeText.text;
myUsername = usernameText.text;
smartFox.login("simpleGame", usernameText.text, "");
}
}
private function tryAgainButton_click(e:MouseEvent):void
{
smartFox.logout();
smartFox.login("simpleGame", myUsername, "");
}
private function exitButton_click(e:MouseEvent):void
{
smartFox.logout();
currentState = "";
}
Here is the rule to validate codes. The digits can’t repeat and for this implementation of the game we are enforcing using 4 digits exclusively.
private function validateCode(code:String):Boolean
{
if (code.length != 4)
{
return false;
}
var charmap:ArrayCollection = new ArrayCollection();
for (var i:int; i < code.length; i++)
{
if (charmap.contains(code.charAt(i)))
{
return false;
}
else
{
charmap.addItem(code.charAt(i));
}
}
return true;
}
Clear the lists items safely.
private function clearLists():void
{
if (myGuessList.dataProvider != null)
{
myGuessList.dataProvider.removeAll();
}
if (playerGuessList.dataProvider != null)
{
playerGuessList.dataProvider.removeAll();
}
}
The following are helper functions used throughout the application to handle sending private messages, retrieving the next player id in turn, …
private function sendMessage(message:String, playerIndex:int):void
{
smartFox.sendPrivateMessage(message, getUserFromPlayerIndex(playerIndex).getId());
}
private function getPlayer(index:int):String
{
return "Player" + index.toString();
}
private function getNextPlayerIndex():int
{
return smartFox.playerId % 2 + 1;
}
private function getUserFromPlayerIndex(index:int):User
{
var room:Room = smartFox.getActiveRoom();
return room.getUser( room.getVariable( getPlayer(index) ));
}
Click here to download the application. You are downloading a ZIP file that contains the MXML for the BullsAndCows game and the AutoClearInput extended InputBox.
Creating the Zone
This is the code for the custom zone and room the game is based on. Note that among other things autoJoin is true because there is only one room in the zone and isGame is also true because we need the player id feature enabled. Finally note that the maximum number of spectators is set but the application doesn’t implement any functionality for spectators currently.
<Zone name="simpleGame">
<Rooms>
<Room name="default" autoJoin="true" isGame="true" maxUsers="2" maxSpectators="10" />
</Rooms>
</Zone>