diff --git a/DDGameKitHelper.h b/DDGameKitHelper.h index abec820..0bd8ff1 100755 --- a/DDGameKitHelper.h +++ b/DDGameKitHelper.h @@ -6,57 +6,70 @@ #import +// ----------------------------------------------------------------- +#define DDGAMEKIT_LOGGING 0 +// ----------------------------------------------------------------- + @protocol DDGameKitHelperProtocol --(bool) compare:(int64_t)score1 to:(int64_t)score2; --(void) onSubmitScore:(int64_t)score; --(void) onReportAchievement:(GKAchievement*)achievement; +- (BOOL) compareScore:(int64_t)score1 toScore:(int64_t)score2; +- (void) onSubmitScore:(int64_t)score; +- (void) onReportAchievement:(GKAchievement*)achievement; @end +// ----------------------------------------------------------------- + @interface DDGameKitHelper : NSObject { - id delegate; - bool isGameCenterAvailable; - NSMutableDictionary* achievements; - NSMutableDictionary* scores; - NSMutableDictionary* achievementDescriptions; - NSString* currentPlayerID; + id _delegate; + BOOL _isGameCenterAvailable; + NSMutableDictionary* _achievements; + NSMutableDictionary* _scores; + NSMutableDictionary* _achievementDescriptions; + NSString* _currentPlayerID; } -@property (nonatomic, retain) id delegate; -@property (nonatomic, readonly) bool isGameCenterAvailable; +// ----------------------------------------------------------------- + +@property (nonatomic, strong) id delegate; +@property (nonatomic, readonly) BOOL isGameCenterAvailable; @property (nonatomic, readonly) NSMutableDictionary* achievements; @property (nonatomic, readonly) NSMutableDictionary* scores; @property (nonatomic, readonly) NSMutableDictionary* achievementDescriptions; -@property (nonatomic, retain) NSString* currentPlayerID; - -+(DDGameKitHelper*) sharedGameKitHelper; - --(void) setNotAvailable; +@property (nonatomic, strong) NSString* currentPlayerID; --(bool) isAvailable; +// ----------------------------------------------------------------- --(void) authenticateLocalPlayer; +// Singleton instance ++ (DDGameKitHelper*) sharedGameKitHelper; --(bool) isLocalPlayerAuthenticated; +// ----------------------------------------------------------------- --(void) submitScore:(int64_t)value category:(NSString*)category; +// Check and set availability +- (void) setNotAvailable; +- (BOOL) isAvailable; --(void) reportAchievement:(NSString*)identifier percentComplete:(float)percent; +// Authenticate and check authentication +- (void) authenticateLocalPlayer; +- (BOOL) isLocalPlayerAuthenticated; --(void) resetAchievements; +// Submitting score and achievements +- (void) submitScore:(int64_t)value category:(NSString*)category; +- (void) reportAchievement:(NSString*)identifier percentComplete:(float)percent; --(void) showGameCenter; +// Resetting achievements +- (void) resetAchievements; --(void) showLeaderboard; - --(void) showLeaderboardwithCategory:(NSString*)category timeScope:(int)tscope; - --(void) showAchievements; - --(GKAchievementDescription*) getAchievementDescription:(NSString*)identifier; +// Showing GameCenter +- (void) showGameCenter; +- (void) showLeaderboard; +- (void) showLeaderboardWithCategory:(NSString*)category timeScope:(int)tscope; +- (void) showAchievements; +// Achievement info - (int) numberOfTotalAchievements; - - (int) numberOfCompletedAchievements; +- (GKAchievementDescription*) getAchievementDescription:(NSString*)identifier; +// ----------------------------------------------------------------- @end +// ----------------------------------------------------------------- \ No newline at end of file diff --git a/DDGameKitHelper.m b/DDGameKitHelper.m index 70f963b..391df8a 100755 --- a/DDGameKitHelper.m +++ b/DDGameKitHelper.m @@ -8,80 +8,63 @@ #import "DDGameKitHelperDelegate.h" #import +// ----------------------------------------------------------------- + static NSString* kAchievementsFile = @".achievements"; static NSString* kScoresFile = @".scores"; +// ----------------------------------------------------------------- + @interface DDGameKitHelper (Private) --(void) registerForLocalPlayerAuthChange; --(void) initScores; --(void) initAchievements; --(void) synchronizeAchievements; --(void) synchronizeScores; --(void) saveScores; --(void) saveAchievements; --(void) loadAchievementDescriptions; --(GKScore*) getScoreByCategory:(NSString*)category; --(GKAchievement*) getAchievement:(NSString*)identifier; --(UIViewController*) getRootViewController; +// Init +- (void) initScores; +- (void) initAchievements; +- (void) loadAchievementDescriptions; +// Saving +- (void) saveScores; +- (void) saveAchievements; +// Synchronizing +- (void) synchronizeAchievements; +- (void) synchronizeScores; +// Authentication +- (void) registerForLocalPlayerAuthChange; +// Getters +- (UIViewController*) getRootViewController; +- (GKScore*) getScoreByCategory:(NSString*)category; +- (GKAchievement*) getAchievement:(NSString*)identifier; @end +// ----------------------------------------------------------------- @implementation DDGameKitHelper +// ----------------------------------------------------------------- -static DDGameKitHelper *instanceOfGameKitHelper; +// ----------------------------------------------------------------- +#pragma mark - Singleton +#pragma mark - +// ----------------------------------------------------------------- -+(id) alloc ++ (instancetype) sharedGameKitHelper { - @synchronized(self) - { - NSAssert(instanceOfGameKitHelper == nil, @"Attempted to allocate a second instance of the singleton: GameKitHelper"); - instanceOfGameKitHelper = [[super alloc] retain]; - return instanceOfGameKitHelper; - } + static DDGameKitHelper *sharedGameKitHelper = nil; - return nil; -} - -+(DDGameKitHelper*) sharedGameKitHelper -{ - @synchronized(self) - { - if (instanceOfGameKitHelper == nil) - { - [[DDGameKitHelper alloc] init]; - } - - return instanceOfGameKitHelper; - } + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedGameKitHelper = [[self alloc] init]; + }); - return nil; + return sharedGameKitHelper; } -@synthesize delegate; -@synthesize isGameCenterAvailable; -@synthesize achievements; -@synthesize scores; -@synthesize achievementDescriptions; -@synthesize currentPlayerID; +// ----------------------------------------------------------------- +#pragma mark - 00 Init & Dealloc +#pragma mark - +// ----------------------------------------------------------------- --(NSString *) returnMD5Hash:(NSString*)concat -{ - const char *concat_str = [concat UTF8String]; - unsigned char result[CC_MD5_DIGEST_LENGTH]; - CC_MD5(concat_str, strlen(concat_str), result); - NSMutableString *hash = [NSMutableString string]; - for (int i = 0; i < 16; i++) - { - [hash appendFormat:@"%02X", result[i]]; - } - - return [hash lowercaseString]; -} - --(id) init +- (instancetype) init { if ((self = [super init])) { - delegate = [[DDGameKitHelperDelegate alloc] init]; + _delegate = [[DDGameKitHelperDelegate alloc] init]; // Test for Game Center availability Class gameKitLocalPlayerClass = NSClassFromString(@"GKLocalPlayer"); @@ -92,49 +75,53 @@ -(id) init NSString* currSysVer = [[UIDevice currentDevice] systemVersion]; bool isOSVer41 = ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending); - isGameCenterAvailable = (isLocalPlayerAvailable && isOSVer41); - NSLog(@"GameCenter available = %@", isGameCenterAvailable ? @"YES" : @"NO"); + _isGameCenterAvailable = (isLocalPlayerAvailable && isOSVer41); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"GameCenter available = %@", _isGameCenterAvailable ? @"YES" : @"NO"); - if (isGameCenterAvailable) + if (_isGameCenterAvailable) [self registerForLocalPlayerAuthChange]; } return self; } --(void) dealloc +// ----------------------------------------------------------------- + +- (void) dealloc { - [instanceOfGameKitHelper release]; - instanceOfGameKitHelper = nil; - [self saveScores]; [self saveAchievements]; - [scores release]; - [achievements release]; - [achievementDescriptions release]; - - [currentPlayerID release]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [super dealloc]; } --(void) setNotAvailable +// ----------------------------------------------------------------- +#pragma mark - 01 Public +#pragma mark - +#pragma mark a) Availability +// ----------------------------------------------------------------- + +- (void) setNotAvailable { - isGameCenterAvailable = NO; + _isGameCenterAvailable = NO; [[NSNotificationCenter defaultCenter] removeObserver:self]; } --(bool) isAvailable +// ----------------------------------------------------------------- + +- (BOOL) isAvailable { - return isGameCenterAvailable; + return _isGameCenterAvailable; } --(void) authenticateLocalPlayer +// ----------------------------------------------------------------- +#pragma mark b) Authentication +// ----------------------------------------------------------------- + +- (void) authenticateLocalPlayer { - if (isGameCenterAvailable == NO) + if (_isGameCenterAvailable == NO) return; GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer]; @@ -144,216 +131,411 @@ -(void) authenticateLocalPlayer { if (error != nil) { - NSLog(@"error authenticating player: %@", [error localizedDescription]); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"error authenticating player: %@", [error localizedDescription]); } else { - NSLog(@"player authenticated"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"player authenticated"); } }]; } } --(bool) isLocalPlayerAuthenticated -{ - if (isGameCenterAvailable == NO) - return isGameCenterAvailable; +// ----------------------------------------------------------------- +- (BOOL) isLocalPlayerAuthenticated +{ + if (_isGameCenterAvailable == NO) + return _isGameCenterAvailable; + GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer]; return localPlayer.authenticated; } --(void) onLocalPlayerAuthenticationChanged +// ----------------------------------------------------------------- +#pragma mark c) Submit Progress +// ----------------------------------------------------------------- + +- (void) submitScore:(int64_t)value category:(NSString*)category { - NSString* newPlayerID; - GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer]; + if (_isGameCenterAvailable == NO) + return; - // if not authenticating then just return + // always report the new score + if (DDGAMEKIT_LOGGING == 1) NSLog(@"reporting score of %lld for %@", value, category); + GKScore* newScore = [[GKScore alloc] initWithCategory:category]; + newScore.value = value; + [newScore reportScoreWithCompletionHandler:^(NSError* error) + { + // if it's better than the previous score, then save it and notify the user + GKScore* score = [self getScoreByCategory:category]; + if ([_delegate compareScore:value toScore:score.value]) + { + if (DDGAMEKIT_LOGGING == 1) NSLog(@"new high score of %lld for %@", score.value, category); + score.value = value; + [self saveScores]; + [_delegate onSubmitScore:value]; + } + }]; - if (!localPlayer.isAuthenticated) - { +} + +// ----------------------------------------------------------------- + +- (void) reportAchievement:(NSString*)identifier percentComplete:(float)percent +{ + if (_isGameCenterAvailable == NO) return; + + GKAchievement* achievement = [self getAchievement:identifier]; + if (achievement.percentComplete < percent) + { + if (DDGAMEKIT_LOGGING == 1) NSLog(@"new achievement %@ reported", achievement.identifier); + achievement.percentComplete = percent; + [achievement reportAchievementWithCompletionHandler:^(NSError* error) + { + [_delegate onReportAchievement:(GKAchievement*)achievement]; + }]; + + [self saveAchievements]; } +} + +// ----------------------------------------------------------------- +#pragma mark d) Resetting Achievements +// ----------------------------------------------------------------- + +- (void) resetAchievements +{ + if (_isGameCenterAvailable == NO) + return; + + [_achievements removeAllObjects]; + [self saveAchievements]; - NSLog(@"onLocalPlayerAuthenticationChanged. reloading scores and achievements and resynchronzing."); + [GKAchievement resetAchievementsWithCompletionHandler:^(NSError* error) {}]; - if (localPlayer.playerID != nil) + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievements reset"); +} +// ----------------------------------------------------------------- +#pragma mark e) Showing Game Center +// ----------------------------------------------------------------- + +- (void) showGameCenter +{ + if (_isGameCenterAvailable == NO) { - newPlayerID = [self returnMD5Hash:localPlayer.playerID]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; + [alert show]; + + return; + } + + if ([GKGameCenterViewController class]) + { + GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init]; + if (gameCenterController != nil) + { + gameCenterController.gameCenterDelegate = self; + [self presentViewController:gameCenterController]; + } } else { - newPlayerID = @"unknown"; + [self showLeaderboard]; } - - if (currentPlayerID != nil && [currentPlayerID compare:newPlayerID] == NSOrderedSame) +} + +// ----------------------------------------------------------------- + +- (void) showLeaderboard +{ + if (_isGameCenterAvailable == NO) { - NSLog(@"player is the same"); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; + [alert show]; + return; } - self.currentPlayerID = newPlayerID; - NSLog(@"currentPlayerID=%@", currentPlayerID); - - [self initScores]; - [self initAchievements]; + GKLeaderboardViewController* leaderboardVC = [[GKLeaderboardViewController alloc] init]; + if (leaderboardVC != nil) + { + leaderboardVC.leaderboardDelegate = self; + [self presentViewController:leaderboardVC]; + } +} + +// ----------------------------------------------------------------- + +- (void) showLeaderboardWithCategory:(NSString*)category timeScope:(int)tscope +{ + if (_isGameCenterAvailable == NO) + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; + [alert show]; + + return; + } - [self synchronizeScores]; - [self synchronizeAchievements]; - [self loadAchievementDescriptions]; + GKLeaderboardViewController* leaderboardVC = [[GKLeaderboardViewController alloc] init]; + if (leaderboardVC != nil) + { + leaderboardVC.leaderboardDelegate = self; + leaderboardVC.category = category; + leaderboardVC.timeScope = tscope; + [self presentViewController:leaderboardVC]; + } } --(void) registerForLocalPlayerAuthChange +// ----------------------------------------------------------------- + +- (void) showAchievements { - if (isGameCenterAvailable == NO) + if (_isGameCenterAvailable == NO) + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; + [alert show]; + return; + } - NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(onLocalPlayerAuthenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil]; + GKAchievementViewController* achievementsVC = [[GKAchievementViewController alloc] init]; + if (achievementsVC != nil) + { + achievementsVC.achievementDelegate = self; + [self presentViewController:achievementsVC]; + } +} + +// ----------------------------------------------------------------- +#pragma mark f) Achievement Numbers +// ----------------------------------------------------------------- + +- (int) numberOfTotalAchievements +{ + int count = 0; + if (_isGameCenterAvailable) + { + count = [_achievementDescriptions allValues].count; + } + return count; +} + + +// ----------------------------------------------------------------- + +- (int) numberOfCompletedAchievements +{ + int count = 0; + if (_isGameCenterAvailable) + { + NSArray* gcAchievementsArray = [_achievements allValues]; + for (GKAchievement* gcAchievement in gcAchievementsArray) + { + if (gcAchievement.completed) + count++; + } + } + return count; } --(void) initScores +// ----------------------------------------------------------------- +#pragma mark g) Achievement Description +// ----------------------------------------------------------------- + +- (GKAchievementDescription*) getAchievementDescription:(NSString*)identifier +{ + GKAchievementDescription* description = [_achievementDescriptions objectForKey:identifier]; + return description; +} + +// ----------------------------------------------------------------- +#pragma mark - 02 Private +#pragma mark - +#pragma mark a) Init +// ----------------------------------------------------------------- + +- (void) initScores { NSString* libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* file = [libraryPath stringByAppendingPathComponent:currentPlayerID]; + NSString* file = [libraryPath stringByAppendingPathComponent:_currentPlayerID]; file = [file stringByAppendingString:kScoresFile]; id object = [NSKeyedUnarchiver unarchiveObjectWithFile:file]; if ([object isKindOfClass:[NSMutableDictionary class]]) { NSMutableDictionary* loadedScores = (NSMutableDictionary*)object; - scores = [[NSMutableDictionary alloc] initWithDictionary:loadedScores]; + _scores = [[NSMutableDictionary alloc] initWithDictionary:loadedScores]; } else { - scores = [[NSMutableDictionary alloc] init]; + _scores = [[NSMutableDictionary alloc] init]; } - NSLog(@"scores initialized: %d", scores.count); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"scores initialized: %d", _scores.count); } --(void) initAchievements +// ----------------------------------------------------------------- + +- (void) initAchievements { NSString* libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* file = [libraryPath stringByAppendingPathComponent:currentPlayerID]; + NSString* file = [libraryPath stringByAppendingPathComponent:_currentPlayerID]; file = [file stringByAppendingString:kAchievementsFile]; id object = [NSKeyedUnarchiver unarchiveObjectWithFile:file]; if ([object isKindOfClass:[NSMutableDictionary class]]) { NSMutableDictionary* loadedAchievements = (NSMutableDictionary*)object; - achievements = [[NSMutableDictionary alloc] initWithDictionary:loadedAchievements]; + _achievements = [[NSMutableDictionary alloc] initWithDictionary:loadedAchievements]; } else { - achievements = [[NSMutableDictionary alloc] init]; + _achievements = [[NSMutableDictionary alloc] init]; } - NSLog(@"achievements initialized: %d", achievements.count); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievements initialized: %d", _achievements.count); +} + +// ----------------------------------------------------------------- + +- (void)loadAchievementDescriptions +{ + if (DDGAMEKIT_LOGGING == 1) NSLog(@"loading achievement descriptions"); + + [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *achievementDesc, NSError *error) + { + _achievementDescriptions = [[NSMutableDictionary alloc] init]; + + if (error != nil) + { + if (DDGAMEKIT_LOGGING == 1) NSLog(@"unable to load achievements"); + return; + } + + for (GKAchievementDescription *description in achievementDesc) + { + [_achievementDescriptions setObject:description forKey:description.identifier]; + } + + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievement descriptions initialized: %d", _achievementDescriptions.count); + }]; } +// ----------------------------------------------------------------- +#pragma mark b) Saving +// ----------------------------------------------------------------- + - (void) saveScores { NSString* libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* file = [libraryPath stringByAppendingPathComponent:currentPlayerID]; + NSString* file = [libraryPath stringByAppendingPathComponent:_currentPlayerID]; file = [file stringByAppendingString:kScoresFile]; - [NSKeyedArchiver archiveRootObject:scores toFile:file]; - NSLog(@"scores saved: %d", scores.count); + [NSKeyedArchiver archiveRootObject:_scores toFile:file]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"scores saved: %d", _scores.count); } --(void) saveAchievements +// ----------------------------------------------------------------- + +- (void) saveAchievements { NSString* libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString* file = [libraryPath stringByAppendingPathComponent:currentPlayerID]; + NSString* file = [libraryPath stringByAppendingPathComponent:_currentPlayerID]; file = [file stringByAppendingString:kAchievementsFile]; - [NSKeyedArchiver archiveRootObject:achievements toFile:file]; - NSLog(@"achievements saved: %d", achievements.count); + [NSKeyedArchiver archiveRootObject:_achievements toFile:file]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievements saved: %d", _achievements.count); } --(void) synchronizeScores +// ----------------------------------------------------------------- +#pragma mark c) Synchronizing +// ----------------------------------------------------------------- + +- (void) synchronizeScores { - NSLog(@"synchronizing scores"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"synchronizing scores"); // get the top score for each category for current player and compare it to the game center score for the same category - [GKLeaderboard loadCategoriesWithCompletionHandler:^(NSArray *categories, NSArray *titles, NSError *error) + [GKLeaderboard loadCategoriesWithCompletionHandler:^(NSArray *categories, NSArray *titles, NSError *error) { if (error != nil) { - NSLog(@"unable to synchronize scores"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"unable to synchronize scores"); return; } NSString* playerId = [GKLocalPlayer localPlayer].playerID; - for (NSString* category in categories) - { + for (NSString* category in categories) + { GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] initWithPlayerIDs:[NSArray arrayWithObject:playerId]]; leaderboardRequest.category = category; leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime; leaderboardRequest.range = NSMakeRange(1,1); - [leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *playerScores, NSError *error) + [leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *playerScores, NSError *error) { if (error != nil) { - NSLog(@"unable to synchronize scores"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"unable to synchronize scores"); return; } GKScore* gcScore = nil; if ([playerScores count] > 0) gcScore = [playerScores objectAtIndex:0]; - GKScore* localScore = [scores objectForKey:category]; + GKScore* localScore = [_scores objectForKey:category]; //Must add the next two lines in order to prevent a 'A GKScore must contain an initialized value' crash - GKScore *toReport = [[[GKScore alloc] initWithCategory:category] autorelease]; + GKScore *toReport = [[GKScore alloc] initWithCategory:category]; toReport.value = localScore.value; if (gcScore == nil && localScore == nil) { - NSLog(@"%@(%lld,%lld): no score yet. nothing to synch", category, gcScore.value, localScore.value); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): no score yet. nothing to synch", category, gcScore.value, localScore.value); } else if (gcScore == nil) { - NSLog(@"%@(%lld,%lld): gc score missing. reporting local score", category, gcScore.value, localScore.value); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): gc score missing. reporting local score", category, gcScore.value, localScore.value); [localScore reportScoreWithCompletionHandler:^(NSError* error) {}]; } else if (localScore == nil) { - NSLog(@"%@(%lld,%lld): local score missing. caching gc score", category, gcScore.value, localScore.value); - [scores setObject:gcScore forKey:gcScore.category]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): local score missing. caching gc score", category, gcScore.value, localScore.value); + [_scores setObject:gcScore forKey:gcScore.category]; [self saveScores]; } - else if ([delegate compare:localScore.value to:gcScore.value]) + else if ([_delegate compareScore:localScore.value toScore:gcScore.value]) { - NSLog(@"%@(%lld,%lld): local score more current than gc score. reporting local score", category, gcScore.value, localScore.value); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): local score more current than gc score. reporting local score", category, gcScore.value, localScore.value); [toReport reportScoreWithCompletionHandler:^(NSError* error) {}]; } - else if ([delegate compare:gcScore.value to:localScore.value]) + else if ([_delegate compareScore:gcScore.value toScore:localScore.value]) { - NSLog(@"%@(%lld,%lld): gc score is more current than local score. caching gc score", category, gcScore.value, localScore.value); - [scores setObject:gcScore forKey:gcScore.category]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): gc score is more current than local score. caching gc score", category, gcScore.value, localScore.value); + [_scores setObject:gcScore forKey:gcScore.category]; [self saveScores]; } else { - NSLog(@"%@(%lld,%lld): scores are equal. nothing to synch", category, gcScore.value, localScore.value); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"%@(%lld,%lld): scores are equal. nothing to synch", category, gcScore.value, localScore.value); } }]; - [leaderboardRequest release]; } }]; } --(void) synchronizeAchievements +// ----------------------------------------------------------------- + +- (void) synchronizeAchievements { - NSLog(@"synchronizing achievements"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"synchronizing achievements"); // get the achievements from game center @@ -361,298 +543,196 @@ -(void) synchronizeAchievements { if (error != nil) { - NSLog(@"unable to synchronize achievements"); + if (DDGAMEKIT_LOGGING == 1) NSLog(@"unable to synchronize achievements"); return; } // convert NSArray into NSDictionary for ease of use NSMutableDictionary *gcAchievements = [[NSMutableDictionary alloc] init]; - for (GKAchievement* gcAchievement in gcAchievementsArray) + for (GKAchievement* gcAchievement in gcAchievementsArray) { [gcAchievements setObject:gcAchievement forKey:gcAchievement.identifier]; } // find local achievements not yet reported in game center and report them - for (NSString* identifier in achievements) + for (NSString* identifier in _achievements) { GKAchievement *gcAchievement = [gcAchievements objectForKey:identifier]; if (gcAchievement == nil) { - NSLog(@"achievement %@ not in game center. reporting it", identifier); - [[achievements objectForKey:identifier] reportAchievementWithCompletionHandler:^(NSError* error) {}]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievement %@ not in game center. reporting it", identifier); + [[_achievements objectForKey:identifier] reportAchievementWithCompletionHandler:^(NSError* error) {}]; } } // find game center achievements that are not reported locally and store them for (GKAchievement* gcAchievement in gcAchievementsArray) { - GKAchievement* localAchievement = [achievements objectForKey:gcAchievement.identifier]; + GKAchievement* localAchievement = [_achievements objectForKey:gcAchievement.identifier]; if (localAchievement == nil) { - NSLog(@"achievement %@ not stored locally. storing it", gcAchievement.identifier); - [achievements setObject:gcAchievement forKey:gcAchievement.identifier]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"achievement %@ not stored locally. storing it", gcAchievement.identifier); + [_achievements setObject:gcAchievement forKey:gcAchievement.identifier]; } } [self saveAchievements]; - [gcAchievements release]; }]; } --(void) submitScore:(int64_t)value category:(NSString*)category +// ----------------------------------------------------------------- +#pragma mark d) Authentication +// ----------------------------------------------------------------- + +- (void) onLocalPlayerAuthenticationChanged { - if (isGameCenterAvailable == NO) + NSString* newPlayerID; + GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer]; + + // if not authenticating then just return + + if (!localPlayer.isAuthenticated) + { return; + } - // always report the new score - NSLog(@"reporting score of %lld for %@", value, category); - GKScore* newScore = [[GKScore alloc] initWithCategory:category]; - newScore.value = value; - [newScore reportScoreWithCompletionHandler:^(NSError* error) - { - // if it's better than the previous score, then save it and notify the user - GKScore* score = [self getScoreByCategory:category]; - if ([delegate compare:value to:score.value]) - { - NSLog(@"new high score of %lld for %@", score.value, category); - score.value = value; - [self saveScores]; - [delegate onSubmitScore:value]; - } - }]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"onLocalPlayerAuthenticationChanged. reloading scores and achievements and resynchronzing."); - [newScore release]; -} - --(GKScore*) getScoreByCategory:(NSString*)category -{ - GKScore* score = [scores objectForKey:category]; + if (localPlayer.playerID != nil) + { + newPlayerID = [self returnMD5Hash:localPlayer.playerID]; + } + else + { + newPlayerID = @"unknown"; + } - if (score == nil) + if (_currentPlayerID != nil && [_currentPlayerID compare:newPlayerID] == NSOrderedSame) { - score = [[[GKScore alloc] initWithCategory:category] autorelease]; - score.value = 0; - [scores setObject:score forKey:category]; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"player is the same"); + return; } - return score; + self.currentPlayerID = newPlayerID; + if (DDGAMEKIT_LOGGING == 1) NSLog(@"currentPlayerID=%@", _currentPlayerID); + + [self initScores]; + [self initAchievements]; + + [self synchronizeScores]; + [self synchronizeAchievements]; + [self loadAchievementDescriptions]; } --(void) reportAchievement:(NSString*)identifier percentComplete:(float)percent +// ----------------------------------------------------------------- + +- (void) registerForLocalPlayerAuthChange { - if (isGameCenterAvailable == NO) + if (_isGameCenterAvailable == NO) return; - GKAchievement* achievement = [self getAchievement:identifier]; - if (achievement.percentComplete < percent) - { - NSLog(@"new achievement %@ reported", achievement.identifier); - achievement.percentComplete = percent; - [achievement reportAchievementWithCompletionHandler:^(NSError* error) - { - [delegate onReportAchievement:(GKAchievement*)achievement]; - }]; - - [self saveAchievements]; - } + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(onLocalPlayerAuthenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil]; } --(GKAchievement*) getAchievement:(NSString*)identifier +// ----------------------------------------------------------------- +#pragma mark e) Getters +// ----------------------------------------------------------------- + +- (GKScore*) getScoreByCategory:(NSString*)category { - GKAchievement* achievement = [achievements objectForKey:identifier]; + GKScore* score = [_scores objectForKey:category]; - if (achievement == nil) + if (score == nil) { - achievement = [[[GKAchievement alloc] initWithIdentifier:identifier] autorelease]; - [achievements setObject:achievement forKey:achievement.identifier]; + score = [[GKScore alloc] initWithCategory:category]; + score.value = 0; + [_scores setObject:score forKey:category]; } - return achievement; -} - -- (void)loadAchievementDescriptions -{ - NSLog(@"loading achievement descriptions"); - - [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *achievementDesc, NSError *error) - { - achievementDescriptions = [[NSMutableDictionary alloc] init]; - - if (error != nil) - { - NSLog(@"unable to load achievements"); - return; - } - - for (GKAchievementDescription *description in achievementDesc) - { - [achievementDescriptions setObject:description forKey:description.identifier]; - } - - NSLog(@"achievement descriptions initialized: %d", achievementDescriptions.count); - }]; + return score; } --(GKAchievementDescription*) getAchievementDescription:(NSString*)identifier -{ - GKAchievementDescription* description = [achievementDescriptions objectForKey:identifier]; - return description; -} +// ----------------------------------------------------------------- --(void) resetAchievements +- (GKAchievement*) getAchievement:(NSString*)identifier { - if (isGameCenterAvailable == NO) - return; + GKAchievement* achievement = [_achievements objectForKey:identifier]; - [achievements removeAllObjects]; - [self saveAchievements]; - - [GKAchievement resetAchievementsWithCompletionHandler:^(NSError* error) {}]; + if (achievement == nil) + { + achievement = [[GKAchievement alloc] initWithIdentifier:identifier]; + [_achievements setObject:achievement forKey:achievement.identifier]; + } - NSLog(@"achievements reset"); + return achievement; } --(UIViewController*) getRootViewController +// ----------------------------------------------------------------- + +- (UIViewController*) getRootViewController { return [UIApplication sharedApplication].keyWindow.rootViewController; } --(void) presentViewController:(UIViewController*)vc +// ----------------------------------------------------------------- +#pragma mark f) View Controllers +// ----------------------------------------------------------------- + +- (void) presentViewController:(UIViewController*)vc { UIViewController* rootVC = [self getRootViewController]; [rootVC presentModalViewController:vc animated:YES]; } --(void) dismissModalViewController +// ----------------------------------------------------------------- + +- (void) dismissModalViewController { UIViewController* rootVC = [self getRootViewController]; [rootVC dismissModalViewControllerAnimated:YES]; } --(void) showGameCenter -{ - if (isGameCenterAvailable == NO) - { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; - [alert show]; - [alert release]; - - return; - } - - if ([GKGameCenterViewController class]) - { - GKGameCenterViewController *gameCenterController = [[[GKGameCenterViewController alloc] init] autorelease]; - if (gameCenterController != nil) - { - gameCenterController.gameCenterDelegate = self; - [self presentViewController:gameCenterController]; - } - } - else - { - [self showLeaderboard]; - } -} +// ----------------------------------------------------------------- -- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController +- (void) gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController { [self dismissModalViewController]; } --(void) showLeaderboard -{ - if (isGameCenterAvailable == NO) - { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; - [alert show]; - [alert release]; - - return; - } - - GKLeaderboardViewController* leaderboardVC = [[[GKLeaderboardViewController alloc] init] autorelease]; - if (leaderboardVC != nil) - { - leaderboardVC.leaderboardDelegate = self; - [self presentViewController:leaderboardVC]; - } -} +// ----------------------------------------------------------------- --(void) showLeaderboardwithCategory:(NSString*)category timeScope:(int)tscope -{ - if (isGameCenterAvailable == NO) - { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; - [alert show]; - [alert release]; - - return; - } - - GKLeaderboardViewController* leaderboardVC = [[[GKLeaderboardViewController alloc] init] autorelease]; - if (leaderboardVC != nil) - { - leaderboardVC.leaderboardDelegate = self; - leaderboardVC.category = category; - leaderboardVC.timeScope = tscope; - [self presentViewController:leaderboardVC]; - } -} - --(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController*)viewController +- (void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController*)viewController { [self dismissModalViewController]; } --(void) showAchievements -{ - if (isGameCenterAvailable == NO) - { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Game Center" message:@"Game Center is not available" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil, nil]; - [alert show]; - [alert release]; - - return; - } - - GKAchievementViewController* achievementsVC = [[[GKAchievementViewController alloc] init] autorelease]; - if (achievementsVC != nil) - { - achievementsVC.achievementDelegate = self; - [self presentViewController:achievementsVC]; - } -} +// ----------------------------------------------------------------- --(void) achievementViewControllerDidFinish:(GKAchievementViewController*)viewController +- (void) achievementViewControllerDidFinish:(GKAchievementViewController*)viewController { [self dismissModalViewController]; } -- (int) numberOfTotalAchievements -{ - int count = 0; - if (isGameCenterAvailable) - { - count = [achievementDescriptions allValues].count; - } - return count; -} +// ----------------------------------------------------------------- +#pragma mark g) Other +// ----------------------------------------------------------------- -- (int) numberOfCompletedAchievements +- (NSString *) returnMD5Hash:(NSString*)concat { - int count = 0; - if (isGameCenterAvailable) + const char *concat_str = [concat UTF8String]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + CC_MD5(concat_str, strlen(concat_str), result); + NSMutableString *hash = [NSMutableString string]; + for (int i = 0; i < 16; i++) { - NSArray* gcAchievementsArray = [achievements allValues]; - for (GKAchievement* gcAchievement in gcAchievementsArray) - { - if (gcAchievement.completed) - count++; - } + [hash appendFormat:@"%02X", result[i]]; } - return count; + + return [hash lowercaseString]; } +// ----------------------------------------------------------------- @end +// ----------------------------------------------------------------- diff --git a/DDGameKitHelperDelegate.m b/DDGameKitHelperDelegate.m index 52b1a2f..069e45c 100644 --- a/DDGameKitHelperDelegate.m +++ b/DDGameKitHelperDelegate.m @@ -2,29 +2,48 @@ // DDGameKitHelperDelegate.h // Version 1.0 +// ----------------------------------------------------------------- +#define DDGAMEKIT_USE_NOTIFICATION 1 +// ----------------------------------------------------------------- + #import "DDGameKitHelperDelegate.h" +#if DDGAMEKIT_USE_NOTIFICATION == 1 #import "GKAchievementHandler.h" +#endif +// ----------------------------------------------------------------- @implementation DDGameKitHelperDelegate +// ----------------------------------------------------------------- -// return true if score1 is greater than score2 -// modify this if your scoreboard is reversed (low scores on top) --(bool) compare:(int64_t)score1 to:(int64_t)score2 +// Returns 'true' if score1 is greater than score2 +// Modify this if your scoreboard is reversed (lowest scores first) +// For example - a lap time in a racer game (the lower the better) +- (BOOL) compareScore:(int64_t)score1 toScore:(int64_t)score2 { return score1 > score2; } -// display new high score using GKAchievement class --(void) onSubmitScore:(int64_t)score; +// ----------------------------------------------------------------- + +// If enabled, display new high score notification using GKAchievementHandler +- (void) onSubmitScore:(int64_t)score; { - [[GKAchievementHandler defaultHandler] notifyAchievementTitle:@"New High Score!!!" andMessage:[NSString stringWithFormat:@"%d", score]]; +#if DDGAMEKIT_USE_NOTIFICATION == 1 + [[GKAchievementHandler defaultHandler] notifyAchievementTitle:@"New High Score!" andMessage:[NSString stringWithFormat:@"%d", (int)score]]; +#endif } -// display the achievement using GKAchievement class --(void) onReportAchievement:(GKAchievement*)achievement +// ----------------------------------------------------------------- + +// If enabled, display achievement notification using GKAchievementHandler +- (void) onReportAchievement:(GKAchievement*)achievement { +#if DDGAMEKIT_USE_NOTIFICATION == 1 DDGameKitHelper* gkHelper = [DDGameKitHelper sharedGameKitHelper]; [[GKAchievementHandler defaultHandler] notifyAchievement:[gkHelper getAchievementDescription:achievement.identifier]]; +#endif } +// ----------------------------------------------------------------- @end +// ----------------------------------------------------------------- \ No newline at end of file diff --git a/README b/README.md similarity index 54% rename from README rename to README.md index 1d7720f..de4552a 100644 --- a/README +++ b/README.md @@ -1,7 +1,13 @@ +DDGameKitHelper +=============== + A simpler GameKitHelper inspired by Steffen Itterheim's version (http://www.learn-cocos2d.com). This version takes a different approach by synchronizing a local cache with game center and visa versa. +Story +--------------- + I was having a lot of troubles getting Steffen's library to work nicely on iOS 4.2 devices. For one it was trying to write to the root bundle directory. I've switch it to write to /Library. @@ -21,12 +27,13 @@ reports the score each time (so that daily and weekly comparisons work), it's only cached locally if the high score has been beat. It also displays a message banner. +Also, I've implemented a cache per game center user. + DDGameKitHelper only deals with achievements and scores. Since none of -my games use multiplayer I didn't try to tackle an api for that. I also -have not tackled someone else signing in to game center. Right now I -think everything locally would synch with the new account, which really -isn't what you want neccessarily. So I will be working on a cache per -user. (UPDATE: I've implemented this) +my games use multiplayer I didn't try to tackle an api for that. + +Dependencies +--------------- The DDGameKitHelperDelegate class is dependent on Benjamin Borowski's GKAchievementNotification class. @@ -37,55 +44,88 @@ It does an excellent job of display a slide down notification that fits in seamlessly with game center. The only thing I needed to add to it was an adjustFrame method to compensate for the iPad. -USING IT +If you don't want to use it, then change pre-processor macro `DDGAMEKIT_USE_NOTIFICATION` to `0` +in `DDGameKitHelperDelegate.m` file. -Authenticating a player ------------------------ +ARC Support +--------------- -[[DDGameKitHelper sharedGameKitHelper] authenticateLocalPlayer]; +This class has been converted to ARC, however still works well with `-fobjc-arc` compiler flag in non-ARC projects. -Checking authentication ------------------------ +Installation +------------ -[[DDGameKitHelper sharedGameKitHelper] isLocalPlayerAuthenticated]; +1. Add the `GameKit` framework to your Xcode project + +2. Add the following files to your Xcode project (make sure to select Copy Items in the dialog): + - DDGameKitHelper.h + - DDGameKitHelper.m + - DDGameKitHelperDelegate.h + - DDGameKitHelperDelegate.m -Unlocking an achievement ------------------------- +3. Import the `DDGameKitHelper.h` file +Usage +----------------------- +###Initializing +You should call this prefferably in the `application:didFinishLaunchingWithOptions:` method, when starting your app. +
+[DDGameKitHelper sharedGameKitHelper];
+
+###Authentication +Handles authentication of the game center user and creates a cache per each new/different user. + +Shows the default game center authentication dialog. +
+[[DDGameKitHelper sharedGameKitHelper] authenticateLocalPlayer];
+
+Returns a `BOOL` depending on player authentication. +
+[[DDGameKitHelper sharedGameKitHelper] isLocalPlayerAuthenticated];
+
+###Achievements +Handles reporting and showing of achievements. +
 [[DDGameKitHelper sharedGameKitHelper] reportAchievement:@"1"
 percentComplete:100];
