Tutorials/simpletournament

= Tic-tac-toe Tournament Tutorial =

Introduction
In this tutorial, we will create a very basic tic-tac-toe tournament. It will be a sit&go1 heads-up2 tournament for four players.

In a Rush?
As always, if you are in a rush, just get the finished project and start playing around with it. You need the game module, the tournament module and the client. Then:
 * 1) Unzip the tournament module, run.
 * 2) Unzip the game module, run.
 * 3) Then unzip the client, open   in four tabs.
 * 4) Log in (with playerId as password) on each tab and register to the tournament, which will then start.

The tournament module is already tucked into  for your convenience, but if you make changes to it, you'll need to copy the   file to that location.

But please note that building tournaments are an inherently complex endeavor. Going through this sample might take you an hour or so, but will be time well invested. Many people (of average intelligence or higher) have tried to create tournaments and failed. Using the tournament support in Firebase, your chances of succeeding are much higher.

Here's a screenshot of the client showing the tournament lobby:



Creating the Tournament Archive
If you want to do this from scratch, start with executing: mvn archetype:generate -DarchetypeGroupId=com.cubeia.firebase.tools -DarchetypeArtifactId=firebase-tournament-archetype -DarchetypeVersion=1.8.0 -DarchetypeRepository=http://m2.cubeia.com/nexus/content/groups/public From the terminal, answer the questions and you're good to go.

Flow of a Tournament
Before we start, let's look at what steps are normally required for a tournament to run and which component is responsible for each step.


 * 1) Create tournaments - done via   from.
 * 2) Open registrations phase - game dependent, typically done by setting.
 * 3) Handle registrations - done in   (called before registration is complete) and   (called after registration is complete).
 * 4) Create tables - done via.
 * 5) Seat players - done via.
 * 6) Start game rounds at tables - done via.
 * 7) Send round reports to tournament from tables - done via.
 * 8) Handle round reports - done via removing players who are out and balancing tables: ,   and.
 * 9) Finish and destroy tournament - done via   in the.

The Tournament Activator
The archetype came with two files for you, the  and the. The activator is responsible for creating and destroying tournaments, so let's start there. To create a tournament, you call  from your. The  will be handed to you via   before the activator is started, so store it into an instance variable. The  method is a good place to actually create the tournaments. Here's what it might look like: public void start { factory.createMtt(1, "tic-tac-toe-tournament-1", new TicTacToeCreationParticipant); factory.createMtt(1, "tic-tac-toe-tournament-2", new TicTacToeCreationParticipant); factory.createMtt(1, "tic-tac-toe-tournament-3", new TicTacToeCreationParticipant); } This will create three tournaments. The first parameter is the  which should correspond to the   in the   file under. The second parameter is the name of the tournament and the third is a creation participant which will be called when the tournament is created and is then responsible for setting up further details, such as the lobby path and capacity of the tournament.

So, create a new file called  and implement it like so: import com.cubeia.firebase.api.lobby.LobbyAttributeAccessor; import com.cubeia.firebase.api.lobby.LobbyPath; import com.cubeia.firebase.api.lobby.LobbyPathType; import com.cubeia.firebase.api.mtt.MTTState; import com.cubeia.firebase.api.mtt.activator.CreationParticipant; import com.cubeia.firebase.api.mtt.support.MTTStateSupport; import org.apache.log4j.Logger;

public class TicTacToeCreationParticipant implements CreationParticipant {

private static final Logger log = Logger.getLogger(TicTacToeCreationParticipant.class);

@Override public LobbyPath getLobbyPathForTournament(MTTState mtt) { LobbyPath lobbyPath = new LobbyPath(LobbyPathType.MTT, 1, "mtt", mtt.getId); log.info("Returning lobby path: " + lobbyPath); return lobbyPath; }

@Override public void tournamentCreated(MTTState mtt, LobbyAttributeAccessor acc) { log.info("Tournament created " + mtt.getName + " " + mtt.getId); MTTStateSupport state = (MTTStateSupport) mtt; state.setCapacity(4); state.setState(new TicTacTournamentState); } } So, what this does is just returning a default LobbyPath when that's requested and then once the tournament is created, we set the  of the tournament to 4 players. We also initialize the domain specific tournament state. This class is very simple and will be listed later.

