Real Time Game Center game disconnects (possibly because of network timeout) - iphone

So, I'm writing a very simple real time game center 2-player game; however, the problem is I keep getting disconnected.
The game works as follows: Each player has a text field on their device. They each enter text in the field and press enter. When both people have inputted text, the game progresses.
Now, when the users are actively playing the game (inputting text every 10 seconds or so), the game works just fine and a user has never been disconnected. However, when the game remains inactive (the user just sits and stares at the app screen) for about 30 seconds or more, at least one player gets disconnected.
I'm pretty confident that my Internet is solid and both devices appear to be connected to the Internet (via wifi).
I know this is a very vague question, I was just wondering if anyone has any ideas related to the symptoms in bold above.
EDIT:
Here's how I initialize the matchrequest and the match. However, I'm having no trouble initializing or starting the match. The only problem is when a player goes idle for some length of time
//toInvite may be nil
- (void) createMatchWithPlayersToInvite: (NSArray *) toInvite
{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = toInvite;
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithMatchRequest:request];
self.myMatchmakerVC = mmvc;
mmvc.hosted = NO;
mmvc.matchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];
}
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match
{
[self dismissViewControllerAnimated:YES completion:nil];
self.myMatch = match;
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.currentMatch = self.myMatch;
if (!self.matchStarted && match.expectedPlayerCount == 0)
{
self.matchStarted = YES;
[self performSegueWithIdentifier:#"gameSegue" sender:self];
}
}
EDIT 2:
So, I've discovered that if I set a timer and send messages across the network (with [self.myMatch sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error]) every 1 second, the program works just fine. Does anyone have idea why this is or how I can fix my issue without resorting to a hacked together NSTimer?
Other notes:
1) My AppDelegate has not been changed

Looks like you have a clear hypothesis here, i.e. that Game Center connections are closed after 30 seconds idle time. I would not be surprised if this is the case. Other games almost certainly will send data within such an interval, and you may be triggering a timeout condition. To test this hypothesis and fix the problem at the same time, I would send a short blind text every five or ten seconds.

Related

Game Center can't find existing turn-based matches

I'm writing a turn-based iPhone game and I can't seem to find my own games.
I have three accounts trying to matchmake in my game - one on my iPhone 5, and two different Game Center test accounts created in the iPhone simulator which I switch between all playing my game through the Game Center sandbox. Unfortunately, they never find eachother and always create new games on their own. How can I fix it so they will ALWAYS find an existing match, if there is one available, and only create a new game if there are NO open games?
I'm assuming you are using GKTurnBasedMatchmakerViewController to find matches and doing something similar to this:
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
GKTurnBasedMatchmakerViewController *mmvc = [[GKTurnBasedMatchmakerViewController alloc] initWithMatchRequest:request];
mmvc.turnBasedMatchmakerDelegate = self;
[self presentViewController:mmvc animated:YES completion:nil];
In your delegate method you can do this to figure out if this was a brand new game or if you're joining an existing game:
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match {
int numberOfParticipants = [match.participants count];
// check the number of valid participant ids to find out if you need to know if this is a "new" game or not.
....
For match making to pair up a user with an existing game, the opponent player must have "completed" their turn by calling this method on GKTurnedBasedMatch:
- (void)endTurnWithNextParticipants:(NSArray *)nextParticipants turnTimeout:(NSTimeInterval)timeout matchData:(NSData*)matchData completionHandler:(void(^)(NSError *error))completionHandler;

iPhone - Online Multiplayer game ... understanding the mechanics

I am developing an online multiplayer game, but I am struggling with Apple documentation. (I have tried these tutorials by Ray Wenderlichs Part 1 and part 2, but they are not working (match never starts because inviting device never receives the match acceptance).
As this topic is vast, I will be creating a single question, then moving to create another question on SO if necessary.
I want to create an online multiplayer game that will let a user to invite from 1 to 3 people. So, it would be a 2 to 4 people match. The game is not turned based. It is live and the data to be transferred between users is minimum.
Lets start with the basic stuff.
1) the first thing I do is create a notification
if (self.gameCenterAvailable) {
NSNotificationCenter *nc =
[NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(authenticationChanged)
name:GKPlayerAuthenticationDidChangeNotificationName
object:nil];
}
to let me know when the notification changes. When this happens, authenticationChanged method will fire... here it is
- (void)authenticationChanged {
GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert application-specific code here to clean up any games in progress.
if (acceptedInvite)
{
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
}
else if (playersToInvite)
{
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 4;
request.playersToInvite = playersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
}
};
}
I grabbed this code from Apple. My question here is this. If Apple say to run this code after the user is authenticated why it is checking for invitation or users to invite? As far as I see, users were not invited yet. Unless the code is not executed at that time, right? It will just sit in memory waiting to be called, WHEN the invitation is done, correct?
If this is the case, I now create an invitation for a match doing
[self dismissModalViewControllerAnimated:NO];
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
request.playersToInvite = self.pendingPlayersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[self presentModalViewController:mmvc animated:YES];
A window will be present to all users I choose to invite. Suppose the first one taps ACCEPT on the invitation. Which method will be fired on my app, how do I get the user identity and how do I know if all users accepted?
thanks.
First of all please be aware that invitations on the Sandbox enviroment tend to work erratically, so I suggest you start by having all players search for an available match. The code would be something like this:
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error || !match) {
// handle the error
}
else if (match != nil){
// match found
}}];
}
Then you have to implement the protocol GKMatchDelegate. There's a method there that will be invoked for each player that joins the match and for each player that gets disconnected from it (on this method you can find out the user identity with its playerID):
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
switch (state){
case GKPlayerStateConnected:
if (match.expectedPlayerCount == 0) {
// start the match
}
break;
case GKPlayerStateDisconnected:
// remove the player from the match, notify other players, etc
break;
}
}
Regarding your first question, the code on authenticationChanged is only registering the handlers for those methods, meaning the code that will be invoked when the notifications arrive.
EDIT: Regarding the question on your comment, if the match was started by invitations, the user that started the match has to wait until all invitations are accepted, or cancel some of them and then press Start Match (or something like that) on the Invite Screen. In this scenario the match.expectedPlayerCount == 0 condition will be satisfied once all the players that accepted the invites are connected to the match.
If the match was started by AutoMatch then Game Center does the following: once it finds minPlayers waiting to start a match it will assign them to a match and then wait a few more seconds to see if it can fill the remaining slots. At some point it will start the match with a certain number of players between minPlayers and maxPlayers. Then the condition match.expectedPlayerCount == 0 will be satisfied only once all the players have effectively join the match, but note that when the decision was taken to start a match the number of players expected for that match is already determined by Game Center.

