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).
The game model needs a new public method returning matching cards. For this task a method returning a boolean result would be sufficient, but we will use the same method for the next task:
- (NSArray *)findCombination;
First generate a potential combination, then check if that combination is valid (meaning it does not contain cards which are used already by previous matches) and check if the current combination is matching. Continue as long as there is no match or no further combination available:
- (NSArray *)findCombination { Card *card = [self.cards firstObject]; NSMutableArray *combination = [NSMutableArray array]; for (NSUInteger i = 0; i < card.numberOfMatchingCards; i++) { [combination addObject:@(i)]; } NSArray *foundCombination; NSArray *nextCombination = combination; do { if (![self validCombination:nextCombination]) continue; if ([self.cards[[nextCombination[0] intValue]] match:[self otherCardsFromCombination:nextCombination]]) { foundCombination = [self cardsFromCombination:nextCombination]; break; } } while ((nextCombination = [self nextCombinationAfter:nextCombination withNumberOfCards:card.numberOfMatchingCards])); return foundCombination; }
The method above used various helper methods to create more structured code. For a valid combination check if its cards have not been used for previous matches:
- (BOOL)validCombination:(NSArray *)combination { for (NSNumber *index in combination) { Card *card = self.cards[[index intValue]]; if (card.matched) return NO; } return YES; }
Because the combinations are arrays of indexes (which makes the calculation of permutations much easier), its necessary to derive the actual cards from those indexes:
- (NSArray *)cardsFromCombination:(NSArray *)combination startinWithIndex:(NSUInteger)start { NSMutableArray *cards = [[NSMutableArray alloc] init]; for (NSUInteger i = start; i < [combination count]; i++) { [cards addObject:self.cards[[combination[i] intValue]]]; } return cards; }
Once this method is needed for all cards, and once for all but the first card:
- (NSArray *)cardsFromCombination:(NSArray *)combination { return [self cardsFromCombination:combination startinWithIndex:0]; } - (NSArray *)otherCardsFromCombination:(NSArray *)combination { return [self cardsFromCombination:combination startinWithIndex:1]; }
Finally, the last helper method generates new combinations based on permutations of the previous combination:
- (NSArray *)nextCombinationAfter:(NSArray *)combination withNumberOfCards:(NSUInteger)numberOfCards { NSUInteger n = [self.cards count]; NSUInteger k = numberOfCards; NSUInteger 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; }
The score is calculated inside the game model. To adjust for the penalty, when adding new cards though there would have been valid matches, introduce a new property:
@property (nonatomic) NSInteger scoreAdjustment;
Which is used when setting the score label:
- (void)updateUI { ... self.scoreLabel.text = [NSString stringWithFormat:@"Score: %ld", (long)(self.game.score + self.scoreAdjustment)]; ... }
The penalty is added when there would have been valid combinations. In addition produce an alert message, when the deck is empty and no possible matches are left:
- (IBAction)touchAddCardsButton:(UIButton *)sender { if ([[self.game findCombination] count]) { self.scoreAdjustment -= self.gameSettings.mismatchPenalty * sender.tag; }; ... if (self.game.deckIsEmpty) { //sender.enabled = NO; //sender.alpha = 0.5; if (![[self.game findCombination] count]) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"No matches left ..." delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Game Over!", nil]; [alert show]; } } ... }
The code above produces the game-over alert only when the “+3” button has been pressed (Which the tasks actually wanted us to do …). However it might be nicer to produce this alert as soon as there a no valid matches left – not depending on pressing an additional button …
The complete code is available on github.
![Share on Facebook Facebook](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/facebook.png)
![Share on Twitter twitter](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/twitter.png)
![Share on Reddit reddit](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/reddit.png)
![Pin it with Pinterest pinterest](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/pinterest.png)
![Share on Linkedin linkedin](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/linkedin.png)
![Share on tumblr tumblr](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/tumblr.png)
![Share by email mail](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/16x16/mail.png)
![Share on Facebook Facebook](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/facebook.png)
![Share on Twitter twitter](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/twitter.png)
![Share on Reddit reddit](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/reddit.png)
![Pin it with Pinterest pinterest](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/pinterest.png)
![Share on Linkedin linkedin](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/linkedin.png)
![Share on tumblr tumblr](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/tumblr.png)
![Share by email mail](https://cs193p.m2m.at/wp-content/plugins/social-media-feather/synved-social/image/social/regular/32x32/mail.png)