At this point you'll want to add some dependencies to your  file: log4j log4j 1.2.14            provided com.google.guava guava 10.0            com.google.code.gson gson 2.1

We also need to talk a bit about that downcast of  to. is an interface that needs to be implemented by the tournament. To make things easier, Firebase comes with a default implementation called. When Firebase creates the tournament, it will use this implementation, so we can safely downcast the state. It is possible to provide your own implementation of, but that's a fair amount of work and will not be covered in this tutorial.

Handling Registrations
The next step is to handle incoming player registrations to the tournaments. If this were a scheduled tournament, we would probably write some code to figure out when the tournament should open for registrations, given the start time. But now we are doing a sit&go, so registrations will be welcome from the beginning. When a player sends a registration request (MttRegisterRequestPacket), first the  method will be called. If this method returns, Firebase will register the player and call. In this sample, we will keep the code easy, but in a real system, you would probably withdraw some funds from the player's account in the  method. If this fails, you would instead respond with, for example. Putting it all together, our simple example implements the two interfaces in one class and looks like this: import com.cubeia.firebase.api.mtt.MTTState; import com.cubeia.firebase.api.mtt.MttInstance; import com.cubeia.firebase.api.mtt.model.MttRegisterResponse; import com.cubeia.firebase.api.mtt.model.MttRegistrationRequest; import com.cubeia.firebase.api.mtt.support.MTTStateSupport; import com.cubeia.firebase.api.mtt.support.registry.PlayerInterceptor; import com.cubeia.firebase.api.mtt.support.registry.PlayerListener; import org.apache.log4j.Logger;

public class TicTacToeRegistrationHandler implements PlayerListener, PlayerInterceptor {

private static final Logger log = Logger.getLogger(TicTacToeRegistrationHandler.class);

@Override public void playerRegistered(MttInstance instance, MttRegistrationRequest mttRegistrationRequest) { log.info("Player registered. Number of registered players: " + instance.getState.getRegisteredPlayersCount); if (instance.getState.getRegisteredPlayersCount == 4) { startTournament(instance); }   }

private void startTournament(MttInstance instance) { log.info("instance.id: " + instance.getId + " instance.state.id: " + instance.getState.getId); instance.getTableCreator.createTables(888, instance.getId, 3, 2, "tic-tac-toe-tournament-table", null); }

@Override public void playerUnregistered(MttInstance mttInstance, int i) { log.info("Player unregistered. Number of registered players: " + mttInstance.getState.getRegisteredPlayersCount); }

@Override public MttRegisterResponse register(MttInstance mttInstance, MttRegistrationRequest mttRegistrationRequest) { log.info("Registering player " + mttRegistrationRequest.getPlayer.getPlayerId + " to tournament " + mttInstance.getId); return MttRegisterResponse.ALLOWED; }

@Override public MttRegisterResponse unregister(MttInstance mttInstance, int playerId) { log.info("Unregistering player " + playerId + " from tournament " + mttInstance.getId); return MttRegisterResponse.ALLOWED; } } You'll also want to go to the  class and edit these two methods to look like this: public PlayerInterceptor getPlayerInterceptor(MTTStateSupport state) { return new TicTacToeRegistrationHandler; }

public PlayerListener getPlayerListener(MTTStateSupport state) { return new TicTacToeRegistrationHandler; }

Delayed Registrations
In the case of scheduled tournaments, you'll probably want to prevent registrations until the registration phase begins. To do this, start by setting a LobbyAttribute called  (or whatever key you think is appropriate) to   (or again, whatever you think feels right). Do this in the. Now, when  is called, you check the   like this: if (!"REGISTERING".equals(instance.getLobbyAccessor.getAttribute("status").getStringValue)) { return MttRegisterResponse.DENIED; } Of course that can be de-uglified by using enums, but you get the point. Note that this is just an example, you don't need to put this code into the tic-tac-toe tournament.

Starting the Tournament
Now it's time to start the tournament! In our case, we will do that as soon as we have 4 players registered. There are two steps to it, you create the required tables and then you seat the players at those tables.

