11

I'm designing game site where many (hopefully thousands) players will simultenaously play certain card games with each other. The deck is the standart 52 card deck. Each card has a suit and a rank. The cards will be being shuffled, dealed, picked, ordered, played all the time. My question is, should Card be an enum, a struct, or a class?

  • For enum: Let each card be a byte 0..51. So a card will occupy very little space. You can represent a hand as a Bit Set of 8 bytes. You can calculate the suit and rank of a given card very quickly when the need arises: i.e. suit(n) = n/13. This will be very efficient. If you need to write methods for Cards, write it through extension methods.

  • For struct: No, that's like writing machine code. A card is a simple structure, holds very little data, is immutable, is small. It does not have much behaviour, so make it a struct and treat it as a passive data structure. You can calculate an index in 0..51 from a given card very quickly when the need arises.

  • For class: No, that is not an object-oriented way of thinking. Make a Card class. Make it immutable. Create exactly 52 instances. Let a Card Pool hold those instances. So when one needs Queen of Spades, it will ask the Card Pool for that. There will be one and only one Queen of Spades even when there are thousands of games going on. Store an index field 0..51 in Card if you want.

I'm inclining towards the last option (class) but I'm not sure. I'm not much worried about performance; I will perhaps make more serious mistakes along the way. What I'm worried about is my entire point of view may be wrong; maybe this is a very easy decision and I hesitate because I lack some piece knowledge everyone else possesses.

What do you think?

Edit: About the behaviour of cards. I think a card will only know about other cards. For example it may define a partial order on "who beats who in Bridge". It does not need to know anything about the deck. And this will be server side code; certainly it will not need to know to draw itself on the screen etc.

Ali Ferhat
  • 2,511
  • 17
  • 24
  • 1
    This is probably not a constructive question for this site, as you are asking for people's opinions. That said, mine happens to be that your instincts are right. :P – Dan J Mar 29 '12 at 20:43
  • What kind of behavior will `Card` have? Will it know whether it's face down or which position it resides on a table? Will it remember of which deck it came from in order to know, e.g., what color its back is? – Jordão Mar 29 '12 at 20:51
  • I don't think a `CardPool` is a good idea, it looks like an early-and-unnecessary optimization... – Jordão Mar 29 '12 at 20:52
  • @Jordao I think a card will be immutable. Any changing behaviour like whether it is face up or face down will not be a part of it. – Ali Ferhat Mar 29 '12 at 21:01
  • 3
    Use good separation of concerns; it is not the job of the deuce of spades to tell you who won that last trick. If you want to implement the rules of Bridge then make an object that *represents the rules of Bridge.* – Eric Lippert Mar 29 '12 at 21:30
  • 1
    @Jordão - agreed, it's the same as making a "number pool" and asking for the number 10. – John Rasch Mar 29 '12 at 22:54
  • @AliFerhat: sure, makes sense; but also depends on your goals, that's why I asked. The basic question you should ask yourself is if cards are _[fungible](http://en.wikipedia.org/wiki/Fungibility) in your design_; all the rest will follow... – Jordão Mar 30 '12 at 00:47
  • "that's like writing machine code" - can you explain that? An immutable struct would be a fine choice because it needs to contain both suit and value for most games. – weston Mar 30 '12 at 08:30
  • @weston My alter ego which is supporting the struct option tells to my alter ego which is supporting the enum option: "Representing each card (only) as an index in a BitArray seems very low level thinking. Like machine code, it may be more performant but it'll result in more complicated code, it's not worth it." – Ali Ferhat Mar 30 '12 at 10:58
  • @AliFerhat I see, I read that as a comment on the struct, in that case I agree with what that alter ego says! – weston Mar 30 '12 at 13:22

7 Answers7

13

The fundamental question you should be asking yourself when deciding between a reference type or a value type is of course is the thing I am modeling logically a value, or a thing referred to? That's why value types and reference types are called "value types" and "reference types" in the first place.

Are you planning on treating a "card" as a reference, the same way that you would treat a physical object that has identity? Suppose for example you are modeling Canasta, which is played with two standard card decks at the same time. Will you ever want to be keeping track of two different queens of spades and treating them as referentially different?

