Game Center Finding a Match Programmatically - iphone

I just can't figure out how this works. What I am trying to do is let two players play a game if a third player joins it can instantly join the game, if the fourth and last player joins it can also instantly join the game. They can also leave the game at anytime for whatever reason, if that happens there should be a space open for another person or for the same person to reconnect. That's the idea.
Now what I got is the following. I authenticate the local player for obvious reasons. Then I search for a match like so:
if (matchRequest) [matchRequest release];
matchRequest = [[GKMatchRequest alloc] init];
matchRequest.minPlayers = 2;
matchRequest.maxPlayers = 4;
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:matchRequest withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error) {
// An error occured
} else {
if (matchCurrent) [matchCurrent release];
matchCurrent = [match retain];
matchCurrent.delegate = self;
}
}];
If I execute this part on three different devices, two of them will find each other and the third is still looking. So I figured after the find match for request has found the minimum amount of players it will be executed once. So what I needed was a method that used the matchCurrent that I retained to add more players. Luckely that method existed, but how would that work? When do you call it in this case? I decided to put it under a button so I could manually execute it when a match has been found.
What I discovered is that when I pressed it on the first device, finally the third device could find the match the first and second device were in. In fact the second and third device contained the playerIDs of every device involved. Which is a good thing. But there are two problems.
Which device should actually call the addPlayersToMatch method? And how can you restrict it to one device executing that method? Plus when should you call it?
Why, on the device calling that method, isn't the playerIDs updated?
[[GKMatchmaker sharedMatchmaker] addPlayersToMatch:matchCurrent matchRequest:matchRequest completionHandler:^(NSError *error) {
//matchCurrent.playerIDs is not updated?!
}];
Actually they are updated. When I see the playerIDs appear on the second and third device I manually update the matchCurrent.playerIDs on device one and suddenly it does recognize the player. However even the 'didChangeState' for player is not called when the new player is discovered on device one.

Your using the Apple iOS Game Center GKMatchmaker class. I'm assuming you are using a peer to peer connection, not hosted.
The GKMatch class gives the playerIDs array to you.
#property(nonatomic, readonly) NSArray *playerIDs
This is an ordered list, so you might be able to use it to select the first player call addPlayersToMatch.
Linked below is some documentation.
http://developer.apple.com/library/ios/#documentation/GameKit/Reference/GKMatchmaker_Ref/Reference/Reference.html
http://developer.apple.com/library/ios/#documentation/GameKit/Reference/GKMatch_Ref/Reference/Reference.html
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008304

Which device should actually call the
addPlayersToMatch method? And how can
you restrict it to one device
executing that method?
You could solve this problem by having the devices "draw straws". Each device generates a random number, then sends it to the others. The device with the largest number is the leader, and it is the one that must call addPlayersToMatch. If two devices pick the same number, throw out the numbers and start over.

I would suggest that you periodically (maybe once per second or two) poll for the status of other players, so you can detect if anyone has joined, or left for any reason. Unless the iphone architecture you are using already provides an function that handles that event.
It sounds like you may want to find some more documentation and/or sample code for the multi-player framework you are using.

I'm doing this with Unity via Prime31's GameCenterMultiplayerBinding. I'm not entirely sure how it maps to GameKit (the docs are sparse and don't give those details), but the names are very suggestive.
To be able to match 2 to 4 players into a match, I'm doing:
findMatchProgrammaticallyWithFilters(
minPlayers: 2,
maxPlayers: 4,
playerGroup: GetCurrentPlayerGroup(),
playerAttributes: 0);
I assume this maps to findMatchForRequest:withCompletionHandler.
After that succeeds, I call:
finishMatchmakingForMatch();
Which surely maps to finishMatchmakingForMatch. I found that calling findMatchForRequest or addPlayersToMatch would fail with "The requested operation has been canceled or disabled by the user" if I didn't call finishMatchmakingForMatch first.
addPlayersToCurrentMatchWithUpdatedMatchRequest(
minPlayers: 2,
maxPlayers: 4,
playerGroup: GetCurrentPlayerGroup(),
playerAttributes: 0,
playersToInvite: null);
addPlayersToMatch:matchRequest:completionHandler
After that succeeds, I call:
finishMatchmakingForMatch();
If I have space for more players, I loop and call addPlayersToMatch again.
I'd expect that since I need to call finishMatchmakingForMatch when I'm done
matchmaking, firing findMatchProgrammatically would keep looking until it
found maxPlayers, but it doesn't. It gives up after the first match. So we need
to call addPlayersToMatch.

Related

CIFaceFeature trackingID is always coming same for multiple faces

