Hi all there.

Given the huge amount of blackjack/card games threads and me being personally interested in poker and other games, I was feeling like starting to play with a few useful (at least I hope so) classes for dealing (no pun intended :P) with card games.

I started my work on the paper first and I thought to write the following classes:

1 - A card class;
2 - A deck class containing all the (used) cards in the deck and functions such as deal, shuffle, etc.
3 - A hand class containing a variable set of cards (a deck object can deal hand objects) and a member value that can be accessed to give a score to the hand (for example in a poker game an unlucky hand containing only an Ace (high card) could have the value of 1, in a blackjack game the hand [A, J] will have the value of 21 etc.)
4 - A player class that contains a hand and a score (i.e. the money)
5 - A game class that contains a set of players, a deck and some functions implementing the rules.

Is that approach acceptable in your opinion? Do you have any criticism / suggestions? As always any hint is welcomed.

Please note that this thread is NOT urgent :P it's not homework of any sort.

here's what I did so far (could be useful for some of the threads I read throughout the forum).

playingcards.h

#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <time.h>

#ifndef H_PLAYINGCARDS
#define H_PLAYINGCARDS

enum frenchValue {
    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
};

enum frenchSuit { HEARTS = 1, DIAMONDS = 2, CLUBS = 3, SPADES = 4 };

class frenchCard;
class frenchDeck;
class emptyDeckException;
std::ostream& operator<<(std::ostream& lhs, frenchCard& obj);
std::ostream& operator<<(std::ostream& lhs, frenchDeck& obj);
bool frenchSuitCompare(frenchCard op1, frenchCard op2);
bool frenchValueCompare(frenchCard op1, frenchCard op2);

class frenchCard {
    public:
        frenchCard(frenchValue v, frenchSuit s);
        friend std::ostream& operator<<(std::ostream& lhs, frenchCard& obj);
        friend bool frenchSuitCompare(frenchCard op1, frenchCard op2);
        friend bool frenchValueCompare(frenchCard op1, frenchCard op2);
        const static std::string strValues[13];
        const static std::string strSuits[4];
        frenchSuit getSuit() { return suit; }
        frenchValue getValue() { return value; }
        friend class frenchDeck;
        friend class frenchHand;
        friend class frenchPlayer;
        friend class frenchGame;
    private:
        frenchSuit suit;
        frenchValue value;
};

class frenchDeck {
    public:
        frenchDeck();
        virtual void build();
        void shuffle();
        void reinsert();
        void discard(std::vector<frenchCard> ds);
        std::vector<frenchCard> deal(int nC);
        friend std::ostream& operator<<(std::ostream& lhs, frenchDeck& obj);
    protected:
        std::vector<frenchCard> deck;
        std::vector<frenchCard> discarded;
        int numCards;
        int position;
};

class emptyDeckException : public std::runtime_error {
    public:
        emptyDeckException() : std::runtime_error("the deck is empty") { };
};

#endif // H_PLAYINGCARDS

playingcards.cpp

#include "playingcards.h"

bool frenchSuitCompare(frenchCard op1, frenchCard op2) {
    return (op1.suit < op2.suit) ? true : false;
}

bool frenchValueCompare(frenchCard op1, frenchCard op2) {
    return (op1.value < op2.value) ? true : false;
}

const std::string frenchCard::strValues[13] = { "Ace", "Two", "Three",
                                                "Four", "Five", "Six",
                                                "Seven", "Eight", "Nine",
                                                "Ten", "Jack", "Queen", "King" };

const std::string frenchCard::strSuits[4] = { "Hearts", "Diamonds", "Clubs", "Spades" };

frenchCard::frenchCard(frenchValue v, frenchSuit s) {
    value = v;
    suit = s;
}

std::ostream& operator<<(std::ostream& lhs, frenchCard& obj) {
    lhs << obj.strValues[obj.value - 1] << " of " << obj.strSuits[obj.suit - 1];
    return lhs;
}

