cs193p – Assignment #2 Task #3

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

Drag out a switch (UISwitch) or a segmented control (UISegmentedControl) into your View somewhere which controls whether the game matches two cards at a time or three cards at a time (i.e. it sets “2-card-match mode” vs. “3-card-match mode”). Give the user appropriate points depending on how difficult the match is to accomplish. In 3-card-match mode, it should be possible to get some (although a significantly lesser amount of) points for picking 3 cards of which only 2 match in some way. In that case, all 3 cards should be taken out of the game (even though only 2 match). In 3-card-match mode, choosing only 2 cards is never a match.

Start by dragging out a segmented control element onto your story board and change its tint in order to make it more visible:

cs193p – assignment #2 task #3 - mode selector
cs193p – assignment #2 task #3 – mode selector


Connect it to an outlet property as well as to an action. When the control element changes its value the action is called. There you could either use the index of the selected segment (which would be 0 or 1) to know the current mode (2 or 3 cards matching). Here I chose a different approach to use the title of the selected segment. Because its a string, it is converted to an integer and assigned to a not yet existing public property of the game object:

@property (weak, nonatomic) IBOutlet UISegmentedControl *modeSelector;
...
- (IBAction)changeModeSelector:(UISegmentedControl *)sender {
    self.game.maxMatchingCards = 
        [[sender titleForSegmentAtIndex:sender.selectedSegmentIndex] integerValue];
}

The outlet property is needed when the deal button is pressed to update the mode of the new game during lazy instantiation:

- (CardMatchingGame *)game
{
    if (!_game) {
        ...
        [self changeModeSelector:self.modeSelector];
    }
    return _game;
}

Create this public property for the card-matching-game class. Because it makes no sense for this value to be less than two use lazy instantiation to validate and set its initial value:

// CardMatchingGame.h
@property (nonatomic) NSUInteger maxMatchingCards;

// CardMatchingGame.m
- (NSUInteger)maxMatchingCards
{
    if (_maxMatchingCards < 2) {
        _maxMatchingCards = 2;
    }
    return _maxMatchingCards;
}

The current card-matching-game class was only able to match a single other card. Instead of creating an array with a single card in it, enumerate over all cards and create an array from selected but not yet matched cards. Only if the number of cards currently chosen is suitable for the matching mode, test for the matching score. Previously only a single other card had to be turned or set as matched, now it is necessary to loop over all other “chosen” cards to update their settings:

- (void)chooseCardAtIndex:(NSUInteger)index
{
    ...
        } else {
            NSMutableArray *otherCards = [NSMutableArray array];
            for (Card *otherCard in self.cards) {
                if (otherCard.isChosen && !otherCard.isMatched) {
                    [otherCards addObject:otherCard];
                }
            }
            if ([otherCards count] + 1 == self.maxMatchingCards) {
                int matchScore = [card match:otherCards];
                if (matchScore) {
                    self.score += matchScore * MATCH_BONUS;
                    card.matched = YES;
                    for (Card *otherCard in otherCards) {
                        otherCard.matched = YES;
                    }
                } else {
                    self.score -= MISMATCH_PENALTY;
                    for (Card *otherCard in otherCards) {
                        otherCard.chosen = NO;
                    }
                }
            }
            ...
}

Finally we need to change the matching algorithm of the playing-card class. Instead of matching only against a single card loop over all “other” cards. Instead of setting a fixed score, increase the current score if there is a match. Because there could also a match within two “other” cards (if there are at least two) remove the first card from the array and run a match for that card against the remaining cards. Because we need the number of objects in the cards array quite a lot, create a helper variable with the card count:

