OOP and card games

Started by
4 comments, last by Extrarius 17 years, 7 months ago
Say, I'm making a hearts game. I've got a Card and Hand (or Pile, I'll just call it Hand) class. Now, how should the connection between Card and Hand be done? Hand -> Card This is the most obvious way. However, in this case a card could be referenced by two hands. Card -> Hand This fixes the above problem - a card can only be in one hand. But this one has a problem, too: querying for all the cards in a hand - which is a common operation - seems to be a bit expensive. Card <-> Hand Here, both have a reference to each other, which makes a two-way reference. In the setter of each, it's checked whether the other one has the correct back-reference, and if not, it is made. This seems to be okay, but a bit ugly. Any ideas?
Advertisement
I think you may be over engineering this. Only design what you actually need. For your game, do you ever need to know, given a card, which hand it is in? If so, then you'll need a mapping from cards to hands. If you don't think you'll need such a thing, don't design it. You can always go back and add it later if it turns out to be needed. If you limit your design to the functionality you really need, not only will you save time, but your code will be easier to maintain.

I probably wouldn't use links at all here. I would make the card a value type in the hand. The card really has only a tiny ammount of data associated with it. As for ensuring that you don't end up with the same card in two hands, just ensure this at a higher level. For example, if cards are added to hands from a deck, and the deck only has one of each card, then you'll never end up with two of the same card.
Note: In the following sentences, you may replace "array" with "vector", "list", "stack" or "hashtable" as you please.

1. Create empty arrays for each location in the game. (deck, discard pile, uncovered cards, current trick, ...)

2. Write functions that will make sure that a card cannot be in more than one logical place at the same time; i.e. methods that move cards from one hand/pile to another. Use proper encapsulation to make sure that cards cannot be accessed/moved/transferred in any other way than through these functions. You will very rarely, if ever, need to check what pile a card is in; as the cards are objects of the game, not agents of the game. At least they should be.

3. Make these functions consistent and fail-safe. Trace the algorithms and make sure they do what they're supposed to do. Basic set theory (mathematics) will help you a great deal formalizing this.

4. Test the functions (automated testing). Add trickier functions (shuffling, sorting) and make sure they work consistently.

5. Write the rest of the game :) Instantiate 52 objects of class "Card" and populate your initial "deck" hand with them. Then, deal the cards by moving them from the deck into other hands accordingly.
Quote:Original post by Sijmen
Say, I'm making a hearts game. I've got a Card and Hand (or Pile, I'll just call it Hand) class. Now, how should the connection between Card and Hand be done?

Hand -> Card
This is the most obvious way. However, in this case a card could be referenced by two hands.

Card -> Hand
This fixes the above problem - a card can only be in one hand. But this one has a problem, too: querying for all the cards in a hand - which is a common operation - seems to be a bit expensive.

Card <-> Hand
Here, both have a reference to each other, which makes a two-way reference. In the setter of each, it's checked whether the other one has the correct back-reference, and if not, it is made. This seems to be okay, but a bit ugly.


Any ideas?


You're designing the code to solve a design problem. Don't design a problem.
Software is built on gaurantees. The goal of architecture & design is to create a set of constraints that allow you to reason about the system. If you can't guarantee a card is only in one hand, you cannot reason about the system.

This is the primary constraint of a card game:
A Player HasA Hand which Contains Cards retrieved from a Shoe that is formed from multiple Decks.
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Quote:Original post by Solias
I probably wouldn't use links at all here. I would make the card a value type in the hand. The card really has only a tiny ammount of data associated with it. As for ensuring that you don't end up with the same card in two hands, just ensure this at a higher level. For example, if cards are added to hands from a deck, and the deck only has one of each card, then you'll never end up with two of the same card.

Cards have not only very little data (possibly their name and/or graphics), they also have almost no behaviour (possibly draw themselves on the screen); cards only need an identity, and it might be a good idea to not implement them as objects, or to give them no data members.
As Thygrrr already observed, card games are concerned with the movement of a fixed set of cards among a fixed set of states. The functions that move cards between these states implement and enforce game rules; the more interesting behaviour belongs in card sets, not in the cards.
For example, rules and AI routines ask questions like:
- What poker combination does a hand contain?
- What combinations currently on the table can be extended with a certain card?
- Does an hand contain any cards in a specified set?
- What card is on top of the deck?
- Has a card been played? (In other words, is it in one of the face up sets?)
All these computations, which are sometimes nontrivial, are concerned with the contents of card locations and care only about card identity; user interface is the only place that cares about individual cards.

Omae Wa Mou Shindeiru

Personally, I'd make a Card class that stores a {ID, Type} pair to represent which cards are in a hand/deck/etc, then I'd have a CardType object that would store advanced information about cards.
You'd basically have one instance of CardType for each unique card that exists in the game, then 'Type' would be a pointer to the appropriate instance of CardType. 'ID' could simply be taken from an integer that starts at 0 and is incremented each time a new instance of Card is created

CardType: {Suit, Value, Graphic, ...}
Card: {ID, CardType*}
Hand: Set<Card>
Deck: Set<Card>

This also works well for far more complicated card games, such as "Magic: The Gathering"
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk

This topic is closed to new replies.

Advertisement