void frenchDeck::build() {
    numCards = 52;
    position = 0;
    for(int i = 1; i <= 4; i++) {
        for(int j = 1; j <= 13; j++) {
            frenchSuit s = (frenchSuit) i;
            frenchValue v = (frenchValue) j;
            deck.push_back(frenchCard(v, s));
        }
    }
}

frenchDeck::frenchDeck() {
    build();
}

void frenchDeck::shuffle() {
    deck.clear();
    build();
    srand((unsigned)time(NULL));
    std::random_shuffle(deck.begin(), deck.end());
}

void frenchDeck::discard(std::vector<frenchCard> ds) {
    for(unsigned int i = 0; i < ds.size(); i++) {
        discarded.push_back(ds[i]);
    }
}

void frenchDeck::reinsert() {
    for(unsigned int i = 0; i < discarded.size(); i++) {
        deck.push_back(discarded[i]);
    }
    numCards += discarded.size();
    discarded.clear();
    srand((unsigned)time(NULL));
    std::random_shuffle(deck.begin()+position, deck.end());
}

std::vector<frenchCard> frenchDeck::deal(int nC) {
    std::vector<frenchCard> hand;
    if(numCards-position<nC) {
        if(discarded.empty()) {
            throw emptyDeckException();
        }
        else {
            reinsert();
            return deal(nC);
        }
    }
    for(int i = position; i < (position + nC); i++) {
        hand.push_back(deck[i]);
    }
    position += nC;
    return hand;
}

std::ostream& operator<<(std::ostream& lhs, frenchDeck& obj) {
    for(int i = obj.position; i < obj.numCards; i++) {
        lhs << obj.deck[i] << std::endl;
    }
    return lhs;
}

hand.h

#include "playingcards.h"

#ifndef H_HAND
#define H_HAND

class frenchHand;

class frenchHand {
    public:
        frenchHand();
        frenchHand(std::vector<frenchCard> h);
        std::vector<frenchCard> operator=(std::vector<frenchCard> h);
        void setValue(int value) { handValue = value; }
        void sortByValue();
        void sortBySuit();
        int getValue() { return handValue; }
        int getNumCards() { return numCards; }
        frenchCard& operator[](int index) { return hand[index]; }
        std::vector<frenchCard> operator*() { return hand; }
    protected:
        std::vector<frenchCard> hand;
        int numCards;
        int handValue;

};

#endif // H_HAND

hand.cpp

#include "hand.h"

frenchHand::frenchHand() {
    handValue = 0;
}

frenchHand::frenchHand(std::vector<frenchCard> h) {
    hand = h;
    numCards = h.size();
    handValue = 0;
}

std::vector<frenchCard> frenchHand::operator=(std::vector<frenchCard> h) {
    hand = h;
    numCards = h.size();
    handValue = 0;
    return h;
}

void frenchHand::sortBySuit() {
    std::sort(hand.begin(), hand.end(), frenchSuitCompare);
}

void frenchHand::sortByValue() {
    std::sort(hand.begin(), hand.end(), frenchValueCompare);
}

a little testfile.cpp

#include "playingcards.h"
#include "hand.h"
#include <iostream>

using namespace std;

int main(int argc, char *argv[]) {
    frenchDeck myDeck;
    myDeck.shuffle();
    frenchHand myHand;
    myHand = myDeck.deal(5);
    cout << "RAW HAND:" << endl;
    for(int i = 0; i < myHand.getNumCards(); i++) {
        cout << "   " << myHand[i] << endl;
    }
    myHand.sortBySuit();
    cout << "HAND SORTED BY SUIT:" << endl;
    for(int i = 0; i < myHand.getNumCards(); i++) {
        cout << "   " << myHand[i] << endl;
    }
    myHand.sortByValue();
    cout << "HAND SORTED BY VALUE:" << endl;
    for(int i = 0; i < myHand.getNumCards(); i++) {
        cout << "   " << myHand[i] << endl;
    }
    return EXIT_SUCCESS;
}

Thanks for your consideration and sorry for the long post :)

P.S.
In case you wonder, the prefix "french" is there because I'm developing also the "italian" equivalents of these classes (different cards, different decks ... definitely different games to play with :P)