Detect when GameCenter UI is displayed

I'm trying to integrate my game with Game Center and encountered this problem:
When user is authenticated for a first time, Game Center shows its UI for setting up the profile.
My problem is that I can not detect when this windows is shown - I want to pause my game at that moment and not play any sounds.
viewWillDisapper, viewDidDisapper in UIViewController are not called, neither are any of AppDelegate methods are called at this time.
I think I know how detect alert views (using changing key window notification), but that Account windows still is not detected there.
Is there any way to do this?
Building on executor21's answer here, I put this together which seems to do the trick in early testing. You can probably adapt it into something less fragile. It is built on the premise that the Game Center notification gets its own window, and it has exactly one subview of type GKGameEventView:
+(BOOL)isGameCenterNotificationUp
{
NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow *win in windows)
{
NSArray *winSubViews = [win subviews];
if([winSubViews count] == 1)
{
Class gcNotificationClass = NSClassFromString(#"GKGameEventView");
if(gcNotificationClass && ([[winSubViews objectAtIndex:0] isKindOfClass:gcNotificationClass]))
{
return YES;
}
}
}
return NO;
}

NSOperations or NSThread for bursts of smaller tasks that continuously cancel each other?

I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.
The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.
The web service call and the subsequent parsing of the result I would like to move to a different thread.
I have written a simple SearchWebService class, it has only one public method:
- (void) searchFor:(NSString*) str;
This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:
- (NSArray*) resultsReady;
I can't figure out how to get this functionality 'threaded'.
If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one.
Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:
- (void) keepRunning {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SearchWebService *searchObj = [[SearchWebService alloc] init];
[[NSRunLoop currentRunLoop] run]; //keeps it alive
[searchObj release];
[pool release];
}
But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?
Hope someone could point me in the right direction, threading is giving me grief:)
Thank you.
Ok, I spend the last 8 hours reading up on every example out there.
I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.
It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.
Is called after each keystroke:
- (void) searchFieldChanged:(UITextField*) textField {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *searchString = textField.text;
if ([searchString length] > 0) {
[self performSelector:#selector(doSearch:) withObject:textField.text afterDelay:0.8f];
}
}
This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart.
(I would have that a lot lower if it where not for the small touch keyboard).
If it is allowed to time out, it is time to search.
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[queue addOperation:searchOperation];
[searchOperation release];
}
Cancel all operations that is currently in the queue. This is called every time a new search is
started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.
The implementation for the ISSearchOperation is really simple:
#implementation ISSearchOperation
- (void) dealloc {
[searchTerm release];
[JSONresult release];
[parsedResult release];
[super dealloc];
}
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
if ([self isCancelled]) return;
[self setJSONresult:/*do webservice call synchronously*/];
if ([self isCancelled]) return;
[self setParsedResult:/*parse JSON result*/];
if ([self isCancelled]) return;
[self performSelectorOnMainThread:#selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}
#end
There are two major steps, the downloading of the data from the web service and the parsing.
After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.
I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.
But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop.
NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.

saving data in iPhone development

I have some unsolved problem right now. My game is done, now I am at the last stage. In the main menu of the game there will be a button called goodies. Only appears when the user win the game once. I can do until that part . But the last part is to save that goodies. Meaning when the player exist the game after he able to make the button appear, the button still there when he gets back to the game? Could you guys help me out I am not quite sure how to do it. I heard a lot about NSUSerDefault, but don know how it works and how to apply it properly. Thanks for any help.
DegrafeurAppDelegate *appdelegate = (DegrafeurAppDelegate *) [[UIApplication sharedApplication] delegate];
//BOOL b = appdelegate.checkStatus;
if(appdelegate.checkStatus == YES)
{
[goodies setVisible:YES];
}
else
{
[goodies setVisible:NO];
}
This is my code to enable the button after winning once. But how to save it, please help me
As you mentioned, you can user NSUserDefaults:
NSUserDefaults myPrefs = [[NSUserDefaults alloc] init];
[myPrefs setBool:YES forKey:#"displayed_win_button"];
[myPrefs release];
and later to check the value:
if([myPrefs boolForKey:#"displayed_win_button"]) { ... }