iosswiftgame-centergkturnbasedmatch

Game Center Sandbox issue


I faced with a very old problem that has been discussed here many times. Despite talking the problem much time I found no acceptable solution so I decided to raise the issue once again.

So, the problem. I'm trying to test a turn-based match. I use two real devices for this. I make a turn on the first device, and match data is updated with no error (I know it for sure), but sometimes the second device doesn't receive any notification and it seems that the first device still in a turn. Sometimes it works as expected.

In another words player(_:receivedTurnEventFor:didBecomeActive) method isn't called sometimes. But if I close the app on the second device, reopen it and join existing match everything works fine. As I understood this is very well known Game Center Sandbox issue, but it makes me crazy while I'm trying to test the app. Does anybody know the way how to workaround? Or maybe there is a best practice how to live and test apps with this strange sandbox behavior?

UPDATE. The method proposed by Thunk is a solution. I rewrote it in Swift and modified to be in line with my game logic. FIrst, I defined global variable var gcBugTimer: Timer

In endTurn(withNextParticipants:turnTimeOut:match:completionHan‌​dler:) completion handler:

let interval = 3.0
self.gcBugTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(self.isMatchActive), userInfo: nil, repeats: true)
self.gcBugTimer.tolerance = 1.0

Code above also should be called in case when a player is joying to a new match and other player in a turn.

Then timer method:

func isMatchActive() {
  // currentMatch - global variable contains information about current match 
  GKTurnBasedMatch.load(withID: currentMatch.matchID!) { (match, error) in
    if match != nil {
      let participant = match?.currentParticipant
      let localPlayer = GKLocalPlayer.localPlayer()
      if localPlayer.playerID == participant?.player?.playerID {
        self.player(localPlayer, receivedTurnEventFor: match!, didBecomeActive: false)
      }
    } else {
      print(error?.localizedDescription ?? "")
    }
  }
}

And I add following code at the very beginning of player(_:receivedTurnEventFor:didBecomeActive):

if gcBugTimer != nil && gcBugTimer.isValid {
  gcBugTimer.invalidate()
}

Solution

  • The only solution I found that worked reliably was to manually recheck my status while waiting for my turn. In the completion handler for endTurnWithNextParticipants I set a timer to continuously reload the match data. I checked if localPlayer had become the active player. If so, then I called receivedTurnForEvent myself, otherwise, I repeated the timer. Like so:

    In the endTurnWithNextParticipants completion handler:

           float dTime = 60.0;     //messages sometimes fail in IOS8.4
            if (SYSTEM_VERSION_EQUAL_TO(@"8.3") )
            {
                dTime = 5.0;        //messages always fail in IOS8.3
            }
            IOS8BugTimer = [NSTimer scheduledTimerWithTimeInterval:dTime
                                                                 target:gameKitHelper
                                                               selector:@selector(isMatchActive:)
                                                               userInfo:theMatch.matchID
                                                                repeats:NO];
    

    and in gameKitHelper:isMatchActive:

    -(void)isMatchActive:(NSTimer *)timer
    {
    
        NSString *matchID = (NSString *)timer.userInfo;
        [GKTurnBasedMatch loadMatchWithID:matchID withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error)
        {
            GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
            GKTurnBasedParticipant *currentParticipant = match.currentParticipant;
    
            if ([localPlayer.playerID isEqualToString:currentParticipant.player.playerID])
            {
                //we have become active. Call the event handler like it's supposed to be called
                [self player:localPlayer receivedTurnEventForMatch:match didBecomeActive:false];
            }
            else
            {
    
                //we are still waiting to become active. Check back soon
                float dTime = 60.0;
    
                if (SYSTEM_VERSION_EQUAL_TO(@"8.3") )
                {
                    dTime = 5.0;
                }
    
                gameController.IOS8BugTimer = [NSTimer scheduledTimerWithTimeInterval:dTime
                                                                           target:self
                                                                         selector:@selector(isMatchActive:)
                                                                         userInfo:matchID
                                                                          repeats:NO];
            }
        }];
    
    }