I'm really hoping I'm wrong about this, but from what I can tell, GameKit hosted matchmaking simply does not work if a player invites some friends into a match but leaves some slots open for automatches. I can get each case to work individually, but when mixed, no one can connect.
Consider three players: A, B, and C. A invites B into the match and leaves a third slot open. C chooses to be matched into a three player game.
Now, when B processes the invite from A (by virtue of having registered an inviteHandler on GKMatchmaker), he contacts the hosting server to connect and sets his player ready state. Player A will then get a delegate message to matchmakerViewController:didReceiveAcceptFromHostedPlayer: saying that B has connected. In a two player game, B could at this point start the game and proceed. Instead, clicking the "Play Now" button causes the match-maker to wait for a third player to arrive.
Meanwhile Player C has chosen to be added to a match automatically. If A and B had also asked to be added automatically, all three would then be notified of the match via the delegate matchmakerViewController:didFindPlayers: method. Instead, however, clicking the "Play Now" button causes the match-maker to wait... forever. Player A is also waiting... forever. Player B knows only about the inviter, player A, so he could in theory try to start the game, but it would be pointless since A only knows about some of the participants.
It's hard for me to believe the system could have such a catastrophic flaw, but I've run this scenario, and traced all the delegate methods exposed in the relevant GameKit classes, and when the players get into their infinite-waiting state there are no delegate messages sent. It's as if GameCenter does not fully know about the existence of A and B, which would help explain why A has to explicitly mark B as present in the view controller despite obviously having been visible "enough" to have received the invite in the first place. I've also tried having player C request only a two-player game, on the idea that perhaps this meant that Player A counted as only one "visible" player.
Does anyone have any direct experience with this API, and can claim explicitly that it works (or doesn't) for this use case? This is a serious issue for me; this pretty much means that I have to throw away my GameKit-based matchmaking code and write something completely from scratch.
Thanks in advance for any information.
Related
I'm totally lost with how to implement a turn-based game. I've been trying to use the GKLocalPlayerListener methods to handle turn-based in iOS9. The only method that ever fires is receivedTurnEventForMatch, which leaves me with no method that I know of that calls an end game routine for each player. I am trying to handle turn-based matches inside of my app using the Game Center match maker view controller and delegate methods. I read that GKLocalPlayerListener methods work for matches when going through the actual Game Center app (Apple docs don't mention this). So if that's true, then GKLocalPlayerListener is not an option for my app?
What can I do to detect when a match ends? I want to keep a win-lose record for each player, so it's important that a routine is called for each player when a match ends.
The lifecycle of a turn based match looks like this:
Create a match
Invite others
Others join
Players take turns and pass the match object around (your game logic decides the order)
Players send exchanges back and forth (optional)
Players start leaving
----because they have been eliminated
----because they quit
----because they timed out
Someone Wins
If you are not the active player, you are notified when steps 3, 4, 7, 8 and 9 happen by playerReceivedTurnEventForMatch firing; however, as you can see from my answer here https://stackoverflow.com/a/34458493/1641444 playerReceivedTurnEventForMatch fires for a lot of different conditions, and it doesn't tell you which one triggered it. You have to discern that based on the status of the players, which player is the active player, and whatever other information you track in the match object.
You are notified of #5 by playerReceivedExchangeRequest firing (and Replies and Cancellation functions).
Your game logic will decide when to trigger #7. My preference is that the match object comes to the eliminated player, they are recognized as defeated, and calls participantQuitInTurnWithOutcome
Players decide when to trigger #8 by quitting, and the code calls either participantQuitInTurnWithOutcome or participantQuitOutOfTurnWithOutcome depending on their state.
Condition #9 is a real pain in the ass. Between limitations in the game design and outright bugs, timeouts can create several unrecoverable edge cases. Handling timeouts warrants its own full answer.
Finally, #10 is triggered by calling endMatchInTurnWithMatchData. GKTurnBasedEventHandlerDelegate and handleMatchEnded were deprecated in IOS7. When using GKLocalPlayerListner, you'll be notified of #10 by yet another occurrence of playerReceivedTurnEventForMatch
Edit--Clarifications based on followup questions:
Yeah, exactly. In your sendTurn function, when the game is over, don't call endTurnWithNextParticipant. Instead, set the each participant's status to indicate who won and who lost, and then call endMatchInTurnWithMatchData.
The other players will see playerReceivedTurnEventForMatch fire. You can discern from the match status and the player status that the game is over and decide what actions to take for that recipient.
To end a match, you should have your game call endMatchInTurnWithMatchData:completionHandler:
If you've implemented GKTurnBasedEventHandlerDelegate protocol on an object, your handleMatchEnded will get a push event.
More details in Apple's programming guide
Let's say I have a turn based match with two players. At some point player 1 recognizes that he is about to lose the game. When it is Player 1's turn, he uses Game Center App to do a swipe to remove the match.
Issues:
A. Take turn timer never expires on Player 1. So the match's turn will not switch to Player 2 when the time expired.
B. The game also offers a view only mode so players can view the game progress while he is out of turn. But since no status was updated to indicate that Player 1 had removed the match manually. App can offers no resolution. Also, you can only end match while it is your turn.
Ideally, I want to declare Player 2 as a winner and end the match.
How do you handle in this situation?
I finally found a workaround for this.
If you delete a match, then call GKTurnBasedMatch:loadMatchesWithCompletionHandler, the deleted match doesn't appear (as expected). However, it turns out that you can still re-download the deleted match using GKTurnBasedMatch:LoadMatchWithID, if you happen to still have the deleted match's ID.
I think we can reasonably assume that The Cheater is going to play the game again; otherwise, why would they care about incurring a loss? Therefore, I implemented the following:
Maintain a table locally, on the device, of matches.
On startup, pull the list of local player's matches from Game Center and compare against my local list.
When The Cheater recognizes the situation and deletes the match using the Game Center interface, the match is removed from Game Center, but not from my local DB. When The Cheater starts my game again, I see that they have more matches locally than on Game Center.
I then call either participantQuitInTurnWithOutcome or participantQuitOutOfTurnWithOutcome, as appropriate, with an outcome of GKTurnBasedMatchOutcomeLost.
This passes the turn to the next player and records a loss for The Cheater. It won't work if the cheater never plays the game again, though. (But, if they're not playing, they're not wrecking any more matches, so the chaos is contained)
I am using Game Center turn-based matches for my card game. It feels like a good fit because people want to check their email or write a text sometimes between turns. Some users have said that they do really want to be able to have a very asynchronous playing experience.
For the people that are keeping the game open between their turns, I want to update the screen to reflect things that the other players are doing on their turns. I have set up a listener on GKLocalPlayer that responds to player:receivedTurnEventForMatch:didBecomeActive. The documentation says that this will get called when match data is saved by another player even if it doesn't become the player's turn (the player on the current device). That doesn't appear to be true 100% of the time. In fact, it appears that it only gets called about 1 in 3 times that match data is saved by other players. It seems more reliable when it becomes the player's turn, but even that isn't 100% reliable.
I am using saveCurrentTurnWithMatchData:completionHandler: on GKTurnBasedMatch to save match data that doesn't end the current player's turn and I'm calling endTurnWithNextParticipants:turnTimeout:matchData:completionHandler: on GKTurnBasedMatch to save the data when it does end the current player's turn. There are a few scenarios where I want to call saveCurrentTurn… with updated matchData. In my game, you can have computers playing in your multiplayer game as well. So, a human player may play a card and then a computer may play a card before that GKPlayer's turn ends. There are also scenarios where an individual player may play twice. (eg. A player plays the last card on a trick. That player takes the trick and gets to lead the next trick.)
I have set up a ton of logging around this and I can see clear scenarios where one device calls saveCurrentTurn… and the completionHandler is called without an error and the other device doesn't get notified with a call to player:receivedTurnEvent… I have also added logging to verify that each time I'm calling saveCurrentTurn… that I'm calling it with new matchData. I'm not making redundant calls.
If I go to the device that didn't get the updated matchData and force it to load the matchData for the match again, it gets the updated data. So, it's definitely getting saved.
I have tried throttling the calls to saveCurrentTurn… so that they don't happen in immediate succession and that didn't help.
Both devices in my testing are running iOS 8.4. There appears to have been an issue in iOS 8.3 that is fixed now (see this question). This Apple forum post also reports this issue 2 years ago and it appears that bug reports were filed and marked fixed.
Has anyone else seen this? I would love to know that I'm doing something wrong. Any ideas are very welcome.
I do something similar. In my game, each player has multiple pieces, saving the match when each piece moves so that other players--if they're in the game--can watch what's happening in real time. Like you describe, the Game Center messaging is almost completely useless.
As you referenced, in 8.3, the "end of turn" messages were completely broken. As of 8.4, they happen most, but not all, of the time. As you're seeing, the "match has been saved" notifications are also erratic. Here are some tips I've used to increase the success rate:
Slow down the saves. If you save too fast, only the last one arrives at the recipient. I set up an NSArray queue, and each time I want to save the match, I add the new matchData to that queue. I have a timer loop running that does the actual saveCurrentTurnWithMatchData, pops the item off the stack if the save was successful, and then sets up a new timer to call itself again a little later. I'm using 2 second intervals which seems to be working well.
Append each new piece of data, don't overwrite. Put a sequence number on each piece of data. So, if you save seq numbers 1, 2, 3 and 4, but the recipient only receives a notice for #4, the records for 1, 2 and 3 are there in the match object. The recipient needs to track the last record it read, and then iterate through any new records from that point when it receives an updated matchData.
I also use the queue's NSArray writeToFile: function to maintain a list of the pending saves. If the user exits the game before the queue is flushed, I reload the queue NSArray from disk at the next startup
Note that even with this mechanism, the notifications to the recipient are erratic. Generally speaking, they arrive in batches of 4+. Then nothing happens until 3 or 4 more saves happen, which again all show up together. Making 1 save and letting the game sit for 10 minutes will probably never generate a notice on the recipient's machine. But, if you save 4 or 6 times in a row, all of them tend to show up in a burst.
Sometimes, the notifications just stop for a few hours. Not sure if this a sandbox flaw or a game-center-in-general flaw. There are no failures of any kind, the messages just stop working for a while. Sometimes, the next morning, they show up in a burst. Sometimes not. In the end, I've stopped relying on the notifications. I set up another timer loop to continuously download the match. It checks if it has become my turn or not, it checks if new updates have been added to the matchData. Then calls player:receivedTurnEventForMatch:didBecomeActive. As far a receivedTurnEventForMatch: knows, it was launched because of an event and it merrily goes on about its business.
It does seem that saving the match is pretty prompt. If you don't get an error, it seems pretty certain that the updated match is immediately available for other players to consume... they just need to know to consume it. The messaging framework, though, has to be viewed as completely unreliable and non-guaranteed. Hence, the timer loop to continuously poll the match.
Edit: arguably, once I implemented #2, #1 shouldn't really matter. Any notification received by the recipient will trigger reading all new records in the data. But, this "hardening" has evolved over the past few months as I wrestle with Game Center's shortcomings. I just haven't gotten around to removing #1.
I'm new to Swift and native iOS Development in general, and I'm trying to making an app which allow two or more users to send and receive in real time data about something (such as a line path), similarly to what happen in multiplayer games. It isn't a game, by the way. Let's make an example:
Let A and B be two users.
There is a canvas on this app, for example.
A can draw a line (which should be an array of points) and B see what A is drawing in real time (so B receive the array).
As I said, it's similar to what happen in multiplayer games (but here I don't want a server to manage what people do, I'd like something like P2P or a direct connection between devices).
How should I manage this?
we are working on a game with real-time matches. 2 players. 1 against 1.
I have to define a list of items in common between the 2 players before the game start.
It's not really a problem in the case of 1 player invite another. In this case i can define the player receiving the invite as the "client". The app of the "server" player will generate the list.
But in the case of an "auto-match" not sure how i can define this kind of relationship between my players as the same code is going to run the same way on the two instance of the apps.
Edit:
Will try this idea:
Will use a timeelapsed between beginning of the connection and the accepted connection. Exchange the data between users and determine which one should be consider as master/server.
Will post a detail example if the implementation working.
Meanwhile, still opened to any others suggestion.
In peer-peer networking, each device can function as the server. Make your networking client/server agnostic once the connection is made so that it doesn't matter.
I had that same problem and ended up defining my own network classes on top of AsyncSocket so that I could communicate in a middle layer. It's here if you're interested:
github link here
It suited my needs and it may give you some ideas.