cikara21 commented: Nice..Learn fast..Very talented..Regard to F.Totti.. +1

Recommended Answers

All 17 Replies

I'd put the deal protocol in the game section as not all card games are dealt the same way.

commented: thank you :-) +2

There is no "right" and "wrong" in these types of designs, but rather "pros" and "cons". You are trying to write some generic classes so that you don't have to rewrite everything for each different game, which is good. At some point, the games get so dissimilar that it's no longer worth generalizing (i.e. the game of War might be so different from 7-card stud that it may be better to write them separately, whereas 5-card stud and 7-card stud are sufficiently similar to use most of the same underlying code). Where that cutoff is when you have to write different underlying classes is not always an easily defined line.

I don't know exactly what you are going for, but this may be a job for polymorphism, particularly on something like the card, deck, and game classes. Your mention of "Italian" versus "French" is a perfect example. The rules of 5-card stud will be the same in either, but you would want to change the "enum" values of both (i.e. "King" in French English, Italian, etc.). You have a "frenchHand" class. Maybe you should have a "hand" class and have "italianHand" and "frenchHand" be derived classes. Similarly when you get to implementing the different games, have a "Poker" class, then derived classes like "Stud" and "Draw" and then "5-card stud" and "7-card stud" deriving from "Stud".

If it were my design, I would pick about ten games, decide how they are similar and different, and derive my classes/design from that.

commented: Thank you for your time and advices +2

I'd put the deal protocol in the game section as not all card games are dealt the same way.

Sounds good to me, I'll make that change :-) Thank you.

@VernonDozier: Thank you too for the advices.
My short-term goal would be to make a Poker game - not that I'd like to play it more than others already existing, I want to do it mainly in the attempt of teaching myself something in the process. I like the subject and this seems a good exercise to me.

I'd like (long-term goal) to write a few games (some similar, some others not quite) for the same purpose: learn something.
I thought that I'd like to reuse as much code as it would be possible, so I'm walking this way and I'll keep you informed of any significant progress. Anything is welcomed as usual, many opinions feed the growing mind it's said :P

Thank you again for the advices, I liked particularly that one of making frenchHand a derived class of a general hand class.

Ok, 11 days have passed and though busy with many other things I brought this some steps further.

I restructured slightly the whole design - basically now all the classes are enough abstract to deal with card games (exception: build method in deck class, but it could be overriden), or at least I hope so.
I confined the specialization in a pokerGame class that'll handle the actual game action.

As of now I'm stuck - there's probably a stupid reason to that but I can't see it. I find it difficult to manage this project because it's bigger than the other things I attempted ^^"
I was trying to write a deal and a showdown functions in the pokerGame class to test the mechanism of dealing cards to every player. Unexpectedly what caused me troubles was the showdown function.

// game.cpp
void pokerGame::showdown() {
    for(unsigned int i = 0; i < m_players.size(); ++i) {
        if(m_players[i].getHand().getSize()!=0) {
            std::cout << m_players[i].getName() << "'s hand: ";
            std::cout /*<< m_players[i].getHand()*/ << std::endl;
        }
    }
    return;
}

If I uncomment that line it gives me segmentation fault when executing. I am using Code::Blocks and I have tried the debugger and though not being confident with it could have caused some misunderstanding I really can't see any problem there. Perhaps I'm just trying something still beyond my reach and I can't even use the debugger properly :S

The program is too long to post it here - I am going to post just a few extracts and attach an archive with the whole thing.

/* this is the main program (if you download the file, the commented part not present here is another (previous) test that works fine */
#include "card.h"
#include "hand.h"
#include "deck.h"
#include "game.h"

int main() {
    std::vector<std::string> names;
    names.push_back("Ludovico");
    names.push_back("Cpu");
    std::vector<bool> areCpu;
    areCpu.push_back(false);
    areCpu.push_back(true);
    std::vector<float> initialScores;
    initialScores.push_back(4000.0);
    initialScores.push_back(4000.0);
    pokerGame myGame(names, areCpu, initialScores);
    myGame.deal();
    myGame.showdown();
    return EXIT_SUCCESS;
}

