The solution I've found to this is to use some message system (Redis pub/sub in my case) to keep each player instance aware of the current status.
Each player has a worker instance which handles his own turn (this includes the timer). When it finishes, either via a move by the player or via the timeout, it advances the turn counter and informs all instances via the pub/sub with the new turn number. All of the instances receive the message and compares the turn number with its own player number. If it matches, then the instance handles the turn and the cycle repeats.
I'll try to provide an example (more of a pseudocode):
// pub & sub are Redis publisher & subscriber clients, respectively
function Game (totalPlayers, playerNumber) {
this.turn = 0
this.totalPlayers = totalPlayers
this.playerNumber = playerNumber
// Subscribe to Redis events
sub.on('message', function (channel, message) {
message = JSON.parse(message)
switch(message.type) {
case 'turn':
this.onTurn(message.turn)
}
})
sub.subscribe(this.channel, function() {
this.checkStart()
})
}
Game.prototype.checkStart = function () {
// This checks if this instance is for
// the last player and, if so, starts the
// main loop:
if(this.playerNumber == this.totalPlayers - 1) {
pub.publish(this.channel, JSON.stringify({type: 'turn', turn: 0})
}
}
Game.prototype.onTurn = function(turn) {
this.turn = turn
if(this.turn == this.playerNumber) {
this.timer = setTimeout(this.endTurn.bind(this), this.turnTime)
}
}
Game.prototype.endTurn = function() {
this.turn = (this.turn + 1) % this.totalPlayers
pub.publish(this.channel, JSON.stringify({type: 'turn', turn: this.turn})
}
I had some problems with this approach and the main problem was the initial status, which wasn't quite right if the players connected almost at the same time. It's also a good idea to send information and make sure all instances are in sync.
I hope that I made this clear if someone is running into the same problem.