cs193p – Assignment #1 Task #5

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.

At the moment (end of lecture #3) the match: method PlayingCard model only supports matches with a single other card. Instead of checking if there is a single card, check if there are other cards (one or more). To adjust the score (3-card matches are more worth then 2-card matches) change the score calculation to add the bonus for each matching card (which might not be accurate concerning the actual odds, …):

- (int)match:(NSArray *)otherCards
{
    int score = 0;    
    if ([otherCards count]) {
        for (PlayingCard *otherCard in otherCards) {
            if ([otherCard.suit isEqualToString:self.suit]) {
                score += 1;
            } else if (otherCard.rank == self.rank) {
                score += 4;
            } else {
                score = 0;
                break;
            }
        }
    }
    return score;
}

UPDATE: Sadly, the code above does not work! Additionally it is necessary to add a variable to check if the “kind of matching” (rank or suits) is the same for each iteration …

For the CardMatchingGame model to know which mode is set, add a new property:

@property (nonatomic) int numberOfMatchingCards;

The getter can be used to “lazy instantiate” to a default value of 2:

- (int)numberOfMatchingCards
{
    if (!_numberOfMatchingCards) {
        _numberOfMatchingCards = 2;
    }
    return _numberOfMatchingCards;
}

The setter can be used to check its validity:

- (void)setNumberOfMatchingCards:(int)numberOfMatchingCards
{
    if (numberOfMatchingCards < 2) _numberOfMatchingCards = 2;
    else if (numberOfMatchingCards > 3) _numberOfMatchingCards = 3;
    else _numberOfMatchingCards = numberOfMatchingCards;
}

… and because both, getter and setter, are “overwritten”, it is necessary to synthesize the property:

@synthesize numberOfMatchingCards = _numberOfMatchingCards;

flipCardAtIndex: needs adjustment to cope with both (or more) modes. First create two arrays by looping over all cards to see witch one is face up. At the end one array will hold those cards, the other one will contain their contents which is used to generate the text messages.

- (void)flipCardAtIndex:(NSUInteger)index
{
    Card *card = [self cardAtIndex:index];
    
    if (card && !card.isUnplayable) {
        if (!card.isFaceUp) {
            NSMutableArray *otherCards = [[NSMutableArray alloc] init];
            NSMutableArray *otherContents = [[NSMutableArray alloc] init];
            for (Card *otherCard in self.cards) {
                if (otherCard.isFaceUp && !otherCard.isUnplayable) {
                    [otherCards addObject:otherCard];
                    [otherContents addObject:otherCard.contents];
                }
            }
            if ([otherCards count] < self.numberOfMatchingCards - 1) {
                self.descriptionOfLastFlip = [NSString 
                  stringWithFormat:@"Flipped up %@", card.contents];
            } else {
                int matchScore = [card match:otherCards];
                if (matchScore) {
                    card.unplayable = YES;
                    for (Card *otherCard in otherCards) {
                        otherCard.unplayable = YES;                        
                    }
                    self.score += matchScore * MATCH_BONUS;              
                    self.descriptionOfLastFlip =
                        [NSString stringWithFormat:@"Matched %@ & %@ for %d points",
                         card.contents,
                         [otherContents componentsJoinedByString:@" & "],
                         matchScore * MATCH_BONUS];
                } else {
                    for (Card *otherCard in otherCards) {
                        otherCard.faceUp = NO;
                    }                    
                    self.score -= MISMATCH_PENALTY;
                    self.descriptionOfLastFlip =
                        [NSString stringWithFormat:@"%@ & %@ don’t match! %d point penalty!",
                         card.contents,
                         [otherContents componentsJoinedByString:@" & "],
                         MISMATCH_PENALTY];
                }
            }
            self.score -= FLIP_COST;
        }
        card.faceUp = !card.faceUp;
    }
}

In story board drag out an UISegmentedControl element and adjust the positions of other elements:

cs193p – Assignment #1 Task #5
cs193p – Assignment #1 Task #5

Create an outlet for the new element (by control drag):

@property (weak, nonatomic) IBOutlet UISegmentedControl *cardModeSelector;

… and an action which will change the mode of the game according to its state:

- (IBAction)cardModeChanged:(UISegmentedControl *)sender {
    switch ([sender selectedSegmentIndex]) {
        case 0:
            self.game.numberOfMatchingCards = 2;
            break;
        case 1:
            self.game.numberOfMatchingCards = 3;
            break;
        default:
            self.game.numberOfMatchingCards = 2;
            break;
    }
}

Because game is reset when a new deck is dealt, it should call this method to read out the value if the control element:

    if (!_game) {
        ...
        [self cardModeChanged:self.cardModeSelector];
    }
    return _game;
}

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

