My Card Class

Started by
28 comments, last by Zahlman 16 years, 10 months ago
// cardclasses.hpp
// Classes for a card game

#include <string>
#include <sstream>
#include <cstdlib>

class Card
{
public:
    Card() // Constructor
    { // use .setCard()! This constructor only sets default values!
        card_symbol = 'e';
        card_symbol_color_black = false;
        card_value = '0';
        card_name.str("ER");
    }
    
    unsigned char getSymbol() const
    {
        return card_symbol;
    }
    bool isColorBlack() const
    {
        return card_symbol_color_black;
    }
    unsigned short int getValue() const
    {
        return card_value;
    }
    std::string getName() const
    {
        return card_name.str();
    }
    
    void setCard( unsigned char symbol, unsigned short int value )
    {
        switch( tolower( symbol ) ) { // Set the symbol
            case 'c': // Cross
            card_symbol = 'c';
            card_symbol_color_black = true;
            break;
            case 'd': // Diamond
            card_symbol = 'd';
            card_symbol_color_black = false;
            break;
            case 'h': // Hearts
            card_symbol = 'h';
            card_symbol_color_black = false;
            break;
            default: // Spades (safety feature as well)
            card_symbol = 's';
            card_symbol_color_black = true;
        }

        card_value = value; // Set the value ( 1..13 )

        card_name.str("");
        
        if( ( card_value >= 2 ) && ( card_value <= 10 ) ) { // Name the card
            card_name << card_value;
        }
        else switch( card_value ) { // Name the card (2)
            case 1:
            card_name << 'A';
            break;
            case 11:
            card_name << 'J';
            break;
            case 12:
            card_name << 'Q';
            break;
            default:
            card_name << 'K';
        }
        
        card_name << card_symbol; // Name the card's symbol.
    }


private: // Vars have std values for debugging
    unsigned char card_symbol;
    bool card_symbol_color_black; // True for black cards, false for red cards
    unsigned short int card_value;
    std::stringstream card_name;
};

class Deck
{
public:
    Deck( bool shuffle = false )
    {
        for( int i = 0; i < 13; i++ ) {
            Carddeck.setCard( 'c', i+1 );
        }
        for( int i = 0; i < 13; i++ ) {
            Carddeck[i+13].setCard( 'd', i+1 );
        }
        for( int i = 0; i < 13; i++ ) {
            Carddeck[i+26].setCard( 'h', i+1 );
        }
        for( int i = 0; i < 13; i++ ) {
            Carddeck[i+39].setCard( 's', i+1 );
        }
        
        if( shuffle ) {
            doShuffle();
        }
    }
    
    void doShuffle() {
        for( int i = 0; i < 52; i++ ) { // Preparation
            Tmpdeck.setCard( Carddeck.getSymbol(), Carddeck.getValue() );
            shuffle_used = false;
        }
        
        srand( time( NULL ) );
        
        for( int i = 0; i < 52; i++ ) { // Hardcore shuffling (could take REALLY long)
            int random;
            do {
                random = rand() %52; // Get a random number 0..51
                Carddeck.setCard( Tmpdeck[random].getSymbol(), Tmpdeck[random].getValue() ); // Set the carddeck by it's copy
            } while( shuffle_used[random] );
            std::cout << "."; // Prehistoric progress-bar (gives 52 .'s)
        }
    }
    
    unsigned char getCardSymbol( int position ) const
    {
        return Carddeck[position].getSymbol();
    }
    bool isCardColorBlack( int position ) const
    {
        return Carddeck[position].isColorBlack();
    }
    unsigned short int getCardValue( int position ) const
    {
        return Carddeck[position].getValue();
    }
    std::string getCardName( int position ) const
    {
        return Carddeck[position].getName();
    }


private:
    Card Carddeck[51]; // No jokers
    Card Tmpdeck[51];
    bool shuffle_used[51];
};