-
-Reporting a score 
------------------
-
+
+Shows the default game center view with the achievements section opened. +
+[[DDGameKitHelper sharedGameKitHelper] showAchievements];
+
+###Leaderboard +Handles reporting and showing leaderboard score. +
 [[DDGameKitHelper sharedGameKitHelper] submitScore:newscore
 category:@"1"];
-
-Showing achievements 
---------------------
-
-[[DDGameKitHelper sharedGameKitHelper] showAchievements];
-
-Showing scores 
---------------
-
+
+Shows the default game center view with the leaderboards section opened. +
 [[DDGameKitHelper sharedGameKitHelper] showLeaderboard];
-
+
+Shows the default game center view with the leaderboards section opened and specific time scope selected. +Besides `GKLeaderboardTimeScopeAllTime` you can also use `GKLeaderboardTimeScopeToday` and `GKLeaderboardTimeScopeWeek`. +
 [[DDGameKitHelper sharedGameKitHelper] showLeaderboardwithCategory:@"LeaderboardID" timeScope:GKLeaderboardTimeScopeAllTime];
-where GKLeaderboardTimeScopeAllTime is also available in GKLeaderboardTimeScopeToday and GKLeaderboardTimeScopeWeek
-
-Resetting achievements 
-----------------------
-
+
+###Resetting achievements +
 [[DDGameKitHelper sharedGameKitHelper] resetAchievements];
+
+Logging +---------- +You can change if DDGameKitHelper should log messages to the output. +Setting `DDGAMEKIT_LOGGING` (in `DDGameKitHelper.h`) pre-processor macro to `0` or `1` will disable or enable logging. -SUMMARY +Summary +---------- I know all of this functionality is available in iOS 5.x, but I want to -still support my 4.x users. This library plays nicely with iOS 4.x and +still support my 4.x users. +This library plays nicely with iOS 4.x and 5.x. -Doug Davies -Owner, Funky Visions +Doug Davies +Owner, Funky Visions www.funkyvisions.com