0
\$\begingroup\$

This question sounds quite specific, but I mean in general, how do you manage something like a card game when it comes to decks (one for player1, one for player2, ...) and placed cards focused on instances of a Card-object?

Specifically in my case - using Java - I got a few cards with static data (image, values displayed on them, ...) and some data, depending on who has the card in their deck or where it lies on the board, for each type of card.

Now I got instances of those cards with their values like

Card DOG = new Card(image, a, b, ...);
Card SHEEP = new Card(image, a, b, ...);
Card PIG = new Card(image, a, b, ...);

statically in Card. Maybe this is already the wrong way to do it?

How do I manage decks and stuff now, because I need specific instances after those blueprints, but fitted to their use, be it the deck where it is in or if it is on hand or on board, ...

The first thought I got was having something like Card.createInstance but is this something that is done?

I'm more or less asking for a best practice on doing something like this.

| improve this question | | | | |
\$\endgroup\$
  • 1
    \$\begingroup\$ "The first thought I got was having something like Card.createInstance but is this something that is done?" There is even a name for this pattern: It's a factory method. \$\endgroup\$ – Philipp Oct 9 '17 at 12:34
  • 1
    \$\begingroup\$ If you can have multiple instances of the same card in play at a time, then you might also want to look at the flyweight pattern, which helps avoid redundantly duplicating data that's the same between every instance. \$\endgroup\$ – DMGregory Oct 9 '17 at 14:00
2
\$\begingroup\$

There is never one "right" or "wrong" way to do something in programming. Just ways which do or do not work for you, your team and your project. So don't worry about doing things the right way, worry about writing a program you can still understand, change and extend later.

The pattern from the question has some implications for later maintainability. When each card is a static, they seem more like card-types to me than actual instances of cards. This conceptual difference might not matter much when each card only exists once in the game, but will matter when you allow multiple cards of the same type.

With this pattern, you can write code like if (lastCard == Card.PIG) sayOink();. But because the ==-operator only checks if both references point to the same object, this only works when lastCard is a reference to Card.PIG. There might be multiple pig-cards in the game, but they are all represented by references to the one Card.PIG object. That means if Card.PIG has any internal state, that state will be shared by all pig-cards in the game.

When your game doesn't have state for individual cards, that's fine.

But if you have some mechanics where cards have a state (like counters on cards, a "tapping" mechanic like in some trading card games, etc...) or when you want cards themselves to be aware of their current location in the game (in the deck, in a player's hand, on the table), this is not going to work. You will then need an instantiated object for each card.

If you want to follow that architecture, you have two different classes Card and CardType. When there are 7 pig-cards in the game, you would have one CardType PIG and 7 instances of Card which each have a reference to PIG which you can pass to it in its constructor. Your code to initialize your deck of cards could look something like this:

static CardType DOG = new CardType(image, a, b, ...);
static CardType SHEEP = new CardType(image, a, b, ...);
static CardType PIG = new CardType(image, a, b, ...);

static int NUM_DOG_CARDS_PER_DECK = 3;
static int NUM_PIG_CARDS_PER_DECK = 7;    
static int NUM_SHEEP_CARDS_PER_DECK = 7;

[...]

int i = 0;

for (i = 0; i < NUM_DOG_CARDS_PER_DECK; i++) {
     deck.add(new Card(DOG));
}    
for (i = 0; i < NUM_PIG_CARDS_PER_DECK; i++) {
     deck.add(new Card(PIG));
}
for (i = 0; i < NUM_SHEEP_CARDS_PER_DECK; i++) {
     deck.add(new Card(SHEEP));
}

But there is always more than one way. Instead of having a CardType class, you could implement each card type as a separate class which inherits from Card. Then your code would look like this:

for (i = 0; i < NUM_DOG_CARDS_PER_DECK; i++) {
     deck.add(new DogCard());
}    
for (i = 0; i < NUM_PIG_CARDS_PER_DECK; i++) {
     deck.add(new PigCard());
}
for (i = 0; i < NUM_SHEEP_CARDS_PER_DECK; i++) {
     deck.add(new SheepCard());
}

The differences between different card types (like their names and images) would then be implemented in DogCard, SheepCard etc.. This architecture would be most appropriate if your card types don't just differ by the values of their variables, but also have some mechanical differences which require card-specific code. That code would then belong into those sub-classes.

| improve this answer | | | | |
\$\endgroup\$
  • \$\begingroup\$ You got exactly what I wanted. I didn't think about a CardType-concept at all, thats a really nice way to do this. I thank you in advance! :) \$\endgroup\$ – nitwhiz Oct 9 '17 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.