If you scroll back up to  you'll see that I already snuck the code in there. The vital part is: private void startTournament(MttInstance instance) { instance.getTableCreator.createTables(888, instance.getId, 3, 2, "tic-tac-toe-tournament-table", null); } Here, 888 is the gameId of our game, in this case tic-tac-toe, 3 is the number of tables to create, 2 is the number of seats at each table, "tic-tac-toe-tournament-table" is the base name of the tables and we don't need to pass any data to the tables so the last parameter is null. In a more complex tournament, we might want to initialize the tables with some configurations, which we could pass as an  via the last parameter.

Now, the tables will be created asynchronously and you will receive a callback when tables are created. There's a bit of a caveat in that you might get several callbacks, so you need to check if all the tables you requested are created before you move on. Here's what our method looks like: public void process(MttTablesCreatedAction action, MttInstance instance) { MTTStateSupport state = getState(instance); log.info("Tournament tables created for tournament: " + instance.getId + " We now have: " + state.getTables.size + " tables."); if (state.getTables.size == 3) { int[] tables = Ints.toArray(state.getTables); TicTacTournamentState ticState = (TicTacTournamentState) state.getState; ticState.setFinalTableId(tables[2]);

seatPlayers(state, tables); startTables(state, state.getTables); }   } So, we check if all table have been created and if so, we seat the players and tell the tables to start.

You might notice that we created 3 tables for our 4 players, which is one table more than we need. I did this so that we have the final table ready when we need it, in order to make the code simpler when we are moving players to the final table. Otherwise we would have to create the table and then wait for the callback, which would make the method above a bit messier. Obviously, there are many (possibly better) ways to skin this cat. We take note of the  for the last table and store that in our domain specific tournament state class, which looks like this: import java.io.Serializable;

public class TicTacTournamentState implements Serializable {

private int finalTableId;

public void setFinalTableId(int tableId) { finalTableId = tableId; }

public int getFinalTableId { return finalTableId; } }

Now, the  method is an existing method in , but the   method needs to be defined: private void seatPlayers(MTTStateSupport state, int[] tables) { List shuffledPlayers = new ArrayList(state.getPlayerRegistry.getPlayers); Collections.shuffle(shuffledPlayers);

/*        * In this tournament, we have hard coded the number of players to be 4. Two matches are played and the * winner from each match will play each other in the final. */       Collection seating = new ArrayList; for (int i = 0; i < shuffledPlayers.size; i++) { MttPlayer player = shuffledPlayers.get(i); SeatingContainer container = new SeatingContainer(player.getPlayerId, tables[i / 2]); log.info("Seating player " + container.getPlayerId + " at table " + container.getTableId); seating.add(container); }       seatPlayers(state, seating); } As you can see we first shuffle the players and then place the first two players at the first table and the last two players at the second table.

Making the Game Support Tournaments
Now we will move from the tournament module to the game module. If you haven't done so already, please follow the game tutorial or just download the TODO:finished project. Then, you need to implement the following interfaces:
 * 1)   - let the   implement this.
 * 2)   - let the   class implement this.
 * 3)   - let the   class implement this.

Starting Rounds
In the  method we saw earlier, we are sending a "start" signal to all the tables. So what we need to do on the game side is to let the  class implement the   interface. Our implementation of these methods looks like this: @Override public void startRound(Table table) { log.info("Starting round " + table.getId + " in 3 seconds.."); GameObjectAction action = new GameObjectAction(table.getId); action.setAttachment("start"); table.getScheduler.scheduleAction(action, 3000); }

@Override public void stopRound(Table table) {

} In our case, we are ignoring calls for stopping the round, because the tournament will never do that. Please note here that the intention is that the same game can run as an individual game or as part of a tournament. One key difference is that in the case of a tournament, the game needs to wait for the tournament to tell it to start a round and should not start rounds on its own initiative. In the tic-tac-toe sample, we start the game when the second player sits down, in the  method. This method is not invoked in the case of a tournament, so we don't need to change it. However, we should make that class (called ) implement   as well. We actually won't do anything in any of those methods, so just go ahead and let your IDE fill in default implementations.