these are the other pieces of code that I think may be connected with the problem:

// pokerGame class declaration
class pokerGame {
    public:
        pokerGame(std::vector<std::string> names, std::vector<bool> areCpu,
                  std::vector<float> initialScores);
        void deal();
        void showdown();
        std::vector<player> getPlayers() { return m_players; }
        deck getDeck() { return m_deck; }
        int getNumPlayers() { return m_numPlayers; }
    protected:
        value m_minInDeck;
        std::vector<player> m_players;
        deck m_deck;
        int m_numPlayers;
        unsigned int m_dealer; 
        // position of the dealer relative to m_players vector
};
// pokerGame class constructor and deal function
pokerGame::pokerGame(std::vector<std::string> names, std::vector<bool> areCpu,
                     std::vector<float> initialScores) : m_deck(names.size()) {
    m_numPlayers = names.size();
    m_minInDeck = (value) (11 - m_numPlayers);
    unsigned int handsize = 5;
    for(int i = 0; i < m_numPlayers; ++i) {
        m_players.push_back(player(handsize, initialScores[i], names[i], areCpu[i]));
    }
    srand((unsigned)time(NULL));
    m_dealer = rand() % m_numPlayers;
}

void pokerGame::deal() {
    m_deck.build();
    m_deck.shuffle();
    for(int j = 0; j < 5; ++j) {
        for(unsigned int i = 0; i < m_players.size(); ++i) {
            m_players[(i+m_dealer+1)%m_numPlayers].deal(m_deck.deal());
            std::cout << "dealing to " << m_players[(i+m_dealer+1)%m_numPlayers].getName() << std::endl;
        }
    }
    return;
}
// player class declaration
class player {
    public:
        player(unsigned int handsize, float initialScore = 0., std::string name = "Player", bool isCpu = false);
        bool deal(card c) { return m_hand.deal(c); }
        hand& getHand() { return m_hand; }
        float getScore() { return m_score; }
        std::string getName() { return m_name; }
        bool isCpu() { return m_isCpu; }
    protected:
        unsigned int m_handsize;
        hand m_hand;
        bool m_isCpu;
        float m_score; // it's money in poker, could be anything in other games
        std::string m_name;
};

Well I think that's all... if I forgot something important please ask.
This is not homework and I'm not in a hurry but any hint would be much appreciated anyway :)

Meanwhile, thank you very much for your time if you read all this mess :P

P.S.
I tried to make the code readable and understandable but any criticism in that regard would be greatly appreciated too!

let me tasted.....:)

I took the zip file and made a MS project around it.

I had to add #include <algorithm> to hand.cpp but other than that it compiled.

I then uncommented your troublesome line:

void pokerGame::showdown() {
    for(unsigned int i = 0; i < m_players.size(); ++i) {
        if(m_players[i].getHand().getSize()!=0) {
            std::cout << m_players[i].getName() << "'s hand: ";
            std::cout << m_players[i].getHand() << std::endl;
        }
    }
    return;
}
void pokerGame::showdown() {
    for(unsigned int i = 0; i < m_players.size(); ++i) {
        if(m_players[i].getHand().getSize()!=0) {
            std::cout << m_players[i].getName() << "'s hand: ";
            std::cout << m_players[i].getHand() << std::endl;
        }
    }
    return;
}

It runs ok, but the output is strange:

dealing to Ludovico
dealing to Cpu
dealing to Ludovico
dealing to Cpu
dealing to Ludovico
dealing to Cpu
dealing to Ludovico
dealing to Cpu
dealing to Ludovico
dealing to Cpu
Ludovico's hand: Ace of Nine, Two of Spades, Four of Five, Three of Six, Four of
 Six
Cpu's hand: Ace of Ace, Ace of Five, Two of Four, Ace of Six, Two of Seven

I think the problem is here:

void deck::build(int numPlayers) {
    m_discarded.clear();
    m_dealt.clear();
    m_deck.clear();
    if(numPlayers == 0) {
        for(int i = 1; i <= 4; ++i) {
            for(int j = 1; j <= 13; ++j) {
                // -- a card expects value , suit which is what we say we're passing
                // -- but j is 1-13 and i is 1-4
                m_deck.push_back(card((value) i, (suit) j));
            }
        }
        return;
    }
    for(int i = 1; i <= 4; ++i) {
        m_deck.push_back(card((value) 1, (suit) i));
        for(int j = 11 - numPlayers; j <= 13; ++j) {
            m_deck.push_back(card((value) j, (suit) i));
        }
    }
    return;
}
commented: Great, you saved me from a big headache! Thanks +2

You're great :) that was driving me mad.

Actually there was a double error, it shouldn't have entered the if because the line m_deck.build(); in pokerGame::deal() should have been m_deck.build(m_numPlayers); .

A lucky error though, because it made "me" (well, you!) discover the other, nasty error.

I still don't understand why it would caused a segfault to me while just a wrong output to you though.

Well, thank you again!

can i get the different result every time i deal???...

That's what it's supposed to do - shuffle the deck and deal different cards every time you run the program.

i mean...
something like this one...

//... 1
myGame.deal();
myGame.showdown();
//... 2
myGame.deal();
myGame.showdown();
commented: thanks for making me notice this :) +2

I see what you mean.

I was only worried to make it work so I forgot a couple of important things:

1 - At the beginning of the pokerGame::deal() function I had to clear all players' hands, something like

for(int i = 0; i < m_numPlayers; ++i) {
     m_players[i].getHand().clear();
}

otherwise the next hand won't be dealt (because theirs are already full)

2 - At the end of the pokerGame::showdown() function I had to update the m_dealer variable with m_dealer = (m_dealer + 1) % m_numPlayers; Thanks for making me notice these.

I am currently working on making it recognize the points, I'll post the whole program zipped as soon as I'm done with this :)

EDIT: Being the deck random shuffling seed initialised from the clock, dealing a couple of hands without anything in between will just result in the same hands dealt to different players. This won't be a problem during a game but I thought to mention it. (I tested this inserting and removing non-portable sleep(1); between the two pokerGame::deal() calls)

aye...g luck..

Ok just a little upgrade:

some minor changes throughout the code and it now recognizes the hands as real poker hands.

