Handle invitation to GKTurnBasedMatch without game center view controllers - iphone

I am working on a simple turn-based word game, and I'm having a difficult time figuring out how invitations are working. When user A invites user B to a game, I want user B to be able to see this game in my custom match interface (ie a screen where all of user B's games are listed). Things are fine if user B clicks on the notification when he is invited to a game, but I still want the game to be visible through my own interface if user B just navigates to the app on his own.
Whenever this match interface screen appears, I am using [GKTurnBasedMatch loadMatchesWithCompletionHandler:] to get all of the local player's matches. With this, I would assume that user B would be able to see the match he has been invited to, but this new match doesn't show up. If I accept the invitation through the GKTurnBasedMatchmakerViewController, the user enters the game as expected, but what makes this even more puzzling is that if I open up my GKTurnBasedMatchmakerViewController and then cancel without accepting the invitation, a new match now shows up in my custom match interface, but it lacks the match data that was sent by user A.
To sum up, I am really just wondering how to properly display and accept GKTurnBasedMatch invitations with a custom interface (rather than having to use GKTurnBasedMatchmakerViewController). Thanks!

This question seems to be of interest to a couple people, so here's a rough approximation of what I ended up doing.
First of all, the reason new matches weren't showing up at all was just that I wasn't calling [GKTurnBasedMatch loadMatchesWithCompletionHandler:] every time I wanted the view to refresh like I though I was. So no real issue there as I recall.
The real issue was the case where I was receiving and displaying the new GKTurnBasedMatch, but none of the data (i.e. the opposing player's first move) was available. Essentially, [GKTurnBasedMatch loadMatchesWithCompletionHandler:] doesn't seem to guarantee providing you with the most up-to-date GKTurnBasedMatch objects available. To make sure the matches are up-to-date, I had to also call [match loadMatchDataWithCompletionHandler:], on each match returned by [GKTurnBasedMatch loadMatchesWithCompletionHandler:] (where match is one of those GKTurnBasedMatches). This returns the most current match data associated with that match as an NSData object. I was then able to use this NSData to make sure all the matches in my match table were refreshed to reflect the most recent changes in game center.
In short, use loadMatchDataWithCompletionHandler on your GKTurnBasedMatch objects to make sure their data is up to date.

UPDATE: now includes the actual answer for receiving invitations.
I have struggled with programmatically handling invites for turn-based matches for close to a week. I finally found the answer. I'm gonna super-highlight it because it took me so long to find:
Game Center treats turn-based invitations as turn events. They are not handled like other invitations.
Turn-based events are handled in this function in the GKLocalPlayerListener protocol:
player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch,
didBecomeActive: Bool)
When you recieve the match, check if you're invited to it, and presto. You've received an invitation.
BUT:
Through frustrating trial and error I have found some caveats, which hopefully can save you some serious time:
It just plain doesn't always work. Believe it or not, Game Center is unreliable. This means you need a back-up system that reviews your local player's open matches and searches them for new invitations. That itself pertains to caveat #2.
A player that is invited to a match (for instance say playerJake is invited to matchFoo) will not actually get that invitation nor see that match in the matches returned by loadMatches until it is their turn. Apparently Game Center does not actually involve any player on a match's invitation list in any way until it is their turn.
If you can identify a match you're invited to, but haven't responded to, you must call acceptInvite(...) directly on that match. So if playerJake inspected the matches retrieved by loadMatches, and was able to detect that matchFoo still had an open invitation, playerJake has to call matchFoo.acceptInvite( /* ...completion handler stuff here... */), and then happy playerJake is off and running.
From this you should be able to get your own programmatic matching system to work. Best of luck, and I mean it!

Related

How to reliably determine which match got created in response to GKTurnBasedMatchmakerViewController success

