With Firebase, security rules and transactions would be the key to an elegant solution.
If you're willing to set up a node.js script or other server-side worker, this is fairly straightforward. Players would write to a "lobby" when they want a match. The server script would perform the matches and write back the "game room" they are going to join. The structure would be basically thus:
/games/$game_id/users/user1/<user_id>
/games/$game_id/users/user2/<user_id>
/lobby/$user_id/false (an unmatched user)
/lobby/$user_id/$game_id (a matched user)
Now clients would simply write to the lobby when they want to join a game, and then wait for the server to assign them a game id:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby/' + <my user id>);
lobbyRef.set(false); // I want to join a game
lobbyRef.on('value', function(snap) {
if( snap.val() !== null ) {
console.log('I joined a game!', snap.val());
}
});
The server is nearly as simple. Assuming node.js:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
var gamesRef = ref.child('games');
var player1 = null;
// listen for requests to join
lobbyRef.on('child_added', function(snap) {
// assign as player1 if nobody is waiting
if( !player1 ) {
player1 = snap.ref();
}
// if someone is already waiting, assign both players a room
else {
var player2 = snap.ref();
var gameRef = gamesRef.push({
players: {
player1: player1.key(),
player2: snap.key()
}
}, function(err) {
if( err ) { throw err; } // a bug
// let the players know they have been matched and which room to join
player1.set(gameRef.key());
player2.set(gameRef.key());
});
}
});
Obviously there is some work to make all this fault tolerant and security rules would be needed to prevent cheating.
Doing this entirely on the client is slightly more involved, but certainly manageable.
Have each player attempt to match themselves to anybody in the lobby. If nobody is in the lobby, then wait there. This is done with a transaction to prevent conflicts.
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
function waitInLobby() {
lobbyRef.once('value', lobbyUpdated);
}
function lobbyUpdated(snap) {
if( snap.val() === null ) {
// lobby is empty, so join and wait
var ref = lobbyRef.child('<my user id>').push(false);
ref.on('value', someoneMatchedMe);
function someoneMatchedMe(snap) {
if( snap.val() !== false ) {
console.log('I got matched in room', snap.val());
ref.off('value', someoneMatchedMe); // stop monitoring
snap.ref().remove(); // leave the lobby
}
}
}
else {
// lobby is not empty, so try to match someone
var possibles = [];
snap.forEach(function(ss) {
possibles.push(ss.ref());
});
}
}
function matchUser(possibles) {
if( !possibles.length ) { waitInLobby(); }
var opponentRef = lobbyRef.child(possibles.shift());
opponentRef.transaction(function(currentVal) {
if( currentVal !== false ) {
// already claimed, start over
matchUser(possibles);
}
});
}
Some security rules would be critical here, in addition to the transactions. There is also plenty of room for optimization, but at the point that you're optimizing for production, that's a job for your engineering team, rather than a Stack Overflow contributor.