6 thoughts on “cs193p – Assignment #1 Task #5”

  1. I believe I have copied your code exactly, but I am getting an error message when I try to flip a card. I have no warnings or error during the compile. Only after the app is launched, to I get the following error:

    “Thread 1: EXC_BAD_ACCESS (code=2, address=0xbf7ffffc)”
    And the line of code in CardGameViewController for the method: -(IBAction)cardModeChanged:(UISegmentedControl *)sender
    is highlighted.

    Any clue or help you can provide would be awesome.

    BTW, your blog is so helpful, I couldn’t have made it through the assignment with out it. I am now able to understand everything that was needed to do this assignment. I am taking the class through iTunes U only.

    Thanks,
    Jonathan

      1. I am using XCode Version 4.6 (4H127).

        I have uploaded all of my code to github here:

        I did have some one else help me, and we found that I was getting in an infinite loop during the creation of game, because the game object isn’t created yet, when it is lazy instantiated here:

        – (CardMatchingGame *)game{
        if (!_game) _game = [[CardMatchingGame alloc] initWithCardCount:self.cardButtons.count
        usingDeck:[[PlayingCardDeck alloc] init]];
        [self cardModeChanged:self.cardModeSelector];
        return _game;
        self.cardModeChanged is being called before the game object is completed.

        The fix was in cardModeChanged, we now set “_game.numberOfMatchingCards” instead of “game.numberOfMatchingCards”

        My only issue now is I don’t understand why it worked for you without that change.

  2. In this implementation, the following three cards match:

    9 Clubs
    9 Spades
    7 Clubs

    Perhaps my understanding of the “rules” is incorrect, but I don’t think this should be a match. I thought that all 3 cards needed to have either the same suit or the same rank?

    I tried implementing a solution that would generate an array of ranks and an array of suits and see if all elements in each array are equal (i.e. an N-of-a-kind or a flush), but it quickly got beyond my objective c knowledge. (I come from a ruby background where it could all be done in a couple of lines)

    Thanks for providing these code samples, they’re really helping me out.

  3. I think I found the solution.

    – (void)flipCardAtIndex:(NSUInteger)index
    {
    Card *card = [self cardAtIndex:index];
    self.threeCardMode = YES;
    NSMutableArray *threeCardsModeArray = [[NSMutableArray alloc]init];

    if (!card.isUnplayable) {
    if (!card.isFaceUp) {
    self.flipDescription = [NSString stringWithFormat:@”Flipped up: %@”, card.description];
    if (self.threeCardMode) {
    for (Card *otherCard in self.cards) {
    if (otherCard.isFaceUp && !otherCard.isUnplayable) {
    [threeCardsModeArray addObject:otherCard];
    if (threeCardsModeArray.count == 2) {
    int matchScore = [card match:threeCardsModeArray];
    if (matchScore) {
    for (Card *cardFlipped in threeCardsModeArray) {
    cardFlipped.unplayable = YES;
    }
    card.unplayable = YES;
    self.score += matchScore * MATCH_BONUS;
    self.flipDescription = [NSString stringWithFormat:@”Cards %@ & %@ matched for %d points”, [threeCardsModeArray componentsJoinedByString:@”,”], card.description, MATCH_BONUS];
    [threeCardsModeArray removeAllObjects];
    }else{
    for (Card *cardFlipped in threeCardsModeArray) {
    cardFlipped.faceUp = NO;
    }
    self.score -= MISMATCH_PENALTY;
    self.flipDescription = [NSString stringWithFormat:@”Cards %@ & %@ don’t match! %d points penalty!”, [threeCardsModeArray componentsJoinedByString:@”,”], card.description, MISMATCH_PENALTY];
    [threeCardsModeArray removeAllObjects];
    }
    //break;
    }
    }
    }
    }else{
    for (Card *otherCard in self.cards) {
    if (otherCard.isFaceUp && !otherCard.isUnplayable) {
    int matchScore = [card match:@[otherCard]];
    if (matchScore) {
    otherCard.unplayable = YES;
    card.unplayable = YES;
    self.score += matchScore * MATCH_BONUS;
    self.flipDescription = [NSString stringWithFormat:@”Matched %@ & %@ for %d points”, card.description, otherCard.description, MATCH_BONUS];
    }else{
    otherCard.faceUp = NO;
    self.score -= MISMATCH_PENALTY;
    self.flipDescription = [NSString stringWithFormat:@”%@ & %@ don’t match! %d points penalty!”, card.contents, otherCard.contents, MISMATCH_PENALTY];
    }
    break;
    }
    }
    }
    self.score -= FLIP_COST;
    }
    }
    card.faceUp = !card.isFaceUp;
    }

Leave a Reply

Your email address will not be published.