The other place where we tell the game to start is after it has finished (in the  method) and we need to change that. The new method will look like: private void progress(Table table, Board board, Winner winner, int playerId) { GameData data; if (winner == NONE) { data = createGameData(board, "act"); data.pid = board.getPlayerToAct; } else if (winner == Winner.TIE) { data = createGameData(board, "tie"); scheduleNewGame(table); } else { data = createGameData(board, "win"); data.pid = playerId; if (isTournamentTable(table)) { sendRoundReport(table, playerId); } else { scheduleNewGame(table); }       }        notifyAllPlayers(table, data); }

protected boolean isTournamentTable(Table table) { return table.getMetaData.getType == TableType.MULTI_TABLE_TOURNAMENT; } Above I also included the  method.

Sending Round Reports
As you can see, if the game is part of a tournament, we will send a round report instead of scheduling a new game when it has finished. A round report tells the tournament that a round has finished. In our case, we don't need to tell the tournament when there's a draw, in that case we just keep on playing until eventually we get a winner. However, in a poker tournament you usually send a round report after each hand, because the tournament might want to balance the tables by moving a player from your table to a table with fewer players, and between hands is a good time for doing that. The round report can contain arbitrary data, and in our case all we need to do is tell the tournament who won the game: private void sendRoundReport(Table table, int winner) { MttRoundReportAction roundReport = new MttRoundReportAction(table.getMetaData.getMttId, table.getId); roundReport.setAttachment(winner); log.info("Sending round report where winner is " + winner + " mttId is " + roundReport.getMttId + " and tableId is " + roundReport.getTableId); table.getTournamentNotifier.sendToTournament(roundReport); }

Believe it or not, but these are all the changes we need to do on the game side. Let's jump back to the tournament and handle those round reports.

= Handling Round Reports = So, now we are back in the tournament module. Open the  class and implement the   method like so: public void process(MttRoundReportAction action, MttInstance instance) { log.info("Received round report. Attachment: " + action.getAttachment); MTTStateSupport state = getState(instance);

// Get the winner and the loser. int winnerId = (Integer) action.getAttachment; int loserId = getLoserId(action.getTableId, winnerId, state);

// Unseat the players. unseatPlayers(state, action.getTableId, Arrays.asList(loserId), Reason.OUT); unseatPlayers(state, action.getTableId, Arrays.asList(winnerId), Reason.BALANCING);

// Remove the loser from the tournament and inform him state.getPlayerRegistry.removePlayer(loserId); sendAction("tournament-lose", instance, loserId);

// If there's only one player left, we have a winner! if (state.getPlayerRegistry.size == 1) { sendAction("tournament-win", instance, winnerId); instance.getLobbyAccessor.setAttribute("status", AttributeValue.wrap("finished")); } else { // Seat the winner at the final table. TicTacTournamentState ticState = (TicTacTournamentState) state.getState; int finalTableId = ticState.getFinalTableId; SeatingContainer container = new SeatingContainer(winnerId, finalTableId); seatPlayers(state, asList(container)); log.info("Players at final table: " + state.getPlayersAtTable(finalTableId).size); if (state.getPlayersAtTable(finalTableId).size == 2) { sendRoundStartActionToTable(state, finalTableId); }       }    }

private int getLoserId(int tableId, int winnerId, MTTStateSupport state) { Collection playersAtTable = state.getPlayersAtTable(tableId); playersAtTable.remove(winnerId); return playersAtTable.iterator.next; }

private void sendAction(String action, MttInstance instance, int playerId) { instance.getMttNotifier.notifyPlayer(playerId, createAction(instance, playerId, new TournamentData(action))); }

private MttDataAction createAction(MttInstance tournament, int playerId, TournamentData data) { MttDataAction action = new MttDataAction(tournament.getId, playerId); Gson gson = new Gson; String json = gson.toJson(data); action.setData(wrap(json.getBytes)); return action; } There's a bit more to chew here, but it should be fairly straightforward. The one trick is how we get a hold of the final table. Again, we could've opted to create the final table on the fly, but since table creations are asynchronous, we would then have to move the seating of players to the table created method.

You'll need the  class as well. It looks like this: public class TournamentData { String action;

public TournamentData(String action) { this.action = action; }

public String getAction { return action; }

public void setAction(String action) { this.action = action; } }

