Object Oriented Card Game

ryansobol
Unregistered
 
Post: #1
I found a great website about creating a GUI-based card game using OO principals and Java. I'm personally making the leap into Objective-C / Cocoa and I thought it would be a great exercise to port the project.

Since I'm pretty new with Objective-C, I'd love to get some feedback from the community on my first class, Rank. Before looking over the code, it might be a good idea to read the author's write-up for the game's foundation classes and the original Rank class in Java .


Rank.h
Code:
#import <Cocoa/Cocoa.h>

@interface Rank : NSObject
{
    NSString *name;
    NSString *symbol;
}

+(void)initialize;

+(Rank *)ace;
+(Rank *)two;
+(Rank *)three;
+(Rank *)four;
+(Rank *)five;
+(Rank *)six;
+(Rank *)seven;
+(Rank *)eight;
+(Rank *)nine;
+(Rank *)ten;
+(Rank *)jack;
+(Rank *)queen;
+(Rank *)king;

-(Rank *)initWithName:(NSString *)aName Symbol:(NSString *)aSymbol;

-(NSString *)name;
-(NSString *)symbol;

-(void)setAceHigh;
-(void)setKingHigh;
-(int)compareTo:(Rank *)aRank;

-(NSString *)description;

@end

Rank.m
Code:
#import "Rank.h"

static BOOL        aceHigh =            NO;

static Rank        *ACE =                nil;
static Rank        *TWO =                nil;
static Rank        *THREE =            nil;
static Rank        *FOUR =                nil;
static Rank        *FIVE =                nil;
static Rank        *SIX =                nil;
static Rank        *SEVEN =            nil;
static Rank        *EIGHT =            nil;
static Rank        *NINE =                nil;
static Rank        *TEN =                nil;
static Rank        *JACK =                nil;
static Rank        *QUEEN =            nil;
static Rank        *KING =                nil;

static NSArray    *VALUES_KING_HIGH = nil;
static NSArray    *VALUES_ACE_HIGH =    nil;

@implementation Rank


+(void)initialize
{
    @synchronized(self)
    {
        if (ACE == nil)
        {
            ACE = [[Rank alloc] initWithName:@"Ace"    Symbol:@"A"];
        }
        
        if (TWO    == nil)
        {
            TWO = [[Rank alloc] initWithName:@"TWO"    Symbol:@"2"];
        }
        
        if (THREE == nil)
        {
            THREE = [[Rank alloc] initWithName:@"Three"    Symbol:@"3"];
        }
        
        if (FOUR == nil)
        {
            FOUR = [[Rank alloc] initWithName:@"Four"    Symbol:@"4"];
        }
        
        if (FIVE == nil)
        {
            FIVE = [[Rank alloc] initWithName:@"Five" Symbol:@"5"];
        }
        
        if (SIX == nil)
        {
            SIX = [[Rank alloc] initWithName:@"Six"    Symbol:@"6"];
        }
        
        if (SEVEN == nil)
        {
            SEVEN = [[Rank alloc] initWithName:@"Seven"    Symbol:@"7"];
        }
        
        if (EIGHT == nil)
        {
            EIGHT = [[Rank alloc] initWithName:@"Eight"    Symbol:@"8"];
        }
        
        if (NINE == nil)
        {
            NINE = [[Rank alloc] initWithName:@"Nine"    Symbol:@"9"];
        }
        
        if (TEN == nil)
        {
            TEN = [[Rank alloc] initWithName:@"Ten"    Symbol:@"10"];
        }
        
        if (JACK == nil)
        {
            JACK = [[Rank alloc] initWithName:@"Jack"    Symbol:@"J"];
        }
        
        if (QUEEN == nil)
        {
            QUEEN = [[Rank alloc] initWithName:@"Queen"    Symbol:@"Q"];
        }
        
        if (KING == nil)
        {
            KING = [[Rank alloc] initWithName:@"King"    Symbol:@"K"];
        }
        
        if (VALUES_KING_HIGH == nil)
        {
            VALUES_KING_HIGH = [NSArray arrayWithObjects:
                ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT
                , NINE, TEN, JACK, QUEEN, KING, nil];
        }
        
        if (VALUES_ACE_HIGH == nil)
        {
            VALUES_ACE_HIGH = [NSArray arrayWithObjects:
                TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT
                , NINE, TEN, JACK, QUEEN, KING, ACE, nil];
        }
    }
}

+(Rank *)ace
{
    if (ACE == nil)
    {
        [self initialize];
    }
    
    return ACE;
}

+(Rank *)two
{
    if (TWO    == nil)
    {
        [self initialize];
    }
    
    return TWO;
}