On iOS 10 I am trying to create a match using the GKTurnBasedMatchmakerViewController. I can bring up the view so the user can choose matchmaking or an invite. I can't figure out how to reliably determine which match got created in response to that. The problem is that, in iOS 10, the didFindMatch method of the GKTurnBasedMatchmakerViewControllerDelegate has been deprecated. That method used to be called with the match. The old days were easy!
The deprecation message says "use GKTurnBasedEventListener player:receivedTurnEventForMatch:didBecomeActive". The docs and stack overflow threads are filled with reasons why that method gets called. See this thread for a good list: Gamecenter ios 9 GameCenter GKLocalPlayerListener methods not called. If you imagine a user that already has several games going, you can see that this method will be called for a lot of different reasons and it could happen at any time, as far as I know. My question is: how to determine which of these calls is the "I just created a match for you, here it is!" call.
Some examples I think won't work:
Simply assuming the first call to receivedTurnEventForMatch that happens after you bring up the GKTurnBasedMatchmakerViewController seems wrong since receivedTurnEventForMatch could be notifying the user that it is their turn in another game. Unless the system guarantees that, while the view is up, they will only call this method with the match that corresponds to the view. That seems like a pretty big hack, so I'm assuming they don't do it.
A call to receivedTurnEventForMatch with the other player in "Matching" state and didBecomeActive=true seems to be a solution for the first player in a match, but not the second (since both players are done matching).
Looking at the MatchID and seeing if we've "seen it before", and if not, assuming it is the match that just got created seems unreliable since the user might have uninstalled the app and then reinstalled and we've forgotten all about which matches we've "seen before".
I'm stuck, any help is appreciated.
Double check your status when the event handler fires. I believe you should still be in invited state until you accept the match.
edit: Actually, I believe there will be two conditions that indicate a new match when the event handler fires:
You are in the invited state (you joined)
Everyone else is in the invited or automatch state (you started the match)
edit 2:
Checking my old code, it turns out that I looked at the matchData object. Since I knew that player 2 doesn't receive the invite until player1 ended the turn (and so forth), and since I knew that player1 had to save match data in order to end the turn, if matchData.length was greater than zero, I assumed I was joining a game in progress. I didn't rely on the participant status. But that doesn't solve your problem.
The other thing I did was create my own header struct which I inserted at the start of the NSData. In that header, player1 would set status flags for each other player, that would be obvious when those players joined. I totally ignored the participant status in the match object.
Regarding your third bullet, instead of saving the list locally, you could save it remotely using cloudKit. The cloudkit data will persist across installs/uninstalls unless you specifically delete it.
The gist with cloudkit is:
when you enable it in your app, your app gets a "container" with a public database that all users can share plus a private database unique to each app user.
You can create a record in the private database with a "Bytes" field
You can save a NSMutableArray directly into the aforementioned Bytes field
You can read back a NSMutableArray directly from the Bytes field

Post single notification with varying object types

I have a class that acts as a wrapper around AVPlayer, and one of the functions it serves is to post notifications every 1 and 10 seconds during playback (ie make addPeriodicTimeObserverForInterval: more convenient in the general case).
Previously, the object I was sending with this notification was the player wrapper itself (ie ABPlayer.sharedPlayer). Today I had the need to allow for some objects to only receive notifications about a specific media item's playback. This can be accomplished by sending [[someAVURLAsset URL] absoluteString] as the notification object (when the asset in the AVPlayer is an AVURLAsset, of course).
The prompted the question: is it appropriate for a single notification to, in different situations, post with different types of objects? I understand the value in sending specific objects or sending nil (catch-all), but I don't recall seeing a situation where an alternative type of object could be sent. In my case, though, it seems to make sense.
I could simply send two distinct notifications, but since these are always only ever being sent to notify observers of a single event, and they are always being sent from the same place in code, they simply feel like a single notification.
I realize what I have is possible and working, but I'm curious if there's a compelling reason to avoid this pattern.
As long as the scenarios in which the different object types will be sent to the observers are well understood and documented, there's no technical reason why you can't do it. It may make more contextual sense to post a different notification for each object type. It would certainly help any developers who may end up maintaining your code.

Game Center Simultaneous Turns

I'm new to iOS (although I do have a lot of C++ experience) and I am working on a turn-based card game using gamecenter. Here's the catch - The player who's turn it is is the judge and waits for all OTHER players to play a card before the turn is over. Is there a way to do this in Game Center, so all players chose what to do simultaneously and once all have done so, the judge is notified, makes a decision, and then passes priority to the next player? For example, say we start turn 1. I ask the question, #"What is your favorite color?" to all players passing them a gamestate which has that as the question and them not having answered it. Then you respond, #"Green", and our friend Steve responds #"Blue". I then decide which is better, give one of you a point, and then whoever I gave the point becomes the new judge. My question is, how do I allow all players to respond simultaneously, rather than sequentially. I know that, worst case, I could sequentially loop around through players until it comes back to me, then judge it, but this would slow down my game and make it less fun. Is there a way to do this simultaneously?
I agree with NSSplendid about the API for turnbased games requiring sequential turns. The only truly simultaneous method would be using the real-time matches from GKMatch, and that isn't really an option for games with more than a few players.
However, the sequential version could be improved slightly by using a programmatic approach to game center instead of the default view controller.
Ending a turn fires off a push notification through Game Center, and by using GKTurnBasedEventHandler's method handleTurnEventForMatch:didBecomeActive:, you can receive that in your app. When the judge asks the question, have the users display that as part of the game, and have their responses stored locally until their turn. Once it becomes a given player's turn, they receive the notification, even with the app in the background.
In the method, it can check the locally stored answer and end the turn immediately, if they've answered. If they haven't, send the turn once the answer is complete. It's not truly simultaneous, but the judge gets the answers as soon as everyone has responded, without the players having to wait for one player to finish before they can enter their own answers.
The players won't get the notification that the judge has ended their turn until they open the app, but they can't see the question anyways without doing that. Another approach to this, though slightly wasteful, is after the judge ends their turn with their question, is to do a runthrough of all the players ending their turn as soon as they get the "Your turn" notification, so everyone knows a question has been asked, then doing the steps from the previous two paragraphs.
The iOS API is built around the model of sequential turns. While the workaround you mentioned will work, there is no way to get GC to do real concurrent turns. Sorry ):