- (int)match:(NSArray *)otherCards
{
    ...
    int numOtherCards = [otherCards count];    
    if (numOtherCards) {
        for (Card *card in otherCards) {
                ...
                    score += 1;
                ...
                    score += 4;
                ...
    if (numOtherCards > 1) {
        score += [[otherCards firstObject] match:
            [otherCards subarrayWithRange:NSMakeRange(1, numOtherCards - 1)]];
    }
    ...
}

The suggestion from hint #7 is not included in the code above. Matching two cards provides the same amount of points in both modes … That it is actually easy to get a match when you are allowed to flip more cards, is only reflected by the “cost to choose” …

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

21 thoughts on “cs193p – Assignment #2 Task #3”

  1. Hi there,

    This solution doesn’t completely solve the problem. In a three card game if a 2 card match happens all 3 cards will be set as .matched.

    1. That’s what’s supposed to happen, per the instructions on Req. 3: “all 3 cards should be taken out of the game (even though only 2 match).”

  2. Your working out and instructions are very good. But just one thing, can you please give an alternate solution to:

    if (numOtherCards > 1) {
    score += [[otherCards firstObject] match:
    [otherCards subarrayWithRange:NSMakeRange(1, numOtherCards – 1)]];

    Just seems like thats something you knew from experience instead of from the lecture notes which I am trying to follow 😛

    1. Well, the matching method (from the lecture), matches a card against all other flipped cards. As the MilaDeveloper mentions above, recursion is the easiest solution 😉

      As long as there are at least two cards left, call the method again using the first of those cards ([otherCards firstObject]) to match it against “the rest” of those cards. In the code above I create a new sub array skipping the first cards (which would have the index 0) starting with the card with the index 1 and reduce the number of elements by one.

      An alternative solution would be to use loops, within loops, withing loops … I don’t think that would be nicer, or more comprehensible, … and was not covered in the lectures, too 😉

      1. eh, a week on and I still can’t solve this. Do you consider this task “difficult”? and will it be damaging to my learning if I just skip this task and take a lower grade (I consider u the teacher lol)?

        1. I won’t grade you 😉 and you are not a Stanford client, so there will be no grades. Recursion is not a problem specific to iOS so it will not hinder your further success there. However, you might have a look at cs106a, if some of the concepts of the course are completely new to you.

  3. Hi!

    Just to be sure I’m not seeing it wrong, in this “for loop”
    for (Card *card in otherCards) {
    ...
    score += 1;
    ...
    score += 4;
    ...

    the ellipsis are the “if”s to compare rank and suit, right? In that case, either the type of the variable card in the for-in declaration is changed to PlayingCard or it has to be explicitly casted to that (since it’s the header that declares those properties) inside the loop, isn’t it?

    1. … which the original code from the lecture already does, and thus the code is not repeated here. Does this answer your question, or did I understand it in a wrong way?

  4. If you don’t like recursion, I did this in my solution:
    int score = 0;
    // creating a new array containing all PlayingCards (so also the Card within self. This new allCards array then will be used within a while loop. This while loop will take each time one PlayingCard and compare it to the other PlayingCards. If a match arises, the score is augmented.
    NSMutableArray *allCards = [[NSMutableArray alloc]initWithArray:otherCards];
    [allCards addObject:self];

    // when allCards is equal to 1 it make no sense to do the comparison any longer as there will be no longer any PlayingCards to compare to
    while ([allCards count]>1)
    {
    PlayingCard *cardToBeComparedTo = [allCards lastObject];
    [allCards removeLastObject];
    for (PlayingCard *otherCard in allCards)
    {
    if ([cardToBeComparedTo.suit isEqualToString:otherCard.suit])
    {
    score += 1;
    } else if(cardToBeComparedTo.rank == otherCard.rank)
    {
    score += 4;
    }
    }
    }
    return score;

  5. for – (int)match:(NSArray *)otherCards in PlayingCards.m, would the following work? thanks!

    – (int) match:(NSArray *)otherCards
    {
    int score = 0;
    while ([otherCards count] > 0) {
    PlayingCard *otherCard = [otherCards firstObject];
    if ([self.suit isEqualToString:otherCard.suit]) {
    score += 1;
    } else if (self.rank == otherCard.rank) {
    score += 4;
    }
    otherCards = [otherCards subarrayWithRange:NSMakeRange(1, [otherCards count] – 1)];
    }
    return score;
    }

    1. My code uses two loops. The first loop compares the current card with all other cards. Next the current card is removed and the whole test is repeated for that subset of cards (using recursion). Your code seems to have only a single loop?

      Otherwise, you don’t have to use recursion, and using a while loop instead of a for loop is perfectly fine 😉

  6. there is little bit easier solution for matching:

    -(int)match:(NSArray *)otherCards{
    int score = 0;

    for (PlayingCard *otherCard in otherCards) {
    if (self.rank == otherCard.rank) {
    score += 4;
    } else if ([self.suit isEqualToString:otherCard.suit]){
    score += 1;
    }
    }

    if (score == 0) {
    PlayingCard *otherCard = [otherCards firstObject];
    score = [otherCard match:@[[otherCards objectAtIndex:[otherCards count]-1]]];
    }

    return score;
    }

      1. actually i posted before i test it. lol
        but if you change “if (score == 0)” to “([other Cards count] > 1)” it will work properly:
        it will match 3 card against each other if possible or only two if 3rd(or 1st or 2nd) card didn’t match (for example: in case you have 3 card with same suit you will gain less scores, then if you have 2 cards with same ranks and another one which didn’t match)
        if you want match more then 3 card you’d probably need use “for in”

        corrected code:
        ——————
        -(int)match:(NSArray *)otherCards{
        int score = 0;

        for (PlayingCard *otherCard in otherCards) {
        if (self.rank == otherCard.rank) {
        score += 4;

        NSLog(@”%@ and %@ matched by rank”, self.contents, otherCard.contents);
        } else if ([self.suit isEqualToString:otherCard.suit]){
        score += 1;

        NSLog(@”%@ and %@ matched by suit”, self.contents, otherCard.contents);

        }
        }

        if ([otherCards count] > 1) {
        PlayingCard *otherCard = [otherCards firstObject];
        score += [otherCard match:@[[otherCards objectAtIndex:[otherCards count]-1]]];
        }

        return score;
        }
        ——
        you can run it and see how it works by NSLog

Leave a Reply

Your email address will not be published.