Destroying the Tournament
The final step is of course to destroy the tournament. If you read the  method carefully, you noticed that we did something when the tournament is finished: instance.getLobbyAccessor.setAttribute("status", AttributeValue.wrap("finished")); This is a way of telling the activator that the tournament can safely be removed. On the activator side, we'll need to check the the tournaments regularly, so add this in the start method: You'll also want to add a  instance variable and let the   implement. The  method looks like this: @Override public void run { log.info("Checking tournaments."); MttLobbyObject[] mttLobbyObjects = factory.listTournamentInstances; for (MttLobbyObject tournament : mttLobbyObjects) { AttributeValue status = tournament.getAttributes.get("status"); if (status != null && "finished".equals(status.getStringValue)) { log.info("Found finished tournament, destroying.."); factory.destroyMtt(888, tournament.getTournamentId); }       }    } Note that the first parameter to the   method is the gameId of the game that this tournament was playing. This is used to shut down any remaining tables.

In a more realistic scenario, you'll probably want to schedule these things. When a tournament ends, the winner typically wants to sit at the table and gloat for a bit, so it might be mean to shut down the table immediately. Also, players might want to log in just to see who won the tournament, and it won't show up in the lobby if it's been destroyed, so at least for bigger tournaments it's common to let it stick around for a number of hours or even days.

Finally, we'll shut down the executor in the  method: public void stop { scheduledExecutorService.shutdown; } This concludes what we need to do on the server side. But we still can't play any tournaments unless we have a client that supports it. So, we'll move on to the client side.

Client Side Changes
All that remains to do now is to implement the changes on the client side. We will take the tic-tac-toe JavaScript client and modify it to support tournaments. It turns out that most of the work is around showing the "tournament lobby", meaning the list of tournaments and creating the register / unregister feature.

If you haven't already, head over to the tic-tac-toe JavaScript client tutorial and grab the code. We will now modify it for our needs.

