In the last SFE update post I described an approach to choosing eggs by a voting system rather than “a pseudorandom progression for the sequence of eggs”; however, as every developer should know, random number generators are almost all pseudorandom. If each client starts with the same seed and only uses the random number generator for generating new egg colours, all peers in the game should agree as to the order of eggs.
The real problem is that the rand() function might be called for egg generation and for other things, meaning the generators will be out of step. I’ve stripped out egg voting and replaced it with the MTRandom library, which wraps the Mersenne Twister random number generation algorithm in a class. All peers vote for the random seed at the start of each game (and each round) and the vote from the peer with the highest ID is chosen. The egg factory sets up an internal MTRandom instance and the egg sequences are synchronised automatically with no further network traffic.
The next problem to solve was the issue of minor timing differences between peers throwing out synchronisation. Suppose an egg lands on one player’s screen. The next egg appears at the top of the grid, and the other player throws across some garbage eggs. Those garbage eggs will have to wait until the current eggs land before they can be added to the grid. Meanwhile, on the networked view of that player on another iPad, the other player throws across some garbage before the egg lands. Network lag meant that the “drop egg” message was delayed by a fraction of a second. The egg lands, the garbage appears, and the two representations of that player’s grid disagree entirely about the current state of the game.
My fix for this was to hold up the appearance of the next egg until all of the networked representations of a grid are ready. Once the current egg pair lands the grid sends a “waiting” message and enters a wait state, in which they can receive garbage eggs, until all peers announce that they are waiting. At that point they all add new eggs and switch back to the active state.
The final problem is rather more tricky: sometimes egg movement messages can be processed out of sequence. All of the egg movement messages are sent by the player’s grid instance. Moving, rotating or dropping an egg results in a message being sent to all peers in the network. Once received, all of the messages are dispatched to the “network controller” instance (where they are queued in order) except for the “egg drop” message, which is sent directly to the appropriate grid. This means that an “egg drop” message could be sent after a “rotate” message, but would end up being processed before the “rotate” message. To fix this I’ll either have to send the “egg drop” message to the controller and treat it as a “down” button press, or I’ll have to scrap the entire concept of a networked controller and create a duplicate grid class that excises most of the game logic and relies on network messages instead. I’m hoping that the controller option works.
The to-do list isn’t growing any shorter. I still need to:
- Create a peer chooser (though it’s possible that I might be able to use an Apple-provided class to do that for me);
- Provide a way to quit the current game when paused;
- Replace the “press a key” bitmap with a “tap to continue” bitmap;
- Figure out why taps on the “press a key” screen are intermittently detected;
- Quit the game automatically when a networked peer disconnects.
Once all of that’s done I’ll sign up for the iOS App Store and get a release out. Next steps will be to merge the changes into the OSX version of the game (to allow for Mac/iPad networked games) and increase the number of players that can participate in a game from 2 to 8.