+(Rank *)three
{
    if (THREE == nil)
    {
        [self initialize];
    }
    
    return THREE;
}

+(Rank *)four
{
    if (FOUR == nil)
    {
        [self initialize];
    }
    
    return FOUR;
}

+(Rank *)five
{
    if (FIVE == nil)
    {
        [self initialize];
    }
    
    return FIVE;
}

+(Rank *)six
{
    if (SIX == nil)
    {
        [self initialize];
    }
    
    return SIX;
}

+(Rank *)seven
{
    if (SEVEN == nil)
    {
        [self initialize];
    }
    
    return SEVEN;
}

+(Rank *)eight
{
    if (EIGHT == nil)
    {
        [self initialize];
    }
    
    return EIGHT;
}

+(Rank *)nine
{
    if (NINE == nil)
    {
        [self initialize];
    }
    
    return NINE;
}

+(Rank *)ten
{
    if (TEN == nil)
    {
        [self initialize];
    }
    
    return TEN;
}

+(Rank *)jack
{
    if (JACK == nil)
    {
        [self initialize];
    }
    
    return JACK;
}

+(Rank *)queen
{
    if (QUEEN == nil)
    {
        [self initialize];
    }
    
    return QUEEN;
}

+(Rank *)king
{
    if (KING == nil)
    {
        [self initialize];
    }
    
    return KING;
}

-(Rank *)initWithName:(NSString *)aName Symbol:(NSString *)aSymbol
{
    self = [super init];
    
    if (self != nil)
    {
        if (name != aName)
        {
            [name release];
            name = [aName retain];
        }
        
        if (symbol != aSymbol)
        {
            [symbol release];
            symbol = [aName retain];
        }
    }
    
    return self;
}

-(NSString *)name
{
    return name;
}

-(NSString *)symbol
{
    return symbol;
}

-(void)setAceHigh
{
    @synchronized(self)
    {
        aceHigh = YES;
    }
}

-(void)setKingHigh
{
    @synchronized(self)
    {
        aceHigh = NO;
    }
}

-(int)compareTo:(Rank *)aRank
{
    @synchronized(self)
    {
        if (aceHigh == YES)
            return [VALUES_ACE_HIGH indexOfObject: self] - [VALUES_ACE_HIGH indexOfObject: aRank];
        else
            return [VALUES_KING_HIGH indexOfObject: self] - [VALUES_KING_HIGH indexOfObject: aRank];        
    }
}

-(NSString *)description
{
    return [self name];
}

-(void)dealloc
{
    [super dealloc];
    
    [name release];
    [symbol release];
}

// -------------- Singleton overrides --------------
// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaObjects/Articles/CreateSingleton.html

+ (id)allocWithZone:(NSZone *)zone
{
    return nil;
}


- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain
{
    return self;
}

- (unsigned)retainCount
{
    return UINT_MAX;  //denotes an object that cannot be released
}

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self;
}

@end

Thanks for your help!
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #2
it looks a bit excessive for cards
Quote this message in a reply
ryansobol
Unregistered
 
Post: #3
Well, the author's programming challenge was to create a foundation of reusable classes for any and all card games. Anyone can implement a game-specific deck of cards using primitive types like int or char. If you have any real suggestions on "trimming" fat, I'm all ears.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #4
a bit too much redundancy.

1) +(void)initialize; only gets called once. (just in case though for whatever future odd reason) We use a static BOOL to keep track of the first initialize and do no further initializes.

2) if your cards are already instantiated in +(void)initialize; then you don't need to check for their nonexistence in your shared-object methods.

oh, and..
3) NEVER EVER call "[self initialize]"!!! This is a class method and is handled by the runtime. Your usage here is plain wrong.

so in closing:
1) make a static BOOL initializedflag variable and set that in your +(void)initialize. Remember that +(void)initialize only gets called once (and NOT by you).

2) change all the shared-object class methods to just return ACE; (or whatever it's supposed to return. When these get called +initialize has already been called (guaranteed) and your objects should exist for use (do your error checking and validity in +initialize).

3) I'm not sure why you are overriding your memory management methods, as with good memory management you'll never ever really need to bother with overriding these. (Unless of course you haven't learned Cocoa memory management in which case, post to the board and prepare for [constructive] flaming.)

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
ryansobol
Unregistered
 
Post: #5
kelvin Wrote:1) +(void)initialize; only gets called once. (just in case though for whatever future odd reason) We use a static BOOL to keep track of the first initialize and do no further initializes.)

That makes sense, but what should the class do if a programmer accidentally calls the +ace, +two, etc. method before they've called +initialize? Return nil?