Handling Invitations for Programmatic Turn-Based Game

Thanks to the updates to GameKit API in iOS 6, I am finally able to implement my turn-based board game the way it should be, complete with turn timeouts and better programmatic creation of matches. However, I am running into an issue that I cannot seem to solve. My desire is to have Game Center running entirely invisible to the end-user, so that everything is programmatic and uses my own custom interfaces.
Therefore, I use my own custom table view to display matches, not the default GKTurnBasedMatchmakerViewController. Right now, I have no problem displaying open matches using the -loadMatchesWithCompletionHandler: method. I also use a custom screen to create a match, with a direct creation for auto-match (not a problem) and a table view that loads Game Center friends of the localPlayer for invitation. Since the playersToInvite attribute can now be filled with playerID's, this is possible in iOS 6.
My main problem is handling the invitation on the recipient's side. Lets say I invite Bob to play my game in a two-player match. Right now I can't seem to find a notification for a new invite on Bob's end. The -handleTurnEvent: only gets called for existing matches or if the banner notification is touched (which I can't guarantee the user will do), and -handleInviteFromGameCenter: does nothing for me in this case.
The only way I have come up with to detect new invites and thus update my custom game view controller is to call the -loadMatchesWithCompletionHandler: method and check for new matches in which lastTurnDate of the invited participant is nil and against an existing array of open matches. I run this check about every 10 seconds in the background since I can't find a notification in GKTurnBasedEventHandler that is called when a new invite is received. Please help!
EDIT: In the end, I have just implemented a pull-to-refresh functionality. There is no way without implementing polling or some other method that would just waste the user's data on their phone, so on demand refreshing is the most ideal solution in my opinion.
Please see this : GKInvite Reference and more specifically inviteHandler.
You just need to register an inviteHandler which will be called after Bob accepts the invite in GK/GC.
T.

iPhone GameCenter invitation with playerGroup

I developed an iPhone App that uses the GameCenter. To support different levels of my game I set the playerGroup within the GKMatchRequest class. If the match is started by an automatic match making process both users knows the playerGroup, but if the invitation process is used, the invitee does not know the playerGroup.
I tried to read the playerGroup within the method
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
by using
[viewController matchRequest].playerGroup, but the property playerGroup always return 0, instead the right player group.
Does anyone have an idea how to solve this problem. I need to know the playerGroup to load the right level.
Thank you very much for your help
As far as I know, this is a built in limitation of Game Center. I have been struggling with it too, and asked on several forums, including Apples devforums, without any response. I've written a bug report to Apple and I suggest you do the same.
As a workaround in my own game, where I have several different rulesets that players choose from and where players have to share the same rules for multiplayer matches to work, I put in a "double check" after the game actually starts to make sure everyone is on the same rules: Just in case someone was invited and is using the wrong rules.
What I did is, as soon as the game starts (which you should detect for in both the didFindMatch method of the GKMatchMakerViewControllerDelegate and in match:player:didChangeState: in GKMatchDelegate) I let the players enter the game and then send out packages containing the rules they use to all the other players. When they receive this data, they match it with their own rules and if anyone is using the wrong rules, the game will put up an alert and disconnect the match.
It's a bit ugly, and it's very clear that Apple ought to implement a way to set a GKMatchRequest for a GKMatchMakerViewController when initialized by an invite. But at least it works.
I stumpled upon the same problem. As a workaround I am sending a handshake with all relevant game customization info through the GKMatch object. I send the data with GKMatchSendDataReliable DataMode so the handshake doesn't fail.
After all information is collected by the clients the actual game logic is created and started on its basis.
You can check the Player Group of a match request like this.
func player(player: GKPlayer, didAcceptInvite inviteToAccept: GKInvite) {
if inviteToAccept.playerGroup == 0 || inviteToAccept.playerGroup == myPlayerGroup {
inviteAccepted()
}
else {
// TO SOMETHING
myPlayerGroup = inviteToAccept.playerGroup
inviteAccepted()
}