Subscribing to Tournaments
The first thing to do is to subscribe to tournaments instead of tables. Change the  function like so: function loginCallback(status, playerId, name) { console.log("Login status " + status + " playerId " + playerId + " name " + name); myPlayerId = playerId; message("Login " + status + ' '); if (status === 'OK') { console.log('Login OK, subscribing to lobby.'); createGrid; message("Subscribing to tournament lobby ");

// Subscribe to tournaments.. var subscribeRequest = new FB_PROTOCOL.LobbySubscribePacket; subscribeRequest.type = FB_PROTOCOL.LobbyTypeEnum.MTT; subscribeRequest.gameid = 1; subscribeRequest.address = "/"; connector.sendProtocolObject(subscribeRequest) } } This creates a subscription request subscribing to the entire tree of tournaments (the "/" means the root of the lobby tree). The  1 corresponds to the   we chose back at the beginning of this tutorial.

Now, we need to handle the tournament lobby packets. Change the  function to: function lobbyCallback(protocolObject) { console.log("Received lobby packet: " + protocolObject + " classid: " + protocolObject.classId); switch (protocolObject.classId) { case FB_PROTOCOL.TournamentSnapshotListPacket.CLASSID: console.log("Handle tournament snapshot list"); handleTournamentSnapshotList(protocolObject.snapshots); updateRegisterButtons(registeredTournaments); break; case FB_PROTOCOL.TournamentUpdateListPacket.CLASSID: console.log("Handle tournament update list"); handleTournamentUpdateList(protocolObject.updates); break; } } A tournament snapshot list packet contains a list of tournament snapshot and a tournament update list is a list of tournament updates. The difference between a snapshot and an update is that a snapshots contains all data about an tournament while a snapshot only contains delta changes.

Handling Tournament Lobby Data
We handle the snapshots and updates like this: function handleTournamentSnapshotList(tournamentSnapshotList) { for (var i = 0; i < tournamentSnapshotList.length; i++) { handleTournamentSnapshot(tournamentSnapshotList[i]); }   jQuery("#lobbyList").trigger("reloadGrid"); }

function handleTournamentSnapshot(tournamentSnapshot) { var params = tournamentSnapshot.params; var name = getStringParam('NAME', params); var registered = getIntParam('REGISTERED', params); var capacity = getIntParam('CAPACITY', params); var i = lobbyData.push({id:tournamentSnapshot.mttid, name:name, capacity:capacity, registered:registered}); jQuery("#lobbyList").jqGrid('addRowData', tournamentSnapshot.mttid, lobbyData[i - 1]); }

function handleTournamentUpdateList(updates) { console.log("Handling tournament update list.") for (var i = 0; i < updates.length; i++) { var update = updates[i]; var params = update.params; var tournament = findTournament(update.mttid); console.log("Updating tournament " + update.mttid + " params: " + params); tournament.registered = getIntParam('REGISTERED', params); console.log("Updating registered for " + tournament.id + " to: " + tournament.registered); jQuery("#lobbyList").jqGrid('setRowData', tournament.id, {registered:tournament.registered}); } } This is of course a bit flawed, since we only care about one possible delta change, namely the number of registered players. In this tutorial, we know that's all that's going to change, so it's OK. Fetching the parameter values from these packets is a bit tricky, since they are base64 encoded, so here's the code for doing that: function getStringParam(key, params) { return getParam(key, params, "string"); }

function getIntParam(key, params) { return getParam(key, params, "int"); }

function getParam(key, params, type) { for (var i = 0; i < params.length; i++) { var param = params[i]; if (param.key == key) { var byteArray = FIREBASE.ByteArray.fromBase64String(param.value); var value; if (type == "string") { value = utf8.fromByteArray(byteArray); }           else if (type == "int") { value = intFromBytes(byteArray); }           console.log("returning value " + value + " for param " + key); return value; }   }    console.log("returning null for param " + key); return null; }

function intFromBytes(x) { var val = 0; for (var i = 0; i < x.length; ++i) { val += x[i]; if (i < x.length - 1) { val = val << 8; }   }    return val; }

Showing the Tournament Lobby
OK, so we can now get the tournament list, but we need to display it as well. Change the  functions like so: function createGrid { $("#lobbyList").jqGrid({       datatype:"local",        data:lobbyData,        height:204,        colNames:['Name', 'Capacity', 'Registered', ''],        colModel:[            {name:'name', index:'name', width:250, sorttype:"string"},            {name:'capacity', index:'capacity', width:110, sorttype:"int"},            {name:'registered', index:'registered', width:110, sorttype:"int"},            {name:'act', index:'act', width:100}        ],        caption:"Lobby",        scroll:true,        multiselect:false,        gridComplete:function  {            var ids = jQuery("#lobbyList").jqGrid('getDataIDs');            for (var i = 0; i < ids.length; i++) {                var mttId = ids[i];                registerButton = "<input class='ui-button' type='button' value='Register' onclick='register(" + mttId + ");'/>";                jQuery("#lobbyList").jqGrid('setRowData', ids[i], {act:registerButton}); }

},       cellSelect:function  { }

});   console.debug("grid created"); }

Handling Registrations and Unregistrations
The  function above refers to a function that will send a registration request. Here's the code for registration and unregistration: function register(mttId) { console.log("Registering to tournament " + mttId); message("Registering to tournament " + mttId); var registerRequest = new FB_PROTOCOL.MttRegisterRequestPacket; registerRequest.mttid = mttId; connector.sendProtocolObject(registerRequest); }

function unregister(mttId) { console.log("Unregistering from tournament " + mttId); message("Unregistering from tournament " + mttId); var unregisterRequest = new FB_PROTOCOL.MttUnregisterRequestPacket; unregisterRequest.mttid = mttId; connector.sendProtocolObject(unregisterRequest); }

Now, we need to listen to the registration responses, so we can update the "Register" button to say "Unregister" correctly. In your  function, add this after the last case: case FB_PROTOCOL.MttRegisterResponsePacket.CLASSID: console.log("Registration response status: " + packet.status); handleRegisterResponse(packet); break; case FB_PROTOCOL.MttUnregisterResponsePacket.CLASSID: console.log("Unregistration response status: " + packet.status); handleUnregisterResponse(packet); break; case FB_PROTOCOL.NotifyRegisteredPacket.CLASSID: console.log("Notify Registered"); registeredTournaments = registeredTournaments.concat(packet.tournaments); console.log("Registered tournaments: " + registeredTournaments); break; The  packet is received when you log in and tells you which tournaments you are already registered to. The corresponding handle functions you need are: function handleRegisterResponse(packet) { if (packet.status == "OK") { registeredTournaments.push(packet.mttid); updateRegisterButtons(registeredTournaments); } }

function handleUnregisterResponse(packet) { if (packet.status == "OK") { removeRegisteredTournament(packet.mttid); updateRegisterButtons(registeredTournaments); } }

function updateRegisterButtons(registeredTournaments) { console.log("Updating register buttons."); var ids = jQuery("#lobbyList").jqGrid('getDataIDs'); for (var i = 0; i < ids.length; i++) { var mttId = ids[i]; var tournamentData = findTournament(mttId); if (tournamentData) { var registerButton; if (registeredTournaments.indexOf(parseInt(mttId)) != -1) { registerButton = "<input class='ui-button' type='button' value='Unregister' onclick='unregister(" + mttId + ");'/>"; } else { registerButton = "<input class='ui-button' type='button' value='Register' onclick='register(" + mttId + ");'/>"; }           tournamentData.act = registerButton; jQuery("#lobbyList").jqGrid('setRowData', mttId, {act:registerButton}); }   } }

function findTournament(tournamentId) { for (var i = 0; i < lobbyData.length; i++) { var object = lobbyData[i]; if (object.id == tournamentId) { return object; }   }    return null; }

Opening the Tournament Table
OK, so now we need to make sure that we will open a table when the tournament starts. Do this by adding another case in the above  function: case FB_PROTOCOL.MttSeatedPacket.CLASSID: console.log("MttSeatedPacket."); handleMttSeated(packet); break;

And then:

function handleMttSeated(packet) { console.log("I've been seated at table " + packet.tableid + " in tournament " + packet.mttid); connector.joinTable(packet.tableid, -1); myTableId = packet.tableid; $('#lobby').hide; $('#table').show; }

Handling Tournament Messages
The last thing to do is to react when the tournament tells us that we won or lost the tournament. This means we must handle the : case FB_PROTOCOL.MttTransportPacket.CLASSID: console.log("Received mtt data."); handleMttTransport(packet); break; Followed by: function handleMttTransport(packet) { var byteArray = FIREBASE.ByteArray.fromBase64String(packet.mttdata); var message = utf8.fromByteArray(byteArray); gameMessage("Received " + message + " from mtt " + packet.mttid)

handleTournamentData(jQuery.parseJSON(message)); }

function handleTournamentData(json) { if (json.action == "tournament-lose") { alert("You are out of the tournament!"); } else if (json.action == "tournament-win") { alert("You won the tournament. Congratulations!"); } }

Running the Tournament
In order to test this out, first do  in the tournament module. Now open the game POM file and add the following dependency:

com.cubeia.tutorial.tictactoe.tournament</groupId> tournament-tutorial</artifactId> 1.0-SNAPSHOT firebase-tar provided

Now start Firebase by running  in the game-module (you just made the game module depends on the tournament module, so both the gar and the tar file will be placed in Firebase' deploy folder). Then open the index.html file in the client and log in with four players (using an integer as the password, which will be the playerId).

Again, you can download the game module here, the tournament module here and the client here.

Conclusion and Known Limitations
This marks the end of probably the longest tutorial on Firebase. If you followed it all the way through, I congratulate you! :) Feel free to contact us if you have any questions.

When you get this running, you'll notice that there are a couple of things missing, mostly on the client side:
 * 1) You can't go back to the lobby once you are at a table
 * 2) When your table is closed, you won't know and won't get back to the lobby
 * 3) The register/unregister button should be disabled once the tournament is running

These are minor things that are left for the reader as an exercise.. :)

Good luck!

Footnotes 1A sit&go is a tournament that starts as soon as there are enough players. 2A heads-up tournament means that players are playing one versus one. For more on this see Wikipedia's article on single-elimination tournaments.