kelvin Wrote:2) if your cards are already instantiated in +(void)initialize; then you don't need to check for their nonexistence in your shared-object methods.)

Okay.

kelvin Wrote:oh, and..
3) NEVER EVER call "[self initialize]"!!! This is a class method and is handled by the runtime. Your usage here is plain wrong.

Thanks for the warning.

kelvin Wrote:3) I'm not sure why you are overriding your memory management methods, as with good memory management you'll never ever really need to bother with overriding these. (Unless of course you haven't learned Cocoa memory management in which case, post to the board and prepare for [constructive] flaming.)

Actually, I got the idea for overriding the memory management methods from Apple's developer website on singletons. It seems like Apple treats singletons as an exception to the reference counting paradigm. I could be wrong though.
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #6
There is absolutely no way they can call +ace or +two, etc, before +initialize because the runtime calls it when the class is loaded into memory (right?)

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #7
Steven Wrote:There is absolutely no way they can call +ace or +two, etc, before +initialize because the runtime calls it when the class is loaded into memory (right?)
Yep.

like I said NEVER EVER put "[something initialize]" anywhere. Runtime does it (and always before you ever have a chance to do anything else I might add).

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
ryansobol
Unregistered
 
Post: #8
kelvin Wrote:Yep.

like I said NEVER EVER put "[something initialize]" anywhere. Runtime does it (and always before you ever have a chance to do anything else I might add).

Wait, are you saying that the runtime system actually sends an initialize message to all class objects before the application starts?
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #9
There's no guarantee as to when it will be called, only that it will be before you can use the class. If the class is there at application start, then yes it would be called during the application startup.

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
ryansobol
Unregistered
 
Post: #10
Steven Wrote:There's no guarantee as to when it will be called, only that it will be before you can use the class. If the class is there at application start, then yes it would be called during the application startup.

I guess I'll have to take your word for it but I'd like to see the documentation on that. The only reason is because I (think) I've read most of Apple's documentation on Objective-C and memory management. Do you have a link to anything official about this?
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #11
No, that was just what made sense to me - you could cook up a test case...

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #12
It's all specified here. Wacko

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
ryansobol
Unregistered
 
Post: #13
kelvin Wrote:It's all specified here. Wacko

I stand corrected. I feel alot more confident using the +initialize technique for singletons in ObjC. Thanks!
Quote this message in a reply
ryansobol
Unregistered
 
Post: #14
After taking everyone's suggestions into consideration, I've updated and re-posted the Rank class.

Rank.h
Code:
#import <Cocoa/Cocoa.h>


@interface Rank : NSObject
{
    NSString *name;
    NSString *symbol;
}

+(Rank *)ace;
+(Rank *)two;
+(Rank *)three;
+(Rank *)four;
+(Rank *)five;
+(Rank *)six;
+(Rank *)seven;
+(Rank *)eight;
+(Rank *)nine;
+(Rank *)ten;
+(Rank *)jack;
+(Rank *)queen;
+(Rank *)king;
+(NSEnumerator *)rankEnumerator;

-(Rank *)initWithName:(NSString *)aName Symbol:(NSString *)aSymbol;

-(NSString *)name;
-(NSString *)symbol;

-(void)setAceHigh;
-(void)setKingHigh;
-(int)compareTo:(Rank *)aRank;

@end


Rank.m
Code:
#import "Rank.h"

static BOOL        initialized    =        NO;
static BOOL        aceHigh =            NO;

static Rank        *ACE =                nil;
static Rank        *TWO =                nil;
static Rank        *THREE =            nil;
static Rank        *FOUR =                nil;
static Rank        *FIVE =                nil;
static Rank        *SIX =                nil;
static Rank        *SEVEN =            nil;
static Rank        *EIGHT =            nil;
static Rank        *NINE =                nil;
static Rank        *TEN =                nil;
static Rank        *JACK =                nil;
static Rank        *QUEEN =            nil;
static Rank        *KING =                nil;

static NSArray    *VALUES_KING_HIGH = nil;
static NSArray    *VALUES_ACE_HIGH =    nil;

@implementation Rank


+(void)initialize
{
    if (initialized == NO)
    {
        initialized = YES;
    
        ACE =    [[Rank alloc] initWithName:@"Ace"    Symbol:@"A"];
        TWO =    [[Rank alloc] initWithName:@"TWO"    Symbol:@"2"];
        THREE = [[Rank alloc] initWithName:@"Three"    Symbol:@"3"];
        FOUR =    [[Rank alloc] initWithName:@"Four"    Symbol:@"4"];
        FIVE =    [[Rank alloc] initWithName:@"Five"    Symbol:@"5"];
        SIX =    [[Rank alloc] initWithName:@"Six"    Symbol:@"6"];
        SEVEN = [[Rank alloc] initWithName:@"Seven"    Symbol:@"7"];
        EIGHT = [[Rank alloc] initWithName:@"Eight"    Symbol:@"8"];
        NINE =    [[Rank alloc] initWithName:@"Nine"    Symbol:@"9"];
        TEN =    [[Rank alloc] initWithName:@"Ten"    Symbol:@"10"];
        JACK =    [[Rank alloc] initWithName:@"Jack"    Symbol:@"J"];
        QUEEN = [[Rank alloc] initWithName:@"Queen"    Symbol:@"Q"];

        VALUES_KING_HIGH = [NSArray arrayWithObjects:
            ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT
            , NINE, TEN, JACK, QUEEN, KING, nil];

        VALUES_ACE_HIGH = [NSArray arrayWithObjects:
            TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT
            , NINE, TEN, JACK, QUEEN, KING, ACE, nil];
    }
}

+(Rank *)ace
{
    return ACE;
}

+(Rank *)two
{
    return TWO;
}

+(Rank *)three
{
    return THREE;
}

+(Rank *)four
{
    return FOUR;
}

+(Rank *)five
{
    return FIVE;
}

+(Rank *)six
{
    return SIX;
}

+(Rank *)seven
{
    return SEVEN;
}

+(Rank *)eight
{
    return EIGHT;
}

+(Rank *)nine
{
    return NINE;
}

+(Rank *)ten
{
    return TEN;
}

+(Rank *)jack
{
    return JACK;
}

+(Rank *)queen
{
    return QUEEN;
}

+(Rank *)king
{
    return KING;
}

+(NSEnumerator *)rankEnumerator
{
    return [VALUES_KING_HIGH objectEnumerator];
}

-(Rank *)initWithName:(NSString *)aName Symbol:(NSString *)aSymbol
{
    self = [super init];
    
    if (self != nil)
    {
        if (name != aName)
        {
            [name release];
            name = [aName retain];
        }
        
        if (symbol != aSymbol)
        {
            [symbol release];
            symbol = [aName retain];
        }
    }
    
    return self;
}

-(NSString *)name
{
    return name;
}

-(NSString *)symbol
{
    return symbol;
}

-(void)setAceHigh
{
    @synchronized(self)
    {
        aceHigh = YES;
    }
}

-(void)setKingHigh
{
    @synchronized(self)
    {
        aceHigh = NO;
    }
}

-(int)compareTo:(Rank *)aRank
{
    @synchronized(self)
    {
        if (aceHigh == YES)
            return [VALUES_ACE_HIGH indexOfObject: self] - [VALUES_ACE_HIGH indexOfObject: aRank];
        else
            return [VALUES_KING_HIGH indexOfObject: self] - [VALUES_KING_HIGH indexOfObject: aRank];        
    }
}

-(NSString *)description
{
    return [self name];
}

-(void)dealloc
{
    [super dealloc];
    
    [name release];
    [symbol release];
}

// -------------- Singleton overrides --------------
// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaObjects/Articles/CreateSingleton.html

+ (id)allocWithZone:(NSZone *)zone
{
    return nil;
}


- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain
{
    return self;
}

- (unsigned)retainCount
{
    return UINT_MAX;  //denotes an object that cannot be released
}

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self;
}

@end


As you can see, I've fixed all the class methods to conform to the runtime system better. I've also added the +rankEnumerator method that will return an NSEnumerator * object for easy iteration of all the static Rank objects. The original Java class used a static List object for iteration, but I think my technique is a little more standard in Cocoa/ObjC.

Thanks for all of your feedback so far! I'd love to hear more critiques for the community on this class.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #15
better...

but, there's still some little kinks.

The instance method -init... should only be ever called once per alloc'd instance (this is up to you and your code). (This is also why most Cocoa programming tutorials suggest you nest the alloc/inits.) So therefore, you shouldn't ever have to check the instance members against nil, just set them. When you alloc an object it's instance members are all nil/NULL.

also, in your dealloc method...
[super dealloc]; should always be the last line of your subclass' dealloc method. Calling it first like you do can release important stuff and cause serious problems. Do your subclass member clean up, then pass to the superclass to do the final clean up.

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Game engines with object programming JonnyThunder 3 8,018 Aug 16, 2010 10:42 AM
Last Post: SethWillits
  Coding A Card Game Jerm #1 3 4,979 Apr 11, 2010 03:05 PM
Last Post: TimMcD