In the game I'm working on I would like to give users the possibility to select between asynchronous and real-time turn-based matches. The one thing I'm missing from the latter is how do I know who's got the first turn. I'm trying to find a way to detect who's got connected first and set that player's turn automatically. Unfortunately, it seems that both players get connected at the same time since right after finding a match expectedPlayersCount yields 1 for both players and in the didChangeState event that same variable yields 0 for both of them too. So I have no way to tell who's got first to the match since it seems that it all happens simultaneously. As a temporary fix, I'm just using their IDs. The one with the lowest ID is the one having the first turn. However, I'm trying to find a better way since with this approach player A will always get the first turn when playing against player B and that represents a small advantage in my game.

This method is what I call when starting a new game. I pass a realTime bool value to specify whether the game returned by the game center wrapper should be a GKMatch or GKTurnBasedMatch.

//Calculate the level group to find other players in the same group
NSInteger levelGroup = (self.player.levelValue / STLevelSkillInterval) * STLevelSkillInterval;

//Find a match for the local player
[self.gameCenter findMatchWithPlayerAttributes:levelGroup realTime:realTime group:0 onCompletion:^(GKTurnBasedMatch *turnMatch, GKMatch *realTimeMatch) {

    [self handleTurnMatchFound:turnMatch realTimeMatch:realTimeMatch completionBlock:onCompletion];

}];

...

This method here is the responsible to handle game center response after finding match. The create and synchronize method stores a match instance in CoreData and also fills its data by fetching the corresponding info from Game Center and my backend. So if at the time of that's done the expected player count reached 0, I call the completion block immediately since the match can begin. Otherwise I just store the completion block so I can use it later when the other player connects to the match. The problem in that part is that it never reaches 0, not for any of the two players.

- (void)handleTurnMatchFound:(GKTurnBasedMatch *)turnMatch realTimeMatch:(GKMatch *)realTimeMatch completionBlock:(void(^)(STMatch *match, NSError *error))onCompletion
{
    if (turnMatch != nil)
    {
        [self createAndSynchronizeLocalMatch:turnMatch onCompletion:^(STMatch *localMatch) {

            onCompletion(localMatch, nil);

        }];
    }
    else if (realTimeMatch != nil)
    {
        [self createAndSynchronizeRealTimeLocalMatch:realTimeMatch onCompletion:^(STMatch *localMatch) {

            if (realTimeMatch.expectedPlayerCount == 0)
            {
                onCompletion(localMatch, nil);
            }
            else
            {
                self.findRealTimeMatchCompletionBlock = onCompletion;
            }

        }];
    }
    else
    {
        onCompletion(nil, nil);
    }
}

...

This is the method that handles player's state changes. So basically here I just update the local instance of the real time match with values from the connected player and then I call the completion block stored earlier.

- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state
{
    if (state == GKPlayerStateConnected)
    {
        STMatchParticipant *placeholderParticipant = [self.realTimeMatch getMatchParticipantWithPlayerId:nil];
        placeholderParticipant.playerId = playerID;
        placeholderParticipant.statusValue = GKTurnBasedParticipantStatusActive;

        //This will sync the information for the connected player only
        [self syncAllPlayerInfoForRealTimeMatchOnCompletion:^(NSArray *players) {

            self.findRealTimeMatchCompletionBlock(self.realTimeMatch, nil);

        }];
    }
    else
    {
        //Notify the observers that the participant has disconnected from the match
        STMatchParticipant *participant = [self.realTimeMatch getMatchParticipantWithPlayerId:playerID];

        for (id<STTurnBasedMatchDelegate> observer in self.matchesObservers)
        {
            if ([observer respondsToSelector:@selector(realTimeMatch:participantDidDisconnect:)])
            {
                [observer realTimeMatch:self.realTimeMatch participantDidDisconnect:participant];
            }
        }
    }
}

I would appreciate any comments.

有帮助吗?

解决方案

Instead of trying to determine who got connected first, why don't you just have all the players pick a random number using arc4random ?

int randomNumber = arc4random%1000000;

The player with the biggest number can play first. You will have to make sure all the players send each other their random numbers so everyone can compare and decide who is first. In the above example, the range would be upto 1 million, so the odds of two players picking the same random number is low.

If two players do pick the same random number, you could compute the hash of their playerId's, and have the player with the larger hash be first.

if ([GKLocalPlayer localPlayer].playerID.hash > otherPlayerId.hash )

The chances of a hash collision occurring are very low, since the playerId strings are short. You can still check for this collision and handle it appropriately (maybe by hashing again ;) ).

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top