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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Cards</key>
<array>
<dict>
<key>cardId</key>
<string>0001</string>
<key>cardName</key>
<string>TestCard01</string>
<key>cardType</key>
<string>0</string>
<key>unitStrength</key>
<string>3</string>
<key>unitHealth</key>
<string>4</string>
<key>abilitySlots</key>
<string>2</string>
</dict>
<dict>
<key>cardId</key>
<string>0002</string>
<key>cardName</key>
<string>TestCard02</string>
<key>cardType</key>
<string>0</string>
<key>unitStrength</key>
<string>6</string>
<key>unitHealth</key>
<string>6</string>
<key>abilitySlots</key>
<string>0</string>
</dict>
<dict>
<key>cardId</key>
<string>0003</string>
<key>cardName</key>
<string>TestCard03</string>
<key>cardType</key>
<string>1</string>
<key>abilitySlots</key>
<string>2</string>
</dict>
</array>
</dict>
</plist>
CardList.h
#import <Foundation/Foundation.h>
#import "CRGCard.h"
@interface CRGCardList : NSObject
@property (strong, nonatomic) NSArray *listOfAllCards;
-(void) buildCardList;
-(void) logCards;
-(CRGCard *) randomCardFromCardList;
-(CRGCard *) cardWithId:(int)cardId;
@end
CardList.m
#import "CRGCardList.h"
NSString* const kCardStrength = @"unitStrength";
NSString* const kCardHealth = @"unitHealth";
NSString* const kCardType = @"cardType";
NSString* const kCardAbilitySlots = @"abilitySlots";
NSString* const kCardName = @"cardName";
NSString* const kCardId = @"cardId";
@implementation CRGCardList
#pragma mark - Initialization
-(id) init {
self = [super init];
if (self) {
_listOfAllCards = [[NSArray alloc]init];
}
return self;
}
-(void) buildCardList {
NSString *plistCatPath = [[NSBundle mainBundle] pathForResource:@"CRGCardListPlist" ofType:@"plist"];
NSDictionary *cardDictionary = [[NSDictionary alloc] initWithContentsOfFile:plistCatPath];
self.listOfAllCards = cardDictionary[@"Cards"];
}
#pragma mark - Card building
-(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;
}
-(int) convertStringToInt:(NSString *)string {
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);
}
}
}
@end
Card.h
#import <Foundation/Foundation.h>
#import "CRGCardType.h"
@interface CRGCard : NSObject
//not all cards will have all the values, so there could be different factory methods, here there is one for units and a default one
-(instancetype) initWithId:(int)cardId name:(NSString *)name type:(CardType)type unitStrength:(int)unitStrength unitHealth:(int)unitHealth abilitySlots:(int)abilitySlots;
-(instancetype) initWithId:(int)cardId name:(NSString *)name type:(CardType)type;
@property int cardId;
@property NSString* name;
@property CardType type;
@property int unitStrength;
@property int unitHealth;
@property int abilitySlots;
@end
Card.m
#import "CRGCard.h"
@implementation CRGCard
-(instancetype) initWithId:(int)cardId name:(NSString *)name type:(CardType)type unitStrength:(int)unitStrength unitHealth:(int)unitHealth abilitySlots:(int)abilitySlots {
self = [super init];
if (self) {
_cardId = cardId;
_name = name;
_type = type;
_unitStrength = unitStrength;
_unitHealth = unitHealth;
_abilitySlots = abilitySlots;
}
return self;
}
-(instancetype) initWithId:(int)cardId name:(NSString *)name type:(CardType)type {
self = [super init];
if (self) {
_cardId = cardId;
_name = name;
_type = type;
}
return self;
}
@end
Game.h
#import <Foundation/Foundation.h>
@interface CRGGame : NSObject
-(void) startGame;
@property NSMutableArray *players;
@end
Game.m
#import "CRGGame.h"
#import "CRGCardList.h"
#import "CRGPlayer.h"
static const int kDeckSize = 30;
@implementation CRGGame {
CRGCardList *_allCardsList;
}
-(id) init {
self = [super init];
if (self) {
_players = [[NSMutableArray alloc]init];
_allCardsList = [[CRGCardList alloc]init];
}
return self;
}
-(void) startGame {
[_allCardsList buildCardList];
[_allCardsList logCards];
[self createPlayers];
[self createDecks];
[self drawStaringHands];
}
-(void) createPlayers {
int numPlayers = 2;
for (int i = 0; i < numPlayers; i++) {
[_players addObject:[[CRGPlayer alloc]init]];
}
}
#pragma mark - Build Decks
-(void) createDecks {
for (CRGPlayer *player in self.players) {
[self fillDeckWithRandomCards:player.deck];
}
}
-(void) fillDeckWithRandomCards:(NSMutableArray *)deck {
for (int i = 0; i < kDeckSize; i++) {
[deck addObject:[_allCardsList randomCardFromCardList]];
}
}
#pragma mark - Draw Hands
-(void) drawStaringHands {
for (CRGPlayer *player in self.players) {
[player drawStartingHand];
}
}
@end
Player.h
#import <Foundation/Foundation.h>
@interface CRGPlayer : NSObject
@property NSMutableArray *deck;
@property NSMutableArray *hand;
-(void) drawStartingHand;
@end
Player.m
#import "CRGPlayer.h"
static const int kStartingHandSize = 3;
@implementation CRGPlayer
-(instancetype) init {
self = [super init];
if (self) {
_deck = [[NSMutableArray alloc]init];
_hand = [[NSMutableArray alloc]init];
}
return self;
}
-(void) drawStartingHand {
for (int i = 0; i < kStartingHandSize; i++) {
[self drawCardFromDeck];
}
}
-(void) drawCardFromDeck {
[_hand addObject:[_deck lastObject]];
[_deck removeLastObject];
}
@end
I realize this is a lot of code, but I felt like it was necessary to see all of it together in order to really understand what I am trying to do. What I am most interested in hearing about is the way that I have the plists set up, and whether this is a good approach or not. Also, is the approach properly OOP?