It's pretty straight-forward, I hope you can understand my simple code. This compiles just fine with Dev-C++. When I tell my program to shuffle the deck over the constructor, the program crashes on shuffling. I've figured out that the two bits of code causing this are
Tmpdeck.setCard( Carddeck.getSymbol(), Carddeck.getValue() );
and
Carddeck.setCard( Tmpdeck[random].getSymbol(), Tmpdeck[random].getValue() ); // Set the carddeck by it's copy
. I have no idea what's wrong with this code. I had to do it this way since I wasn't able to use operator= ... I hope someone can point out my mistake. PS: I know the code can be done more professionally with #DEFINE 's or with vectors instead of arrays, I just don't have the C++ knowledge...
Advertisement
  1. Your card deck only contains 51 cards. It will overflow when accessing the 52nd. This is probably causing the crash.
  2. Create a non-default constructor, so that cards can be initialized directly into their correct state.
  3. You don't need a card_symbol_color_black variable, since its value can be computed at runtime by a function from the card symbol.
  4. Why the card_ prefix in the member variables?
  5. Don't use an std::stringstream (use it locally only when needed), use std::string for storage. This will let you use the default operator=.
  6. Use operator=, and remove the setCard function.
  7. Use an enumeration instead of chaacters for the card symbol.
  8. Make the temporary array local to the function where it's used, and pass it by reference to subroutines. Same for shuffle_used.
  9. Drop all the get and is functions in the deck class. Overload operator[] instead.
  10. Don't use "safety" values in your switch statements. Write one case per correct value, and assert(false) in the default clause.
Quote:Original post by ToohrVyk
  1. Your card deck only contains 51 cards. It will overflow when accessing the 52nd. This is probably causing the crash.
And on that note, it also writes out of bounds of it's array for just this reason. In these lines :

for( int i = 0; i < 52; i++ ) { // Preparation   Tmpdeck.setCard( Carddeck.getSymbol(), Carddeck.getValue() );


and these :

for( int i = 0; i < 52; i++ ) { // Hardcore shuffling (could take REALLY long)    int random;    do {        random = rand() %52; // Get a random number 0..51        Carddeck.setCard( Tmpdeck[random].getSymbol(), Tmpdeck[random].getValue() ); // Set the carddeck by it's copy    } while( shuffle_used[random] );


Maybe others too, but these were the most obvious.


1. Yes, an obvious mistake, thank you for pointing it out :) I guess this is a real killer error in a big project, since the compiler didn't tell me about it.
2. A what? This is my first Object ever, because I'm learning about OOP
3. Good point, I just felt like making it more complete. I'm programming a little poker-game, so the whole colour issue is obsolete anyway.
4. Why not? I see your point though, I migh change this design flaw later.
5. Alright :)
6. How would I go on about that? I had trouble using a constructor with Card in combination with the Card array in Deck
7. I thought of that after making this class aswell, but I haven't gotten to changing it yet. I was going to use some #DEFINE 's.
8. Okay, I don't understadn what you mean with "by reference" though.
9. This is hocuspocus to me, please explain.
10. Same as 9
Quote:Original post by c4c0d3m0n
1. Yes, an obvious mistake, thank you for pointing it out :) I guess this is a real killer error in a big project, since the compiler didn't tell me about it.


The compiler gives you more than enough rope to hang yourself.

That is one of the reasons why you want to add asserts, unit tests, and write your code so that you aren't directly manipulating pointers and arrays.

Pointer arithmetic is dangerous. There are techniques and practices that let you avoid writing dangerous code, and increase the ability for the compiler to detect bugs before they crash you at runtime.

Quote:
Quote:# Use operator=, and remove the setCard function.

6. How would I go on about that? I had trouble using a constructor with Card in combination with the Card array in Deck


The default operator= will work if all of the parts of the card have the same assignment-semantics: once you change std::stringstream to std::string, this will be true.

Quote:
Quote:# Use an enumeration instead of chaacters for the card symbol.

7. I thought of that after making this class aswell, but I haven't gotten to changing it yet. I was going to use some #DEFINE 's.


#define is an example of something you should avoid unless you don't have another choice.

enum suit { hearts, diamonds, spades, clubs };


gives you names that the compiler can understand that can be used as constants.


Quote:
Quote:# Make the temporary array local to the function where it's used, and pass it by reference to subroutines. Same for shuffle_used.

8. Okay, I don't understadn what you mean with "by reference" though.


struct example {  int foo;  int bar[100];};void change_example( example& ex ) {  ex.foo = 2;  ex.bar[0] = -1;}


I passed the struct "example" by reference into the function change_example.

A reference is somewhat like a const pointer to non-const data, with the following exceptions:

foo& a; // error, references *must* be initializedfoo c;foo& b = c; // valid.  This is analagous to foo*const x = &cfoo d;b = d; // this is analagous to *x = d


References must be bound. Once bound, references cannot be rebound. Assignment changes the data you are bound to, and not which item you are bound to (this is very different than pointers).

You can use reference parameters to functions to make functions that change their parameters.

Quote:
Quote:# Drop all the get and is functions in the deck class. Overload operator[] instead.

