Please note, this blog entry is from a previous course. You might want to check out the current one.
You could add better score-keeping to the Set part of this application if you can figure out an algorithm for calculating whether a Set exists in the current cards in play. Then you can penalize the user not only for mismatches, but for clicking the “deal 3 more cards” button if he or she missed a Set. You’d also know when the game was “over” (because the user would click on “deal 3 more cards” and there would be no more cards in the deck and no more Sets to choose).
Create a new public method in the card-matching-game model which returns the first set of matching cards. The first possible combination of cards is just the first cards. Generate an array of their indexes with a simple for loop. Then check if the combination is valid (e.g. contains no unplayable card). Separate the first card and from the others using helper functions to check if they match. If they do, return those cards otherwise get the next possible combination, and start again with the checks:
// CardMatchingGame.h - (NSArray *)matchingCards; // CardMatchingGame.m - (NSArray *)matchingCards { NSMutableArray *combination = [[NSMutableArray alloc] init]; for (int i = 0; i < self.numberOfMatchingCards; i++) { [combination addObject:@(i)]; } NSArray *matchedCards; NSArray *nextCombination = combination; do { if (![self validCombination:nextCombination]) continue; if ([self.cards[[nextCombination[0] intValue]] match:[self otherCardsFromCombination:nextCombination]]) { matchedCards = [self cardsFromCombination:nextCombination]; break; } } while ((nextCombination = [self nextCombinationAfter:nextCombination])); return matchedCards; }
Like described above a valid combination just contains no unplayable cards (which is not possible for the set game but you can use this code also for the playing-card matching game):
- (BOOL)validCombination:(NSArray *)combination { for (NSNumber *index in combination) { Card *card = self.cards[[index intValue]]; if (card.isUnplayable) return NO; } return YES; }
To return cards for the given combinations use simple helper functions which just loop over the indexes and add those cards:
- (NSArray *)cardsFromCombination:(NSArray *)combination startinWithIndex:(int)start { NSMutableArray *cards = [[NSMutableArray alloc] init]; for (int i = start; i < [combination count]; i++) { [cards addObject:self.cards[[combination[i] intValue]]]; } return cards; } - (NSArray *)cardsFromCombination:(NSArray *)combination { return [self cardsFromCombination:combination startinWithIndex:0]; } - (NSArray *)otherCardsFromCombination:(NSArray *)combination { return [self cardsFromCombination:combination startinWithIndex:1]; }
The helper function to get the next possible combination is the tricky part of this task. Increase the last index of the given combination. If this results in a invalid value (because it would point to a non-existing card) move to the previous card and change the previous card. Do this till you get a valid combination, or you ended up at the last possible value for the first index. If you end up with an invalid combination, return nil. Finally the found value needs to be normalized and returned …
- (NSArray *)nextCombinationAfter:(NSArray *)combination { int n = [self.cards count]; int k = self.numberOfMatchingCards; int i = k - 1; NSMutableArray *next = [combination mutableCopy]; next[i] = @([next[i] intValue] + 1); while ((i > 0) && ([next[i] intValue] > n - k + i)) { i--; next[i] = @([next[i] intValue] + 1); } if ([next[0] intValue] > n - k) return nil; for (i = i + 1; i < k; ++i) { next[i] = @([next[i - 1] intValue] + 1); } return next; }
To be able to use this code to adjust the score from outside the model (note that it is read only at the moment), change it to be writable from the API (… however it would be much “nicer” to add an additional API method to handle this “safely”):
// CardMatchingGame.h @property (nonatomic) int score;
Finally adjust the code for the add-cards button. Before the new cards are added check for possible matches and add the penalty (you might also want to add a suitable message for the player).
After the cards have been added and the deck is empty check again and send a “game over” alert:
- (IBAction)addCardsButtonPressed:(UIButton *)sender { if ([[self.game matchingCards] count]) { self.game.score -= self.gameSettings.mismatchPenalty * sender.tag; self.gameResult.score = self.game.score; [self updateUI]; } ... if (self.game.deckIsEmpty) { ... if (![[self.game matchingCards] count]) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"No matches left ..." delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Game Over!", nil]; [alert show]; } } }
The complete code is available on github.