testmain.cpp provides a little example of a 4-hand "match" (there's still to do comparison between points to decide winner...).
No action at all but it kinda works so far :P

I hope this'll help someone other than me, in the meantime I am much grateful for any criticism and I want to thank all of you spending time with this :)

Please make the following changes (if you can) so I don't have to re-make them the next time I re-import your code:

Add #include <time.h> to deck.cpp and game.cpp

(You know you're only supposed to call srand() once per program run, right?)

Add #include <algorithm> to game.cpp and hand.cpp

Ok changes made :) next attachment (to appear after christmas I guess) will have them.

srand() call in deck::build was there because I used deck class directly before writing pokerGame class - now it's gone as it should, thanks for noticing!

I'm seeing an assertion (for array index out of bounds) on the last line posted below:

void pokerGame::showdown() {
    m_points.clear();
    m_points.reserve(m_numPlayers);
    for(int i = 0; i < m_numPlayers; ++i) {
        if(m_arePlaying[(i+m_turn)%m_numPlayers]) {
            m_points[(i+m_turn)%m_numPlayers] = pokerPoint(m_players[(i+m_turn)%m_numPlayers].getHand(), m_minInDeck);

The clear() gets rid of all of the elements in the vector.
You call reserve() which makes sure the array can hold that many but doesn't fill it.
You could call resize() to actually change the size of the vector, but it appears that it would require pokerPoint to have a default constructor and it doesn't have one.

What do you think about giving pokerPoint a default constructor that sets it to a non-hand (or really low hand if a non-hand isn't possible). Then instead of calling clear() and reserve(), leave the pokerPoint instances in but call a new method pokerPoint::update(hand h, value minInDeck) passing in the players hand. This new method would update the pokerPoint based on the new hand. (You could keep re-constructing them to get the points, but is there a value added to create/destroy/re-create?)

I may try to do something like that myself just to get it running again.

ok, I did the default constructor and called resize(). The assert is gone and the program runs.

I also did a little more playing, I added a third player 'Bob' who is also a CPU (not that it really matters right now).

Here's the output from my last run:

### HAND 1 ###
CPU's hand: Nine of Hearts, Ten of Spades, Jack of Hearts, Queen of Spades, King of Spades
Bob's hand: Ace of Spades, Eight of Spades, Jack of Diamonds, Jack of Spades, Queen of Hearts
Ludovico's hand: Eight of Hearts, Nine of Diamonds, Ten of Clubs, Jack of Clubs, Queen of Clubs

CPU has Straight, King high
Bob has One Pair, Jack
Ludovico has Straight, Queen high

### HAND 2 ###
Bob's hand: Ace of Hearts, Eight of Diamonds, Ten of Diamonds, Jack of Clubs, King of Clubs
Ludovico's hand: Eight of Clubs, Nine of Spades, Queen of Diamonds, Queen of Spades, King of Diamonds
CPU's hand: Ace of Diamonds, Ace of Clubs, Nine of Diamonds, Ten of Hearts, King of Hearts

Bob has High Card, Ace
Ludovico has One Pair, Queen
CPU has One Pair, Ace

### HAND 3 ###
Ludovico's hand: Ace of Hearts, Ace of Diamonds, Eight of Diamonds, Jack of Diamonds, Queen of Diamonds
CPU's hand: Nine of Diamonds, Ten of Spades, Jack of Spades, King of Diamonds, King of Clubs
Bob's hand: Eight of Hearts, Ten of Diamonds, Jack of Hearts, Jack of Clubs, King of Hearts

Ludovico has One Pair, Ace
CPU has One Pair, King
Bob has One Pair, Jack

### HAND 4 ###
CPU's hand: Ace of Clubs, Eight of Diamonds, Eight of Spades, Jack of Hearts, Queen of Hearts
Bob's hand: Nine of Diamonds, Ten of Hearts, Ten of Spades, Queen of Diamonds, King of Clubs
Ludovico's hand: Ace of Spades, Nine of Hearts, Nine of Clubs, Nine of Spades, King of Diamonds

CPU has One Pair, Eight
Bob has One Pair, Ten
Ludovico has Three Of A Kind, Nine

I don't know about you, but I'd almost like to see the hands in a short form, something like this:

### HAND 4 ###
CPU's hand: AC, 8D, 8S, JH, QH
Bob's hand: 9D, 10H, 10S, QD, KC
Ludovico's hand: AS, 9H, 9C, 9S, KD

CPU has One Pair, Eight
Bob has One Pair, Ten
Ludovico has Three Of A Kind, Nine

Also when I was going through fixing it so it would run again, I spent more time actually looking at the design. I have a couple of comments. First, although you can declare a lot of classes and/or methods as friends, it is generally not good design. If there is a motivating reason, go ahead, but for simple things you can avoid it.

As an example, card had 7 friends:

friend std::ostream& operator<<(std::ostream& lhs, card& obj);
        friend bool suitCompare(card op1, card op2);
        friend bool valueCompare(card op1, card op2);
        friend class deck;
        friend class hand;
        friend class player;
        friend class pokerGame;

almost all of those are friends so they can access the protected data members:

protected:
        suit m_suit;
        value m_value;

But you provided public accessors for those items, they're even inline so in theory you don't even pay the price of a call. (But you could change them later if you needed to and no-one would REALLY care.)

suit getSuit() { return m_suit; }
value getValue() { return m_value; }

I would recommend that you re-visit all of your friend relationships to determine if they are really adding value. (I still have the pokerPoints output operator friendship, he's pretty connected, but I'm still not sure about him.)

You also have at least 2 copies of the string arrays for the card names and the suit names. (One in card and one in game.) Also when people are using the arrays, they are always doing something like strValue[foo.m_value - 1] (with the non-friendship it looks more like strValue[foo.getValue()-1] . So I added a couple of static methods to card to do the lookup (and then I hid the arrays).

static std::string const & valueText(value v);
static std::string const & suitText(suit s);

These methods can be accessed like card::valueText(foo.getValue()) and return a constant reference to the string from the hidden array.

Speaking of constant references, const can be your friend (sometimes he's a little painful at first, but you'll come to love him too). There were several places where you're passing literal copies of objects or passing non-constant references. (If you pass a non-constant reference, the function or method you call could change the object you pass them.) It's normally good form when you're writing a method that does not need to make changes to a parameter passed by reference to make it a const reference.

A side effect of this is that when you're working with const references, you can only call methods of the object that have been marked const. So, for example, I marked card's accessors (to get the suit and the value) as const because they will not change the card. So they could be called from someone that had a const reference to a card.

suit getSuit() const { return m_suit; }
value getValue() const { return m_value; }

The last thing (I remember) doing to card was moving the sort compare functions from being external friends to public static members of the class. (As members of the class, they get 'free' permission to access the protected data members.) Ok, so here's the class header:

#ifndef H_FRENCH_CARD
#define H_FRENCH_CARD

#include <iostream>
#include <string>

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
};

enum suit { HEARTS = 1, DIAMONDS = 2, CLUBS = 3, SPADES = 4 };

class card {
    public:
        card(value v, suit s);
        card(const card &cpy);
        card & operator=(const card &op2);

        static std::string const & valueText(value v);
        static std::string const & suitText(suit s);
        static bool cmpSuit(card const & x, card const & y);
        static bool cmpValue(card const & x, card const & y);
        static bool cmpValueAceHigh(card const & x, card const & y);

        suit getSuit() const { return m_suit; }
        value getValue() const { return m_value; }

    protected:
        suit m_suit;
        value m_value;
};

// Helper operator to output the 'text' version of a card
std::ostream& operator<<(std::ostream& lhs, card const & obj);

#endif // H_CARD

The cmpValueAceHigh() comparison function could be used in hand sorting (oh, say from pokerPoints) to sort the hand by value and to get the ace to the end of the array. It would tend to make the hand outputs a little prettier and might allow you to be more generic in some of the 'if I have an ACE' code in pokerPoints. I was thinking about it, and wrote the comparison, but I haven't really looked into using it.

Here's the source to match the header:

#include "card.h"

const std::string strValues[13] = { "Ace", "Two", "Three",
                                          "Four", "Five", "Six",
                                          "Seven", "Eight", "Nine",
                                          "Ten", "Jack", "Queen", "King" };

const std::string strSuits[4] = { "Hearts", "Diamonds", "Clubs", "Spades" };

std::string const & card::valueText(value v)
{
    return strValues[static_cast<int>(v) - 1];
}

std::string const & card::suitText(suit s)
{
    return strSuits[static_cast<int>(s) - 1];
}

bool card::cmpSuit(card const & x, card const & y)
{
    return x.m_suit < y.m_suit;
}

bool card::cmpValue(card const & x, card const & y)
{
    return x.m_value < y.m_value;
}

bool card::cmpValueAceHigh(card const & x, card const & y)
{
    return x.m_value != ACE && x.m_value < y.m_value;
}

card::card(value v, suit s) {
    m_value = v;
    m_suit = s;
}

card::card(const card & cpy) {
    m_value = cpy.m_value;
    m_suit = cpy.m_suit;
}

card & card::operator=(const card &op2) {
    m_value = op2.m_value;
    m_suit = op2.m_suit;
    return *this;
}

std::ostream& operator<<(std::ostream& lhs, card const & obj) {
	lhs << card::valueText(obj.getValue()) << " of " << card::suitText(obj.getSuit());
    return lhs;
}

note that to implement the above changes, there were ripples through almost all of the other project files. Fairly straightforward, but they were there.

commented: Wow, what a post! Thank you! Now that Christmas is gone I think I'm going to partially rewrite this project following some of your advices before going on. Thanks again for your time :-) +2
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.