HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Trading Card Game Prototype in OOP

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
cardgameprototypetradingoop

Problem

So I am sure there are better ways to set this up, but I wanted to see how something like this would work in Objective-C, so I went ahead and built a prototype of a Tradeable Card Game. I've never loaded information from a plist before, so this was the perfect opportunity for me to try that.

The idea here is that the plist will contain all the relevant statistical information for all of the cards in the game in a bunch of dictionaries, each one of them representing a card. Each of those dictionaries may or may not have certain properties attached to predetermined keys. Note, it may not have the key and value at all if that specific card does not need those properties.

When the Game is created, it builds a CardList object using this plist which simply holds an array of these card dictionaries in the order that it finds them in the plist. Then when asked, the CardList object can spit out a Card object that is either random, or determined by its cardId, which would also be its index in the CardList array. I don't know if this is a completely sane way to set this up, but it is working.

One concern is that once the game was "released", the order inside the plist could not be changed easily. However this might be mitigated by using a CardCollection object to hold the permanent storage of cards for a given player from which they can construct their decks. Once a Card is created out of the CardList, only its properties matter and not how it got them. The Game is only building the decks manually in this way for initial testing.

I'll put the plist first so you can see what kind of data the Card objects need to have.

CardListPlist.plist

```


Cards



cardId
0001
cardName
TestCard01
cardType
0
unitStrength
3
unitHealth
4
abilitySlots
2

Solution

CardList.h/CardList.m

This is a really big question, so I'm going to address one thing at a time...

One concern is that once the game was "released", the order inside the plist could not be changed easily.

So, instead of embedding the plist in the app, we instead ask the app to connect to the Internet and download the card properties from our server. Given that any update to an iOS app takes 3-7 days, it's good to be able to move portions of our app out of the source code and into remote files. Where we can update our app without going through Apple's review process, we should.

-(CRGCard *) randomCardFromCardList {
    int randomNumber = arc4random_uniform(_listOfAllCards.count);
    NSDictionary *cardDict = [_listOfAllCards objectAtIndex:randomNumber];
    CRGCard *card = [[CRGCard alloc]initWithId:[self convertStringToInt:[cardDict objectForKey:kCardId]]
                                          name:[cardDict objectForKey:kCardName]
                                          type:[self convertStringToInt:[cardDict objectForKey:kCardType]]
                                  unitStrength:[self convertStringToInt:[cardDict objectForKey:kCardStrength]]
                                    unitHealth:[self convertStringToInt:[cardDict objectForKey:kCardHealth]]
                                  abilitySlots:[self convertStringToInt:[cardDict objectForKey:kCardAbilitySlots]]];
    return card;
}
-(CRGCard *) cardWithId:(int)cardId {
    NSDictionary *cardDict = [_listOfAllCards objectAtIndex:cardId];
    CRGCard *card = [[CRGCard alloc]initWithId:[self convertStringToInt:[cardDict objectForKey:kCardId]]
                                          name:[cardDict objectForKey:kCardName]
                                          type:[self convertStringToInt:[cardDict objectForKey:kCardType]]];
    return card;
}


I don't understand the difference between these two methods (besides that one is random)--I don't understand why different CRGCard init methods are called.

Why can't we just do:

- (CRGCard *)randomCardFromCardList {
    return [self cardWithID:arc4random_uniform(_listOfAllCards.count)];
}


-(int) convertStringToInt:(NSString *)string {
    return [string intValue];
}


This method is really only acceptable if we have some special way of converting. Like if we've got some crazy logic for determining if this string is actually hex or binary rather than decimal, and no matter what form the number is, we return an int value.

If the idea is to increase readability, this is completely redundant. intValue is a common method across several Objective-C classes. NSString, NSMutableString, NSNumber, and NSDecimalNumber all respond to intValue, additionally anyone who writes any custom class that might store some sort of values will most likely write an intValue method for their class too. And honestly, it doesn't matter what type the receiver is... we only care that we need an intValue out of it, and that's readable enough.

But there's another problem here too. This is a useless instance method. If we really need a special way of converting from strings to ints, there are two better options.

-
An NSString category, where we write an instance method for NSString objects

-
A C-Style function, which would look something like this:

int stringToInt(NSString *string) {
    // do something hopefully more than simply:
    return [string intValue];
}


Between the two, the first option is probably the best.

#pragma mark - Diagnostic Methods
-(void) logCards {
    for (NSDictionary *dict in self.listOfAllCards) {
        for (id key in dict) {
            NSString *string = [dict objectForKey:key];
            NSLog(@"%@", string);
        }
    }
}


Given this is in a section of code marked as Diagnostic Methods, and really should only be used in debug builds, let's ensure this code is only executed in debug builds. We don't want to eliminate the entire method, because if someone left a call to logCards in a release build, eliminating the method would cause problems, so let's just #if DEBUG the body:

#pragma mark - Diagnostic Methods
-(void) logCards {
#if DEBUG
    for (NSDictionary *dict in self.listOfAllCards) {
        for (id key in dict) {
            NSString *string = [dict objectForKey:key];
            NSLog(@"%@", string);
        }
    }
#endif
}

Code Snippets

-(CRGCard *) randomCardFromCardList {
    int randomNumber = arc4random_uniform(_listOfAllCards.count);
    NSDictionary *cardDict = [_listOfAllCards objectAtIndex:randomNumber];
    CRGCard *card = [[CRGCard alloc]initWithId:[self convertStringToInt:[cardDict objectForKey:kCardId]]
                                          name:[cardDict objectForKey:kCardName]
                                          type:[self convertStringToInt:[cardDict objectForKey:kCardType]]
                                  unitStrength:[self convertStringToInt:[cardDict objectForKey:kCardStrength]]
                                    unitHealth:[self convertStringToInt:[cardDict objectForKey:kCardHealth]]
                                  abilitySlots:[self convertStringToInt:[cardDict objectForKey:kCardAbilitySlots]]];
    return card;
}
-(CRGCard *) cardWithId:(int)cardId {
    NSDictionary *cardDict = [_listOfAllCards objectAtIndex:cardId];
    CRGCard *card = [[CRGCard alloc]initWithId:[self convertStringToInt:[cardDict objectForKey:kCardId]]
                                          name:[cardDict objectForKey:kCardName]
                                          type:[self convertStringToInt:[cardDict objectForKey:kCardType]]];
    return card;
}
- (CRGCard *)randomCardFromCardList {
    return [self cardWithID:arc4random_uniform(_listOfAllCards.count)];
}
-(int) convertStringToInt:(NSString *)string {
    return [string intValue];
}
int stringToInt(NSString *string) {
    // do something hopefully more than simply:
    return [string intValue];
}
#pragma mark - Diagnostic Methods
-(void) logCards {
    for (NSDictionary *dict in self.listOfAllCards) {
        for (id key in dict) {
            NSString *string = [dict objectForKey:key];
            NSLog(@"%@", string);
        }
    }
}

Context

StackExchange Code Review Q#59433, answer score: 2

Revisions (0)

No revisions yet.