Or are you going to treat them as values, the way you would treat numbers? You don't ever say "this number six over here is different from that number six over there" because numbers are only differentiated by their values.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    +1 That is *exact* point. The question is not `reference` or `value` but the **game**, or gaming behavioral model you gonna construct. – Tigran Mar 29 '12 at 21:40
  • Thank you. I'd treat the cards as values, like numbers I suppose. But this is tricky: The alternative is making an immutable reference type with memoization, so that there will be only one Queen of Spades in existence anyway. I honestly can't see the difference between being a value and being a reference in this case. (Or is it the case that there is none; an immutable memoized reference type is always semantically equivalent to a value type?) – Ali Ferhat Mar 29 '12 at 21:57
  • 5
    @AliFerhat: Correct; an immutable memoized reference type behaves very much like a value type. Essentially every "card" becomes a singleton. If the amount of information in the card is smaller than the size of a reference, I see little reason to make a singleton; what does the indirection buy you? – Eric Lippert Mar 29 '12 at 22:47
  • I think we can all agree that a java-style enum class would provide the best implementation. Distinct set of values, while still allowing methods. Mix this with C# properties...oh how glorious it would be :/ – Mr Anderson Jun 20 '16 at 21:09
5

Make a struct or class for card, the card's value should be an enum, and the card's suit is also an enum.

To make a deck, you could use a class, it contains a list of card's, has operations like Shuffle, etc.

Matthew
  • 24,703
  • 9
  • 76
  • 110
  • the only thing worth mantioning is to define `enum` with the values power off 2, cause it's only hope to identify combinations of the cards in given user's deck. – Tigran Mar 29 '12 at 20:53
  • 2
    @Tigran I'm not sure what you're getting at with this. – Matthew Mar 29 '12 at 20:58
  • 1
    @Matthew: The idea is that if there are 52 cards, each of which is present or not present in a given set, the set of cards may be represented as a single long integer. Nice concept, and there are times it's useful; one difficulty is that there are a variety of possible encodings, which are useful for different purposes. – supercat Mar 29 '12 at 21:23
  • @Matthew: if you define cards like `enum` values, how you gonna identify some set of cards available in the hand of specified user? To be clear: how can I understand if someone wins BlackJack? And what about Bridge? They are completely different games, with not only different rules (so behavioral model) but also different winning sets of cards. – Tigran Mar 29 '12 at 21:23
2

You could represent each card as a number, and wrap them then in both an enum and a class. If each number between 0 to 51 represents a card, you can then have an enum:

public enum Card
{
   HeartsAce = 0,
   Hearts2 = 1,
   // ... and so on
}

int nCard = 16;
Card myCard = (Card)nCard;

You could also have a class:

public class Card
{
    private readonly int _nSuitNumber = 0;
    private readonly int _nCardNumber = 0;

    public Card(int a_nNumber)
    {
        _nSuitNumber = a_nNumber / 13;  // 1 = Hearts, 2 = Diamonds ...
        _nCardNumber = a_nNumber % 13;  // 1 = ace, 2 = two
    }
}

Or better yet combine them.

public class Card
{
    private readonly Suit _suit;
    private readonly Value _value;

    public Card(int a_nNumber)
    {
        _suit = (Suit)(a_nNumber / 13);  // 0 = Hearts, 1 = Diamonds ...
        _value = (Value)(a_nNumber % 13 + 1);  // 1 = ace, 2 = two
    }

    public Suit Suit
    {
        get { return _suit; }
    }

    public Value Value
    {
        get { return _value; }
    }

    public int ToNumber()
    {
        return (int)_suit * 13 + ((int)_value - 1);
    }
}

public enum Suit
{
    Hearts = 0,
    Diamonds = 1,
    Clubs = 2,
    Spades = 3
}

public enum Value
{
    Ace = 1,
    Two = 2,
    Three = 3,
    Four = 4,
    Five = 5,
    Six = 6,
    Seven = 7,
    Eight = 8,
    Nine = 9,
    Ten = 10,
    Jack = 11,
    Queen = 12,
    King = 13,
}

With this class you have the benefit of both a number (0-52) and a class. The class is nothing more than a wrapper around the number. You can add whatever operations, method, properties to the class as you need. And when you store or transmit the data you need only use the number.