9. This is hocuspocus to me, please explain.


struct example1 {  int one, two, three;  int& operator[](int index) {    if (index == 1) return one;    if (index == 2) return two;    if (index == 3) return three;    assert(false);  }  int const& operator[](int index)const {    if (index == 1) return one;    if (index == 2) return two;    if (index == 3) return three;    assert(false);      }};struct datum {  int x,y;};struct example2 {  datum data[100];  datum& operator[](int index) {    assert( index < 100 && index >= 0 );    return data[index];  }  datum const& operator[](int index)const {    assert( index < 100 && index >= 0 );    return data[index];  }};// use:example2 foo;datum bar = {2,3};foo[5] = bar; // sets foo.data[5] for me


Quote:
Quote:# Don't use "safety" values in your switch statements. Write one case per correct value, and assert(false) in the default clause.

10. Same as 9


A better switch statement pattern:
      switch( card_value ) { // Name the card (2)          case 1: {              card_name << 'A';          } break;          case 11: {              card_name << 'J';          } break;          case 12: {              card_name << 'Q';          } break;          case 13: {              card_name << 'K';          } break;          default: {              assert(false); // we should never see this -- complain              card_name << 'A'; // program defensively          } break;        }

Some miscellaneous tips:
  1. Get rid of the "card_" in your member variable names. It's redundant.
  2. Instead of setCard, create a constructor that uses the same parameters. If you must have a default constructor, it should create a valid card.
  3. Consider representing the card suit using an enum rather than a char.
  4. Make the card value an int instead of an unsigned short int. It is simpler and neither unsigned nor short gain you anything.
  5. Determine the color of the card from the suit, rather than storing it separately.
  6. Use std::random_shuffle to shuffle the deck. Your shuffle is broken, but even if it worked it would be slower and less random than std::random_shuffle.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Wow, a lot of information suddenly...
Since 9 and 10 are still over my head, I decided to start over making my classes.

/***  ## cardclasses.hpp****  Card***/enum suit { clubs, diamonds, hearts, spades };class Card{public:    Card( int card_value, suit card_symbol ) { // Constructor        if( card_value > 13 )            assert( false );        else            value = card_value;                symbol = card_symbol;    }    private:    int value;    suit symbol;};

This is as far as I have come... I kinda figured out what assert() does. My class now seems very simple, I can still easely add some get functions. You told me not to do that though. My question is: Why would I use a class over a struct or vice versa (keeping in mind I still have to make a deck)?

I don't know what pointers nor references are. I haven't been learning C++ that long, I'm trying to figure out what they are and what they are used for in this moment of my learning curve. I've programmed a little database and TicTacToe before in C++ without using this hocuspocus... :S Thanks for any help

Edit: I'm going to make an enum for the values aswell, if this still allows me to do simple mathematical comparisons (considering this is going to be a cardgame)
Quote:Original post by c4c0d3m0n
My question is: Why would I use a class over a struct or vice versa (keeping in mind I still have to make a deck)?


The only difference between the two in C++ is that one defaults to private members (class) whilst the other default to public members (struct).
class my_class {  int my_var;}struct my_struct {  int my_var;}

Since we've not specified the accessibility for these variables (public, private, protected) my_var in the class will be private, whilst in the struct it will be public.

EDIT:
Quote:Original post by c4c0d3m0n
Edit: I'm going to make an enum for the values aswell, if this still allows me to do simple mathematical comparisons (considering this is going to be a cardgame)


Yes, you can specify the numerical value for each enum as well.
enum suit { clubs = 8, diamonds = 46, hearts = 20, spades = 12 };
Best regards, Omid
That's not what I meant. I meant weather I could use enum for the card's values aswell, since Kings, Queeens, Jacks and Aces would be nice enumerations ;)
Quote:Original post by c4c0d3m0n
That's not what I meant. I meant weather I could use enum for the card's values aswell, since Kings, Queeens, Jacks and Aces would be nice enumerations ;)


I think it would be more useful to keep the card value as an integer and create constants for the face cards and aces. Maybe something like this:
     class Card    {        ...        static int const LOW_ACE  = 1;        static int const JACK     = 11;        static int const QUEEN    = 12;        static int const KING     = 13;        static int const HIGH_ACE = 14;        ...    }; 
Also, rather than doing this:
    if( card_value > 13 )        assert( false );    else        value = card_value; 
do this. It clearer:
    assert( card_value <= 13 );    value = card_value; 


John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!

This topic is closed to new replies.

Advertisement