I want to detect multiple faces in my project. Therefore I planned to use the trackingID property of the CIFaceFure to keep the track of the face. But I found that every time it is coming same for every face.
So my problem is that how can I identify a face uniquely when multiple face are there in the video frame. I don't want to recognize the face for later purpose only detection for the current video frame. Thanks.
I am using the same code as in SqaureCam apple sample project. in iOS 6.
for ( CIFaceFeature *face in features ) {
NSLog(#"face.trackingID %d",face.trackingID);
}
The above code is priting the same ID for every face.
If you haven't already done so, you need to make sure to specify the usage of CIDetectorTracking in the detector's options. If I remember correctly, it should look something like this:
NSDictionary *detectorOptions = #{CIDetectorTracking: #YES};
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:detectorOptions];

Game Center Quickmatch: randomly matching opponents (GKTurnBasedMatch)

I want there to be a "Quickmatch" mode in my turn-based game, where the player gets automatically matched with the first player to become available. I'm using my own custom UI. My code so far looks like this:
- (void)quickMatch {
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = nil;
[GKTurnBasedMatch findMatchForRequest:request withCompletionHandler:^(GKTurnBasedMatch *match, NSError *error) {
NSLog(#"MATCH: %# %# %# %d",error,match,match.matchID,(int)match.status);
}];
This successfully creates a match, but the 2nd participant in the match has a null ID (playerID:(null) status:Matching).
I thought that if I ran this same code on another instance, using a different Game Center ID, then the two users would be matched against each other... but that does not appear to be correct. Whenever I call the GKTurnBasedMatch loadMatchesWithCompletionHandler function, I continue to retrieve the same matches, each with only 1 valid participant (the local player).
This question appears to be similar to iOS Development: How do I auto match players in Game Center? which does indicate that setting request.playersToInvite = nil; should accomplish auto-matching, yet this doesn't appear to be working for me.
How can I cause Game Center to automatically match these players against each other?
Let me address the issues you are seeing here. First off, it is not necessary to set the playersToInvite property to nil, as that is the default state unless players are assigned to it, however that is not causing your "issue". I put that in quotes because you actually did the code correctly, and only perceive a problem that isn't there. Let me walk you through what happens when findMatchForeRequest is completed.
When the completion block is called, Game Center has created a new GKTurnBasedMatch object with two participants, the first is the local player (you), and the second is actually an empty participant with a nil playerID. The reason for this is that Game Center does not assign all participants when a match is created with random (unassigned) opponents. A random participant is assigned when the turn is sent to them. In your game, that match will not show up in the cloud for others to play in until you take your first turn.
Now, calling loadMatchesWithCompletionHandler on your other device/Game Center ID will not automatically display the match UNLESS you specifically invited that player with playersToInvite (and have already taken your turn as specified above). Think about it like this: if it worked that way, every player in the world would see every auto-match in existence when they called loadMatchesWithCompletionHandler.
The other Game Center ID must actually call findMatchForRequest with no playersToInvite property set in order to be matched into the empty seat available in the game your other ID created. This way the paradigm of "it's always your turn" when creating a match is preserved, but that player is now in the second slot, not the first. Simply create a game on the second ID in the exact same way you did on the first, and your game will be created with two participants, the first being from the ID that originally created it, and the second being the ID that joined the match by calling findMatchForRequest. The key here is findMatchForRequest doesn't ALWAYS create a new match if playersToInvite is nil. If there is an existing match with an open seat, it will simply match the local player into that.
Happy Coding!
Corbin

Accurately reading of iPhone signal strength

There are a few questions on this already, but nothing in them seems to provide accurate results. I need to determine simply if the phone is connected to a cell network at a given moment.
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html
This class seems to be documented incorrectly, returning values for mobileCountryCode, isoCountryCode and mobileNetworkCode where no SIM is installed to the phone. carrierName indicates a 'home' network or a previous home network if the phone has been unlocked.
I also looked up and found some people claiming the following to work, which uses an undocumented method of the CoreTelephony framework, but the results have been useless to me, reporting seemingly random figures, where perhaps it is not itself updating consistently.
-(int) getSignalStrength
{
void *libHandle = dlopen("/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony", RTLD_LAZY);
int (*CTGetSignalStrength)();
CTGetSignalStrength = dlsym(libHandle, "CTGetSignalStrength");
if( CTGetSignalStrength == NULL) NSLog(#"Could not find CTGetSignalStrength");
int result CTGetSignalStrength();
dlclose(libHandle);
return result;
}
Thanks.
Edit: The app is connected to an internal wifi and must remain so, making a reachability check more difficult.
I'm playing with this function and I've noticed you're calling it in an interesting way. I'm calling it by adding CoreTelephony.framework as a compile-time link. For the function itself, you'll want to declare it's prototype somewhere (perhaps immediately above the method you call from):
int CTGetSignalStrength();
This needs to be declared since it isn't in a public header for CoreTelephony.
Now, I built a simple app that prints signal strength every second.
int CTGetSignalStrength();
- (void)viewDidLoad
{
[super viewDidLoad];
while (true) {
printf("signal strength: %d\n", CTGetSignalStrength());
sleep(1);
}
}
I ran it on my iPad mini and it shows steady values until I picked it up, where the number went up. Wrapping my iPad in tin foil (tin foil is a debugging tool I have never used before) caused the number to go down. When I put my iPad in airplane mode, it kept repeating the last value, so this will not be an accurate measure for you.
If you want to test if a device currently has a cellular data network connection, you may be more interested in Reachability, specifically kSCNetworkReachabilityFlagsIsWWAN.
Ok I think I have the correct solution now, which was a bit simpler in the end.
The issue with the CTGetSignalStrength() method is that it works normally, but if you remove a sim, it reports the last signal before the removal. I found another method in the same framework called CTSIMSupportGetSIMStatus(), also undocumented, which can tell you if a SIM is currently connected. Using both as follows should confirm the current network signal.
First declare the methods:
NSString * CTSIMSupportGetSIMStatus();
int CTGetSignalStrength();
Then check connectivity to cell network like so:
NSString *status = CTSIMSupportGetSIMStatus();
int signalstrength = CTGetSignalStrength();
BOOL connected = ( [status isEqualToString: #"kCTSIMSupportSIMStatusReady"] && signalstrength > 0 );

MediaPlayer.framework: How to "translate" MPMusicRepeatModeDefault into an actual mode?

As is stated in Apple documents:
enum {
MPMusicRepeatModeDefault,
MPMusicRepeatModeNone,
MPMusicRepeatModeOne,
MPMusicRepeatModeAll
};
typedef NSInteger MPMusicRepeatMode;
Yet, MPMusicRepeatModeDefault is described as The user’s preferred repeat mode. Since I am writing a music player I require to know every time what is the current repeat mode, and when this is returned, what of the "actual" modes:
MPMusicRepeatModeNone
MPMusicRepeatModeOne
MPMusicRepeatModeAll
shall be chosen? Or is there no way to get such information?
My understanding is that MPMusicRepeatModeDefault is only used for instantiating your own player as described here.
MPMusicPlayerController* appMusicPlayer = [MPMusicPlayerController applicationMusicPlayer];
// Use whatever the user has set in their iPod settings
// Omitting this line has no real effect because deferring to the
// user mode is the default setting for new players
[appMusicPlayer setRepeatMode: MPMusicRepeatModeDefault];
If you want to know what that default setting actually is, you should be able to get it from the iPodMusicPlayer instance:
MPMusicPlayerController* iPodMusicPlayer =
[MPMusicPlayerController iPodMusicPlayer];
MPMusicRepeatMode theDefaultMode = [iPodMusicPlayer repeatMode];

Proper Management Of A Singleton Data Store In IOS With Web Service

I'm currently using a singleton as a data store for my app. I essentially store a number of events that are pulled and parsed from a web service and then added as needed. Each time I make a request from the web service, I parse the results and see if the items already exist. If they do, I delete them and add the updated version provided by the web service.
Everything appeared to be working properly until I fired up the Instruments panel to find out that my system is leaking the objects every time it loads them from the web service (from the second time on). The core method where things appear to be messing up is this one, which is located in my HollerStore singleton class:
- (void)addHoller: (Holler *)h
{
//Take a holler, check to see if one like it already exists
int i = 0;
NSArray *theHollers = [NSArray arrayWithArray:allHollers];
for( Holler *th in theHollers )
{
if( [[th hollerId]isEqualToString:[h hollerId]] )
{
NSLog(#"Removing holler at index %i", i);
[allHollers removeObjectAtIndex:i];
}
i++;
}
[allHollers addObject:h];
}
Quick explanation: I decided to copy the allHollers NSMutableArray into theHollers because it's being updated asynchronously by NSURLConnection. If I update it directly, it results in a crash. As such, I switched to this model hoping to solve the problem, however the Instruments panel is telling me that my objects are leaking. All the counts are exactly the # of items I have in my data set.
From what I can tell removeObjectAtIndex isn't effectively removing the items. Would love to get the thoughts of anybody else out there on three things:
Is my analysis correct that something else must be retaining the individual hollers being added?
Should I be using CoreData or SQLite for storing information pulled from the web service?
Do you know how long data stored in a Singleton should be available for? Until the app is killed?
Update
I think I've found the source, however perhaps someone can provide some clarity on the proper way to do this. I've created a method called parseHoller which takes a dictionary object created through SBJSON and returns my own model (Holler). Here are the last couple lines:
Holler *h = [[[Holler alloc] initFromApiResponse:hollerId
creatorId:creatorId
creatorName:creatorName
creatorImageUrl:creatorImage
comments:comments
attendees:attendees
wishes:wishes
invitees:invites
createdAt:createdAt
text:text
title:title
when:when]autorelease];
//(some other autorelease stuff is here to clean up the internal method)
return h;
I figured that since I'm returning an autoreleased object, this should be fine. Do you see anything wrong with this?
Have you tried to do a retain count on the objects that is leaking? Maybe that could clear up when or where it is being retained.
The code should be
[putObjectHere retainCount];
and then write to an NSLog
Hope it gives you something
Peter