I'm creating a Game Center game with GKTurnBasedMatch matches. I'm having a problem where the readonly matchData property on a GKTurnBasedMatch does not seem to be properly stored on Game Center servers.
I'm using this StackOverflow answer to generate an md5 checksum on the matchData NSData, both when being sent and received to and from the Game Center servers.
I note the checksum of my NSData game data object when I send the matchData using the GKTurnBasedMatch instance method endTurnWithNextParticipants:turnTimeout:matchData:completionHandler:.
The opponent then retrieves the matches using GKTurnBasedMatch's class method loadMatchesWithCompletionHandler:, and when the matches arrive (no errors), I note the checksum again.
The two checksums do not match, and the resulting data are clearly not identical based on the reconstructed game. I have checked in the two accounts that the matchID property on my GKTurnBasedMatch objects are identical.
I've also performed the following test:
NSLog(#"matchID: %# matchData checksum: %#",
match.matchID,
[Utilities md5StringFromData:match.matchData]);
// match is a valid `GKTurnBasedMatch` object.
[match endTurnWithNextParticipants: #[ opponent ] // My `GKTurnBasedParticipant` opponent
turnTimeout:600
matchData:data // This is a valid NSData object
completionHandler:^(NSError *error) {
if (nil != error) {
NSLog(#"%#", error);
} else {
NSLog(#"Successfully ended turn.");
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error) {
if (nil != error) {
NSLog(#"Error getting matches: %#", [error localizedDescription]);
} else {
for (GKTurnBasedMatch *match in matches) {
NSLog(#"matchID: %# matchData checksum: %#",
match.matchID,
[Utilities md5StringFromData:match.matchData]);
}
}
}];
}
}];
In this sample, where I end the turn with data and immediately retrieve the matches from Game Center, the data match. However, when I access the matchData from the opponent's Game Center account and device, they differ.
Anyone encountered anything like this?
I discovered the solution on Apple's Dev Forums.
It turns out that loadMatchesWithCompletionHandler: doesn't always grab the most up-to-date matchData. To make sure you have the most recent version, make sure you call the loadMatchDataWithCompletionHandler: method on your GKTurnBasedMatch object.
Related
I am facing an issue when trying to update data in Parse.
Here is my code that i am using.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query whereKey:#"name" equalTo:#"Username"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *item, NSError *error)
{
if (!error)
{
[item setObject:#500 forKey:"Score"];
}
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *itemError)
{
if (!error)
{
NSLog(#"Score is updated");
}
else
{
NSLog(#"Error in updating: %#",error.description);
}
}];
}
else
{
NSLog(#"Error in getting Score: %#",error.description);
}
}];
This code works only when i create a new PFObject and then try to update it.
But,when i exit my app and then try to update the score again,i am unable to update the data.It throws this error..
Error in getting Score: Error Domain=Parse Code=101 "No results matched the query." UserInfo={error=No results matched the query., NSLocalizedDescription=No results matched the query., code=101}
It works again,if i create a new PFObject.
Please help,i am new to Parse and am still try to understand it.
Thank you.
You need to use findObjectsInBackgroundWithBlock instead of getFirstObjectInBackgroundWithBlock as the latter can only be used if there's at least 1 object.
Reference - Parse Questions
You could also use the PFQuery's count method. If the count is >= 1, use getFirstObjectInBackgroundWithBlock, otherwise display a message / handle that case however you'd like.
Other options include storing the objectId of the GameScore object associated with a player on their user object, creating an object without data using the objectId, then fetching it. Or simply use a pointer, but that can do weird things when saving / querying / fetching.
I have a bunch of objects from a PFFetch in an array. Those objects have links (pointer array <-> pointer) to other object. To limit the amount of data sent for my initial fetch I don't want to download all of the linked objects using includeKey on the fetch.
Sometime later, I have a subset of these objects and I do want to fetch the related objects and relations of those objects. If I fetch them again, I duplicate the objects in my app (and presumably needlessly send objects over the wire that I already have in my app)
I.e. I would like some of my objects in my original array to appear as if the original fetch had:
[query includeKey:#"relationship"];
[query includeKey#"relationship.secondRelationship"];
set on the original key. What is the best way to do this? I had imagined some API on PFObject like:
+(void) fetchIfNeededInBackground:(NSArray*)objects includeKey:(NSString*)key block:...
or
- (void)includeKeyAtNextFetch:(NSString*)key
But I can't find anything like this.
it's POSSIBLE you're after the famous containedIn query..
here's an example of using containedIn to address the famous problem "match friends from FB"....
+(void)findFBFriendsWhoAreOnSkywall
{
// issue a fb graph api request to get the fbFriend list...
[APP huddie];
[FBRequestConnection
startForMyFriendsWithCompletionHandler:^(
FBRequestConnection *connection, id result, NSError *error)
{
if (!error)
{
// here, the result will contain an array of the user's
// FB friends, with, the facebook-id in the "data" key
NSArray *fbfriendObjects = [result objectForKey:#"data"];
int kountFBFriends = fbfriendObjects.count;
NSLog(#"myfriends result; count: %d", kountFBFriends);
// NOW MAKE A SIMPLE ARRAY of the fbId of the friends
// NOW MAKE A SIMPLE ARRAY of the fbId of the friends
// NOW MAKE A SIMPLE ARRAY of the fbId of the friends
NSMutableArray *fbfriendIds =
[NSMutableArray arrayWithCapacity:kountFBFriends];
for (NSDictionary *onFBFriendObject in fbfriendObjects)
[fbfriendIds addObject:[onFBFriendObject objectForKey:#"id"]];
for (NSString *onef in fbfriendIds)
NSLog(#"and here they are .. %#", onef);
// query to find friends whose facebook ids are in that list:
// USE THAT SIMPLE ARRAY WITH THE MAGIC 'containedIn' call...
// amazingly easy using the ever-loved containedIn:
// amazingly easy using the ever-loved containedIn:
// amazingly easy using the ever-loved containedIn:
PFQuery *SWUSERSTOADDASFRIENDS = [PFUser query];
[SWUSERSTOADDASFRIENDS whereKey:#"fbId" containedIn:fbfriendIds];
[SWUSERSTOADDASFRIENDS findObjectsInBackgroundWithBlock:^
(NSArray *objects, NSError *error)
{
if (error) // problem getting the matching-friends list
{
[PFAnalytics .. it al went to hell.];
NSLog(#"disaster at the last step! but that's ok");
[APP.hud hide:YES];
[backTo literallyGoToMainPage];
}
else
{
// so all the people in OBJECTS, now make them in to SW friends.
[PFAnalytics trackEvent:#"FBMatchDone" ...];
NSLog(#"found this many fb matches ... %d", objects.count);
[FBMatch actuallyMakeThemFriends:objects];
[APP.hud hide:YES];
[FBMatch message .. objects.count showTheseNames:objects];
}
}];
}
else // problem getting the friend list....
{
[PFAnalytics .. problem];
[APP.hud hide:YES];
[backTo literallyGoToMainPage];
}
}];
}
I hope it helps!
I want to save PFObjects from a Parse query in an NSMutableArray that my class has called listdata. I will later use the listdata array. When I traced through my code, it updated the highScoreObjects array for each object found. But when I try to set the listdata array to the highScoreObjects array, the highScoreObjects array is empty. Is there a way to keep the data after the query ends?
NSMutableArray *highScoreObjects = [[NSMutableArray alloc] initWithCapacity:5];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
[highScoreObjects addObject:object];
NSLog(#"%#", object.objectId);
}
dispatch_async(dispatch_get_main_queue(), ^ {
[self.tableView reloadData];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
self.listData = highScoreObjects;
I also tried keeping the line self.listData = highScoreObjects;
inside the self.listData = highScoreObjects; loop. This didn't make any difference.
It isn't that it isn't set. It's that it isn't set yet. This is because you're using findObjectsInBackgroundWithBlock and the asynchronous process hasn't completed yet.
Move your assignment (self.listData = highScoreObjects;) into the block, just before you dispatch the request to reload the table view.
This is yet another case of not understanding the nature of asynchronous programming.
Consider this situation:
You want to make an egg sandwich. You put the eggs on to boil, and set an alarm for when they're cooked to get them out, peel them, cut them up and add them to your sandwich. While you wait you get the bread and butter it, then wait for the alarm to go off.
Your call to findObjectsInBackgroundWithBlock is putting the eggs on to boil. The block you pass it is the alarm and what you plan to do with the eggs once cooked.
Your code above is akin to putting the eggs on to boil, then straight away trying to use the uncooked/partially-cooked eggs on your sandwich. Makes a big mess.
The solution is to call a method at the end of the block your pass to the method.
I can retrieve all Game Center friends with this code...
GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
if (lp.authenticated)
{
[lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error)
{
NSLog(#"MY FRIENDS: %#",friends);
if (friends != nil)
{
[GKPlayer loadPlayersForIdentifiers:friends withCompletionHandler:^(NSArray *players, NSError *error)
{
if (error != nil)
{
// Handle the error.
NSLog(#"PLAYERLIST ERROR: %#",[error localizedDescription]);
}
if (players != nil)
{
// Process the array of GKPlayer objects.
NSLog(#"PLAYERS: %#",players);
}
}];
}
}];
}
... however, is there a way to retrieve only the friends with GameKit who are online?
It does not look like you are able to. Since GKPlayer does not offer any way to view if a player is online or not.
Also since technically once a person logs on to Game Center they are "Online" till they log off. Meaning they could be online for days while using their phone. When they are logged on if you send them an invite they will get the trumpet noise.
http://developer.apple.com/library/IOS/#documentation/GameKit/Reference/GKPlayer_Ref/Reference/Reference.html#//apple_ref/doc/uid/TP40009599
Any chance I can save/update matchdata even when it is not my turn?
[currentMatch saveCurrentTurnWithMatchData:data completionHandler:^(NSError *error) {
if (error)
{ }];
The above code can be used if it is still this user's turn, but what if it is not this user's turn? How do I send data between two players?
As of iOS 6.0, you cannot. :(
You can save match data without advancing the turn (assuming you are
the current player). see - saveCurrentTurnWithMatchData:completionHandler:
You can end a game out of turn. see - participantQuitOutOfTurnWithOutcome:withCompletionHandler:
However, you cannot update match data out of turn.
GKTurnBasedMatch Reference
Try this
- (void) advanceTurn
{
NSData *updatedMatchData = [this.gameData encodeMatchData];
NSArray *sortedPlayerOrder = [this.gameData encodePlayerOrder];
this.MyMatch.message = [this.gameData matchAppropriateMessage];
[this.myMatch endTurnWithNextParticipants: sortedPlayerOrder turnTimeOut: GKTurnTimeoutDefault
matchData: updatedMatchData completionHandler ^(NSError *error) {
if (error)
{
// Handle the error.
}
}];
}