Jordan
  • 9,642
  • 10
  • 71
  • 141
  • Thank you. I agree in the principle that a Card may need to be converted between a number and a struct/class. – Ali Ferhat Mar 30 '12 at 11:02
2

I don't believe you are really asking the right question here. How to represent your card should be a matter determined by what you want to do with your cards; By answering the question of how to represent your card now, you're automatically constraining yourself in the ways which you can solve other more interesting/challenging problems which you'll be coming up against later.

The kinds of things you really ought to think about are how you are going to implement the behaviour of your program. for example

  • What will happen when a "card" is dealt? Do you want to represent a card being physically moved between the entities which hold those cards? (for example, the Deck, a Player's hand, a discard pile, the table, etc), or do you want those entities to merely reference a central repository of cards? or perhaps use an entirely separate counting mechanism?

  • How are you going to "order" cards? The games which you choose to implement may have different rules (e.g. Ace Low or High? do royal suits all have the same value? will there be a trump card?) These might be decisions which are determined on a game-by-game basis, and even changed during the game itself, therefore simply having a single definition of a card based on it's ordering may not prove to be a good abstraction.

    • What kinds of behaviour and game rules are you trying to represent which will depend on the card? Will the card itself have any dependencies?

    • Is the card going to be involved in user interaction, or is it purely a logical object used by the parts of your program which defines the card game(s)?

    • Have you thought about the possibility of using different kinds of cards? You mentioned a class in your original question, but if a card is going to define any kind of state, behaviour or interact with other classes, then you might first want to think about defining a Card interface before worrying about the details.

As a direct answer to your question - I don't personally think that you've supplied enough information for the answer to be anything other than an opinion based upon guesswork and personal preference; that's ok because usually it's difficult to come up with all the answers before you've begun implementation. You might want to try all three and see which best suits your needs. Don't constrain your design by making these kinds of decisions up front.

Ben Cottrell
  • 5,741
  • 1
  • 27
  • 34
  • Thank you. All your points are correct. I'm in process of dreaming about a framework as game agnostic as possible, which is perhaps not as fruitful as I thought. – Ali Ferhat Mar 29 '12 at 22:10
  • The "how are cards dealt" question is interesting. One possible advantage of using cards as a reference type is that each card could have a `Location` property of type `ICardCollection`, identifying the `ICardCollection` where it resides (the constructor of a card would require a reference to the `ICardCollection` where it would belong initially). The implementations of `ICardCollection` could be written so as to enforce the invariant that every card must at any moment always be in exactly one collection. – supercat Aug 24 '12 at 20:33
0

You might consider what abstraction you're trying to build. If a card is a low-level object passed around by other functions then a simpler enum might be more appropriate, but if you want a card to be able to draw() itself or whatever then you'll need something more sophisticated. How is the card going to fit into your application?

simpleigh
  • 2,854
  • 18
  • 19
0

I think this question can logically be answered. I'll take a look at it from an object oriented point of view. I think a card has two child elements, Suit and Value. Those should probably be enum. A hand (class) should probably contain a list of cards, which leads me to believe it would be best to have a card as struct or class. In order to determine if a hand has a pair, that would be a function of the hand class.

With multiple people playing games with different type of games, each deck would have to know what type of cards it has and how many. But the card itself would be immutable even in this case.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
0

I would go with a class or struct, using enums, and put an internal constructor on it:

publlic class PlayingCard
{
    internal PlayingdCard(CardSuit suit, CardFace face)
    {
        this.Suit = suit;
        this.Face = face;
    } 

    public CardSuit Suit { get; private set; }

    public CardFace Face { get; private set; }
}

Define your Deck class to manage cards, but (most importantly) have it allocate the cards during its construction:

public class Deck
{
    private List<PlayingCard> cards = new List<PlayingCard>();
    public Deck()
    {
        for(var i = 0; i < 4; i++)
        {
            for (var j = 1 to 13)
            {
                this.cards.Add(new PlayingCard((CardSuit)i, (CardFace)j));
            }
        }
    }

    public void Shuffle() { /* implement me */ }
    public PlayingCard GetNextCard() { /* implement me */ }

}

This way, the deck size is fixed, and the faces and suits are created with as little code as possible (though I'm sure someone can come up with a way to LINQify that).

Mike Hofer
  • 16,477
  • 11
  • 74
  • 110