Syncronizing a card game

Started by
3 comments, last by Headkaze 12 years, 5 months ago
I am writing a card game and wanted to add a Co-op mode so multiple players can play the same game together.

So far I have determined who will be the server and who are the clients. Currently touch movements are sent directly to the others and then each game registers the touch movement onscreen for the player.

For the most part it works but there are the occasional syncronization problems (which I was expecting) in that you can grab the same card at the same time resulting in both players being able to move the card around independently. Also I've seen the game go out of sync, where a card has been placed on two separate piles.

Since I wanted to have this game in realtime so you can watch the other player move cards around I didn't want to make it turn based.

So I just need to know what would be the easiest way to make sure the games' are in sync. Do I need to sync the games' on a per-frame bases? If so how does the server keep the clients in sync? Does it send some sort of "timer" packet?

Since animation is based on elapsedTime do I need to send this value from the server to the clients and then have all animation run based from the server's delta time? If the game runs at 60 FPS will lag be a problem over bluetooth, wifi or internet?

So I just need to get a basic understanding on how to keep the games' in sync. I have all the communication working so far using struct's called Packet's and all the low level communication is handled by a library.
Advertisement

For the most part it works but there are the occasional syncronization problems (which I was expecting) in that you can grab the same card at the same time resulting in both players being able to move the card around independently. Also I've seen the game go out of sync, where a card has been placed on two separate piles.


You should probably separate the protocol into multiple parts. One part for UI, and one part for resolution.
You also need to make the UI understand about being denied operations it's already trying to do.

I would build it something like:

When the user grabs a card, start sending "user X is moving card Y from position V" messages to the server, and let the user drag the card around.
When the server gets the first "grab" message for a card, it sends out "user X is moving card Y" to all clients, assuming "position V" agrees with the server state.
When a subsequent "grab" message comes in for the same card from user Z, the server sends out "user Z is denied moving the card."
When a client is showing dragging cards, and gets a "you were denied" message, simply stop letting the user move that card.

Finally, when trying to drop a card, you should send "user X dropped card Y at position W from position V" to the server. The server will only allow this message if user X was actually the first mover/winner of the card movement race.
In this case, send "user X moved card Y to position W from position V" to all clients.
If the user was the loser ("Z" in the above case), send "user Z was denied moving card Y."
Finally, don't let the client show the drop highlight feedback, or send the "user X dropped card Y" message, until you've gotten a "user X is moving card Y" message back from the server.

Now, you have a system where the UI is responsive, but some users are rolled back/denied when a clash happens. The game should then stay in sync.

You probably should draw out all the different possible states and transitions as a state diagram -- with three different clients, and the server, as separate state domains.
Additionally, you have to figure out what you WANT to happen if user A starts moving card X, and user B starts moving card Y, and they both want to drop the card on position W -- either don't allow two cards to move at the same time, or make the second "dropper" be denied if he started dragging before seeing the user A action resolved.
enum Bool { True, False, FileNotFound };
Since multiplayer was not a part of the original design of the game I'm hoping I can "tack it on" without too many changes to the underlying engine.

The main input methods of the game which also call into the main gameplay mechanics are the following.

void TouchDown(int playerId, float x, float y);
void TouchMove(int playerId, float x, float y);
void TouchUp(int playerId, float x, float y);

Currently I send in movements from other clients directly into these methods. Each card selection and movement is managed for each playerId.

The problem is there is currently no feedback from these methods if a card selection is valid or a way to cancel a movement. It really just blindly processes the input for each user.

So rather than have to re-design things I was hoping I could just make sure the inputs are syncronised across all clients to prevent de-sync. The actual logic of dealing with potential conflicting moves between multiple players can be handled ingame. So what I need is a robust way to sync the inputs between players without having to check valid moves first.

I've been looking at multiplayer MAME solutions like Kaillera and CSMame. Kaillera works by the server processing all input and appending it together before sending it back out to the clients. CSMame deals with desync issues by sending over the entire compressed game state every so often.

My main issues are about the input and animation being in sync. I can make sure that each client receives input in the same order as every other client by letting the server manage it. But since all the animation is based on elapsedTime I can see a potential problem of de-sync unless all games are perfectly synced on a per-frame bases.

So I guess my question is, how should I manage the main game loop in a networked situation? Should I have a HeartBeat packet that sends out the server's timestamp and all clients animate based on that? The idea of elapsedTime is so animation will be smooth if there is some framerate instability. But I guess if you're playing over a network lag is when a peer needs to wait for another peer so perhaps clients could use the same elapsedTime as the server and have their main Update loop in sync with it. Is it crazy to think about having the two games sync on a per frame bases over a network?

Since multiplayer was not a part of the original design of the game I'm hoping I can "tack it on" without too many changes to the underlying engine.


I don't think that's realistic, at least if you want a good user experience.

If you have no choice, then I would suggest maybe tying into the save/load game function, and send full snapshots of the game state from the server each time the game state changes on the server. This means that someone who is in the middle of moving a card that someone else moved and committed, will at least get "snapped" to the proper state once it's distributed by the server.
enum Bool { True, False, FileNotFound };
Thanks for your replies hplus0603. I'll briefly go over how I ended up dealing with the issues of adding co-op to my card game. Feel free to comment.

- All Touch* events are sent from the clients to the server and then sent back out to all clients.
- Everytime a move is made the Move data (which is a struct used for undo/redo/loading/saving) is sent from the client to the server.
- Each Move has a CRC which is checked against the server's Move stack. If they don't match a de-sync has been detected.
- If a de-sync is detected the moves following the crc mismatch are sent from the server to the client. When the client receives the moves it undoes the moves to that point and then redoes the moves sent from the server.
- Every half a second a "heartbeat" packet is sent from clients->server and server->clients. The heartbeat packet syncs the game clock and score. It also has a value of all the Move CRCs totalled. This is a fallback check and if this fails to match all the moves are sent from server to client and replayed.

So far it appears to be working but I have to force a fake de-sync to actually test it. During my testing a de-sync has never naturally occured. This is probably because it uses bluetooth / wifi for connection. It will not support play over the internet anyway so it appears a de-sync will be a rare occurrence.

This topic is closed to new replies.

Advertisement