Please note, this blog entry is from a previous course. You might want to check out the current one.
The Set game only needs to allow users to pick sets and get points for doing so (e.g. it does not redeal new cards when sets are found). In other words, it works just like the Playing Card matching game. The only differences are that it is a 3-card matching game and uses different cards (deal your Set cards out of a complete Set deck).
Let’s start with the card model, instead of a suit and a rank, we have colors, symbols, shadings and the count of symbols, for which we create public properties, setters and getters, and therefore need also to synthesize them:
// SetCard.h @property (strong, nonatomic) NSString *color; @property (strong, nonatomic) NSString *symbol; @property (strong, nonatomic) NSString *shading; @property (nonatomic) NSUInteger number; // SetCard.m @synthesize color = _color, symbol = _symbol, shading = _shading; - (NSString *)color { return _color ? _color : @"?"; } - (void)setColor:(NSString *)color { if ([[SetCard validColors] containsObject:color]) _color = color; } - (NSString *)symbol { return _symbol ? _symbol : @"?"; } - (void)setSymbol:(NSString *)symbol { if ([[SetCard validSymbols] containsObject:symbol]) _symbol = symbol; } - (NSString *)shading { return _shading ? _shading : @"?"; } - (void)setShading:(NSString *)shading { if ([[SetCard validShadings] containsObject:shading]) _shading = shading; } - (void)setNumber:(NSUInteger)number { if (number <= [SetCard maxNumber]) _number = number; }
Like for the playing cards we need arrays with their valid values:
// SetCard.h + (NSArray *)validColors; + (NSArray *)validSymbols; + (NSArray *)validShadings; + (NSUInteger)maxNumber; // SetCard.m + (NSArray *)validColors { return @[@"red", @"green", @"purple"]; } + (NSArray *)validSymbols { return @[@"oval", @"squiggle", @"diamond"]; } + (NSArray *)validShadings { return @[@"solid", @"open", @"striped"]; } + (NSUInteger)maxNumber { return 3; }
For the card contents we concatenate all these values (we will not be able to use this value directly for the view, so just take care all the information is here for debugging or other later use):
- (NSString *)contents { return [NSString stringWithFormat:@"%@:%@:%@:%d", self.symbol, self.color, self.shading, self.number]; }
In the matching method check first if there are the right number of cards. Then create an array for each property and fill them looping over all cards. A set matches if all of those arrays contain either a single value or the same amount of different values as matched cards:
- (int)match:(NSArray *)otherCards { int score = 0; if ([otherCards count] == self.numberOfMatchingCards - 1) { NSMutableArray *colors = [[NSMutableArray alloc] init]; NSMutableArray *symbols = [[NSMutableArray alloc] init]; NSMutableArray *shadings = [[NSMutableArray alloc] init]; NSMutableArray *numbers = [[NSMutableArray alloc] init]; [colors addObject:self.color]; [symbols addObject:self.symbol]; [shadings addObject:self.shading]; [numbers addObject:@(self.number)]; for (id otherCard in otherCards) { if ([otherCard isKindOfClass:[SetCard class]]) { SetCard *otherSetCard = (SetCard *)otherCard; if (![colors containsObject:otherSetCard.color]) [colors addObject:otherSetCard.color]; if (![symbols containsObject:otherSetCard.symbol]) [symbols addObject:otherSetCard.symbol]; if (![shadings containsObject:otherSetCard.shading]) [shadings addObject:otherSetCard.shading]; if (![numbers containsObject:@(otherSetCard.number)]) [numbers addObject:@(otherSetCard.number)]; if (([colors count] == 1 || [colors count] == self.numberOfMatchingCards) && ([symbols count] == 1 || [symbols count] == self.numberOfMatchingCards) && ([shadings count] == 1 || [shadings count] == self.numberOfMatchingCards) && ([numbers count] == 1 || [numbers count] == self.numberOfMatchingCards)) { score = 4; } } } } return score; }
Please note the code above uses a new – not yet declared property holding the number of matching cards. … we will use this value also to set the default value for the card-matching game (where we before used the constant “2”). To be able to use it there – remember it does not care if we use playing cards or set cards just cards. We need to define it in the super class Card. For the set sub class we adjust it when a card gets initialized:
- (id)init { self = [super init]; if (self) { self.numberOfMatchingCards = 3; } return self; }
Define the new property in the super class, by using lazy instantiation here, we do not even need to change the playing-card class … and two is not an unreasonable number of cards if you want to match them against each other 😉
// Card.h @property (nonatomic) NSUInteger numberOfMatchingCards; // Card.m - (NSUInteger)numberOfMatchingCards { if (!_numberOfMatchingCards) _numberOfMatchingCards = 2; return _numberOfMatchingCards; }
A deck of set cards consists of all possible permutations of colors, shapes, shadings, and numbers of symbols. Just loop over all of them to create the deck:
- (id)init { self = [super init]; if (self) { for (NSString *color in [SetCard validColors]) { for (NSString *symbol in [SetCard validSymbols]) { for (NSString *shading in [SetCard validShadings]) { for (NSUInteger number = 1; number <= [SetCard maxNumber]; number++) { SetCard *card = [[SetCard alloc] init]; card.color = color; card.symbol = symbol; card.shading = shading; card.number = number; [self addCard:card atTop:YES]; } } } } } return self; }
Adjust the card-matching-game class to cope with the new property introduced above (remember before we just set the default value to “2”):
- (NSUInteger)maxMatchingCards { Card *card = [self.cards firstObject]; if (_maxMatchingCards < card.numberOfMatchingCards) { _maxMatchingCards = card.numberOfMatchingCards; } return _maxMatchingCards; }
Finally create the new set-card deck in the view controller:
- (Deck *)createDeck { return [[SetCardDeck alloc] init]; }
The set card game is now actually playable … however, due to the suboptimal display, quite difficult … we will deal with this in the following tasks …
The complete code is available on github.
In assignment one we were supposed to be able to drag and drop images into images.xcassets. I could drag images into the area but when I dropped them they just sprang back to their original folder. Do you know how I would fix this to work correctly?
Thanks!
The lecturer demonstrate it in quite some detail. Try to do it in parallel with the video … and you should see if you have missed something.
The stanford images downloaded as png files with the png in all caps. When I lowered them to lowercase, then the images dropped when I dragged them.
hey there.. thanks for the answer.. it’s awesome.
But from my understanding of the solution above, there’s something missing. It compares self with otherCards.
Suppose self is “Card A”, and the content of otherCards is “Card B” and “Card C”.
The algorithm above, only compare (“Card A” to “Card B”) and (“Card A” to “Card C”).
it doesn’t compare “Card B” to “Card C”.
if the colour of “Card B” and “Card C” is both green. and the colour of “Card A” is purple. The algorithm above will give a match. But it shouldn’t. Because all card colour
must be different or all must be the same.
Do you agree with that? or it is me that misunderstand your code?
forget it.. my bad.. the code is good.
if card b and card c is both green and card is purple, it won’t give a match.
cause it’s filtered with containsObject: method.
I’m so sorry.
You are welcome 😉
Happy Easter
I think you could do this to assign 4 to the score:
if([colors count]%1 && [symbols count]%1 && [shadings count]%1 && [numbers count]%1)score = 4;
As the assignment said in a set game and a playingcard game 3 cards would be used, it should work. It has the advantage not having to define the extra variable in Card. Unless numberOfMatchingCards will be used later on.
line should be:
if([colors count]%2 == 1 && [symbols count]%2 == 1 && [shadings count]%2 == 1 && [numbers count]%2 == 1)score = 4;
Ooops I misread the assignment, apparently the playingCard game should be done with 2 cards. So one can ignore the last remark. Darned, you’re good !
you are welcome, and thank you for the compliment!
The simplicity and genius of your code! How did you think of that..
I created 8 methods for it!
-(BOOL)allTheSameSize:(NSArray *)otherCards {
bool allTheSame = NO;
NSUInteger numOtherCards = [otherCards count];
SetCard *otherCard = [otherCards firstObject];
if (numOtherCards == 1) {
return otherCard.size == self.size;
} else if (numOtherCards > 1) {
allTheSame = otherCard.size == self.size && [self allTheSameSize:[otherCards subarrayWithRange:NSMakeRange(1, numOtherCards-1)]];
}
return allTheSame;
}
-(BOOL)allDifferentSize:(NSArray *)otherCards {
bool allDifferent = YES;
NSUInteger numOtherCards = [otherCards count];
if (numOtherCards) {
for (id object in otherCards) {
if ([object isKindOfClass:[SetCard class]]) {
SetCard *otherCard = (SetCard *)object;
if (otherCard.size == self.size)
return NO;
}
}
}
if (numOtherCards > 1) {
allDifferent = [[otherCards firstObject] allDifferentSize:[otherCards subarrayWithRange:NSMakeRange(1, numOtherCards-1)]];
}
return allDifferent;
}
etcetera..
Maybe I should have started with CS106B and CS107/110 before doing this.. 😉 But time is not on my side. Anyway, great work!
Only issue i can see is the fact you don’t consider different score accordingly to match difficulty, unless mathematically the difficulty is the same for all match or all different, i haven’t done the math anyway
Hi,
one question: why putting this
if (([colors count] == 1 || [colors count] == self.numberOfMatchingCards)
&& ([symbols count] == 1 || [symbols count] == self.numberOfMatchingCards)
&& ([shadings count] == 1 || [shadings count] == self.numberOfMatchingCards)
&& ([numbers count] == 1 || [numbers count] == self.numberOfMatchingCards)) {
score = 4;
}
in for loop? We are not suppose to evaluate the score after collecting all the attributes?
… because you need to check every single card with each other card?
I think they both have the same result no matter we put this inside or outside the FOR loop. But it’s useless to put it inside.
The condition [attribute count] == self.numberOfMatchingCards) would never be met before the last loop (3rd loop). We have self.numberOfMatchingCards) = 3 for Set Game, before last loop, [attribute count] < 3. So the whole condition becomes ([colors count] == 1) && ([symbols count] == 1) && ([shadings count]==1) &&([numbers count]==1), and this condition can never be established.
You are right!
– (NSUInteger)maxMatchingCards
{
Card *card = [self.cards firstObject];
if (_maxMatchingCards < card.numberOfMatchingCards) {
_maxMatchingCards = card.numberOfMatchingCards;
}
return _maxMatchingCards;
}
In the above method what does this line of code do??
" Card *card = [self.cards firstObject];"
It gets the first card from the cards array …
what is the importance of the if statement
if (_maxMatchingCards < card.numberOfMatchingCards)
It ensures that the maximal number of matching cards is not low than the current number of matching cards.