cs193p – Assignment #3 Task #10 & #11

Please note, this blog entry is from a previous course. You might want to check out the current one.

If there are more cards than will fit on the screen, simply allow the user to scroll down to see the rest of the cards. Pick a fixed (and reasonable) size for your cards and keep them that size for the whole game.

… which the code already does by default …

It is very important that you continue to have a “last flip status” UI and that it show not only matches and mismatches, but also which cards are currently selected (because there can be so many cards now that you have to scroll to get to all the cards in a match). A UILabel may no longer be sufficient for this UI.

To rewrite the current status label the updateUI method has to be overwritten by the set-game view controller. Do display the card symbols in the status label, we will add a subview for each card. At the start we make sure there is no subview from a previous status display. If there are any we loop over each of them and remove it.

There are three different kinds of status messages – that a card has been flipped, cards matched, or cards did not match. Check the current label for the kind of status message.

If a card has been flipped, loop over all cards and collect all which are face up (selected) at the moment. For the other two states this is not possible because the cards have already been removed, or not selected any more. Thus the effected cards have to recovered from the current status string. Finally those cards can be displayed together with an appropriate message:

- (void)updateUI
    [super updateUI];
    for (UIView *view in self.resultOfLastFlipLabel.subviews) {
        [view removeFromSuperview];
    if (self.game.descriptionOfLastFlip) {
        if ([self.game.descriptionOfLastFlip rangeOfString:@"Flipped up"].location != NSNotFound) {
            NSMutableArray *setCards = [[NSMutableArray alloc] init];
            for (int i = 0; i < self.game.numberOfCards; i++) {
                Card *card = [self.game cardAtIndex:i];
                if (card.isFaceUp && [card isKindOfClass:[SetCard class]])
                    [setCards addObject:(SetCard *)card];
            [self updateUILabel:self.resultOfLastFlipLabel withText:@"Flipped up: " andSetCards:setCards];
        } else if ([self.game.descriptionOfLastFlip rangeOfString:@"Matched"].location != NSNotFound) {
            [self updateUILabel:self.resultOfLastFlipLabel withText:@"✅ "
                    andSetCards:[self setCardsFromString:self.game.descriptionOfLastFlip]];
        } else {
            [self updateUILabel:self.resultOfLastFlipLabel withText:@"❌ "
                    andSetCards:[self setCardsFromString:self.game.descriptionOfLastFlip]];
    [self.resultOfLastFlipLabel setNeedsDisplay];    

Recovering cards from the status string can be achieved by pattern matching using regular expressions. First the pattern (matching the format of a in the status string) is generated using the class methods from setCard.

After generating the regular expression and search through the string, the result is an array of “search results” holding the ranges of the found substrings. These are used to generate a new set card … and finally an array set cards contained in the status string:

- (NSArray *)setCardsFromString:(NSString *)string
    NSString *pattern = [NSString stringWithFormat:@"(%@):(%@):(%@):(\\d+)",
                         [[SetCard validSymbols] componentsJoinedByString:@"|"],
                         [[SetCard validColors] componentsJoinedByString:@"|"],
                         [[SetCard validShadings] componentsJoinedByString:@"|"]];
    NSError *error = NULL;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
    if (error) return nil;
    NSArray *matches = [regex matchesInString:string
                                        range:NSMakeRange(0, [string length])];
    if (![matches count]) return nil;
    NSMutableArray *setCards = [[NSMutableArray alloc] init];
    for (NSTextCheckingResult *match in matches) {
        SetCard *setCard = [[SetCard alloc] init];
        setCard.symbol = [string substringWithRange:[match rangeAtIndex:1]];
        setCard.color = [string substringWithRange:[match rangeAtIndex:2]];
        setCard.shading = [string substringWithRange:[match rangeAtIndex:3]];
        setCard.number = [[string substringWithRange:[match rangeAtIndex:4]] intValue];
        [setCards addObject:setCard];
    return setCards;

The “appropriate” message string together with the found cards are used to update the label. First the string is set as label text. Its width is used to find the position for the first card symbol. Looping over all cards the are added as subviews to the label:

- (void)updateUILabel:(UILabel *)label withText:(NSString *)text andSetCards:(NSArray *)setCards
    if ([setCards count]) {
        label.text = text;
        CGFloat x = [label.text sizeWithFont:label.font].width;
        for (SetCard *setCard in setCards) {
            [self addSetCard:setCard toView:label atX:x];
            x += label.bounds.size.height * LASTFLIP_CARD_OFFSET_FACTOR;
    } else label.text = @"";

… where the actual subview adding is realized in another helper method, which also sets up the set-card view:

- (void)addSetCard:(SetCard *)setCard toView:(UIView *)view atX:(CGFloat)x
    CGFloat height = self.resultOfLastFlipLabel.bounds.size.height;
    SetCardView *setCardView = [[SetCardView alloc] initWithFrame:CGRectMake(x, 0, height, height)];
    setCardView.color = setCard.color;
    setCardView.symbol = setCard.symbol;
    setCardView.shading = setCard.shading;
    setCardView.number = setCard.number;
    setCardView.backgroundColor = [UIColor clearColor];
    [view addSubview:setCardView];

The complete code is available on github.


Leave a Reply

Your email address will not be published.