Reputation: 1048
I'm trying to get to grips with Objective C and Cocoa so I may be using the wrong terminology here.
I've made an Objective C class for a deck of cards that is read by my main AppDelegate.h & AppDelegate.m, which has two methods, deckOfCards & pickACard. deckOfCards is just an NSMutableArray with every card type written out in string form, then in pickACard I create a new array like this:
-(void)pickACard
{
DeckOfCards *newDeck = [[DeckOfCards alloc] init];
int r = arc4random() % 52;
NSLog (@"The card you picked is: %@, and there are %i cards left", [newDeck objectAtIndex:r], [newDeck count]);
[newDeck removeObjectAtIndex:r];
}
However XCode is saying that I cannot use objectAtIndex & removeObjectAtIndex on this new array, so I can't randomly pick a card and then "remove it from the pack" by removing that part of the array.
This did work when it was all in deckOfCards, but then when it was called by AppDelegate.m it would create a new array so I would get different cards, but never remove more than one from the pack.
I'm going to guess that I'm not creating this new array properly.
Just for more clarity, DeckOfCards.h is this:
#import <Foundation/Foundation.h>
@interface DeckOfCards : NSObject
{
@private
}
-(void) deckOfCards;
-(void) pickACard;
@end
DeckOfCards.m is this:
@implementation DeckOfCards
-(void)deckOfCards
{
NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects:
@"One of Hearts", @"Two of Hearts"..., nil];
}
-(void)pickACard
{
DeckOfCards *newDeck = [[DeckOfCards alloc] init];
int r = arc4random() % 52;
NSLog (@"The card you picked is: %@, and there are %i cards left",[newDeck objectAtIndex:r], [newDeck count]);
[newDeck removeObjectAtIndex:r];
}
@end
Upvotes: 0
Views: 9570
Reputation: 96373
However XCode is saying that I cannot use objectAtIndex & removeObjectAtIndex on this new array, …
No, it's saying that you aren't trying to use it on an array.
What you are trying to use it on is a DeckOfCards:
DeckOfCards *newDeck = [[DeckOfCards alloc] init]; int r = arc4random() % 52; NSLog (@"The card you picked is: %@, and there are %i cards left", [newDeck objectAtIndex:r], [newDeck count]); [newDeck removeObjectAtIndex:r];
newDeck
being the DeckOfCards
that you created on the first line.
A DeckOfCards
is not an array:
@interface DeckOfCards : NSObject
A DeckOfCards
is simply an object. Not just any object; you have added things to it (the deckOfCards
and pickACard
methods). But it is not an array, and so does not respond to objectAtIndex:
and is not a mutable array and so does not respond to removeObjectAtIndex:
, either.
So where do you have an array? Well, you create one in your deckOfCards
method:
-(void)deckOfCards { NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects: @"One of Hearts", @"Two of Hearts"..., nil]; }
But what happens to that array?
Nothing. You create it, and then you drop it on the floor. You aren't returning it to whatever called you; indeed, you can't, since this method returns void
.
Momentarily leaving aside the architectural issues, which can be forgiven assuming that you are very new to programming, you need to do several things:
deckOfCards
method to declare that it will return an NSMutableArray *
, not void
.Add a line immediately after the creation of the array that returns the array:
return deckOfCards;
(And note that deckOfCards
here refers to the array*.)
Change pickACard
to call deckOfCards
. That will create the array and (once you've made change #2) return it to you. Declare a variable in pickACard
, similar to the one you declared in deckOfCards
, and assign the result to deckOfCards
to it.
pickACard
, you can attempt to retrieve one of its items, log it, and remove it. Do this by sending the objectAtIndex:
and removeObjectAtIndex:
messages to the array, not to the object that isn't an array.r
. NSArray indexes are of type NSUInteger
, not int
.Now the program should compile, run, and almost work.
Almost? Well, you have one more thing to do.
deckOfCards
creates a fresh deck every time; you remove a single card from that deck, but then forget all about that deck, and will be using another fresh deck the next time through.
This is your next challenge: Change the program to create a deck in initialization, and remember and reuse that deck for each subsequent draw. Be sure to handle the case of deck exhaustion (no more cards) correctly, probably by creating a fresh deck at that moment.
(Your first instinct will be to copy and paste. Do that as a first draft only. Then change it so that the deck-creation code is in only one place, a method for that purpose, and you call that method from both places where you need to create a fresh deck.)
I'll give you a hint: Instance variable.
*In your deckOfCards
method, you'll see two things named deckOfCards
: That method, and the array you declare within it. Two different things with the same name. The compiler doesn't care; methods and variables exist in separate namespaces, so it has no problem knowing which is which at any given moment. But you should probably change one or the other to aid your own understanding, not to mention others' suggestions of what to change (“do this with deckOfCards
”, I say—but which deckOfCards
do I mean?).
I suggest renaming the array variable. Just naming it array
will do for a start. (But only as long as it's a local variable, declared within that method.)
When you make it an instance variable, change the name back to something more specific. _deckOfCards
is the common naming convention nowadays—the underscore tells you (again, the compiler doesn't care) that this name is the name of an instance variable.
Upvotes: 3
Reputation: 104092
As the other commenters have said, you have multiple problems with the structure of your app. I've created an example app to show you how to start approaching this problem. First, in your DeckOfCards class you want a property that points to the mutable array that holds your deck of cards -- I called this just deck. Then, you need an init method that not only returns an instance of your DeckOFCards class, but also creates and populates that array of cards. So here is what I put in the DeckOfCards class.
In the .h, just this:
@interface DeckOfCards : NSObject
@property (retain,nonatomic) NSMutableArray *deck;
@end
And, in the .m :
-(id)init {
if (self = [super init]) {
NSArray *cardNames = [NSArray arrayWithObjects:@"Two",@"Three",@"Four",@"Five",@"Six",@"Seven",@"Eight",@"Nine",@"Ten",@"Jack",@"Queen",@"King",@"Ace",nil];
NSArray *suits = [NSArray arrayWithObjects:@" of Hearts",@" of Diamonds",@" of Spades",@" of Clubs",nil];
self.deck = [NSMutableArray array];
for (NSString *aCard in cardNames) {
for (NSString *aSuit in suits) {
[self.deck addObject:[aCard stringByAppendingString:aSuit]];
}
}
}
return self;
}
You want to create an instance of this class, and pick cards from inside another class, not from within the DeckOfCards class itself. In this example I've done that in the app delegate (for simplicity ), but it would be better to do this in some controller class. So, I have a property, aNewDeck to keep a reference to that instance (the compiler won't let you use a name that starts with "new", not sure why, or if that's a new thing in mountain lion). I create that new instance in the applicationDidFinishLoading method, and then have an IBAction connected to a button that picks the card. Notice that I changed the random number generator to not use 52, but the count of the array -- if you don't do that, you'll be picking numbers that are bigger than the array as it gets smaller. You should notice, that to get access to the array, I use aNewDeck.deck which is the way to access that property, deck, of the instance aNewDeck. Here's the .h file:
@class DeckOfCards;
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (assign) IBOutlet NSWindow *window;
@property (strong) DeckOfCards *aNewDeck;
-(IBAction)pickACard:(id)sender ;
@end
An here's the .m:
#import "AppDelegate.h"
#import "DeckOfCards.h"
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.aNewDeck = [[DeckOfCards alloc] init];
}
-(IBAction)pickACard:(id)sender {
if (self.aNewDeck.deck.count != 0) {
int r = arc4random() % [self.aNewDeck.deck count];
NSLog (@"The card you picked is: %@, and there are %li cards left", [self.aNewDeck.deck objectAtIndex:r], [self.aNewDeck.deck count] - 1 );
[self.aNewDeck.deck removeObjectAtIndex:r];
}else{
NSLog(@"Game Over!");
}
}
I hope this helps you get started.
Upvotes: 2
Reputation: 18363
I see a few issues here. The immediate cause of your compiler issue is that you are trying to call NSMutableArray
methods on an instance of DeckOfCards
. Instead, you want toc call these methods on the NSMutableArray
returned by the method-[DeckOfCards deckOfCards]
. It would look something like this:
DeckOfCards *newDeck = [[DeckOfCards alloc] init];
NSMutableArray *deckArray = [newDeck deckOfCards];
int r = arc4random() % 52;
NSLog (@"The card you picked is: %@, and there are %i cards left",[deckArray objectAtIndex:r], [deckArray count]);
[deckArray removeObjectAtIndex:r];
But this leads us to our next issue - -deckOfCards
doesn't actually return anything - its return type is defined as void
. So in order to get that NSMutableArray
instance back to the rest of your program you're going to have to return
it, and define the method with the appropriate return type. If we were just to address this issue, we'd change your -deckOfCards
method like so:
-(NSMutableArray *)deckOfCards
{
NSMutableArray *deckOfCards = [NSMutableArray arrayWithObjects:
@"One of Hearts", @"Two of Hearts"..., nil];
return deckOfCards;
}
OK, so that should compile now. But there are some serious errors here. I'm afraid I don't have much more time to put into this answer right now, but I suggest you read some introductory Objective-C, focusing on the difference between objects and classes, and the lifetime of objects and memory management. Essentially, nothing is ever persisted. No matter how many times you call -pickACard
on any instance of DeckOfCards
, you will always get a separate instance instantiated which will then get a brand new NSMutableArray
instantiated. But none of these objects are ever retained (assuming you're using Automatic Reference Counting) and they all disappear immediately. So you will always, every time, get a brand new 52 card deck from -pickACard
.
Hopefully this helps. With some quality time put into reading and learning the fundamentals of Objective-C and Object Oriented Programming, you will be well on your way to making this program work like you want. Let me know if you have questions and I'll try to answer them.
Upvotes: 4
Reputation:
DeckOfCards is not a subclass of NSMutableArray and it doesn't implement the - objectAtIndex:
and - removeObjectAtIndex:
methods either - so it's fine that you can't use these methods.
Upvotes: 1