This code was first critiqued here (without the Card class): Card Deck class for a Poker game
After learning more about data structures both online and in class, I wanted to revisit my Card
and Deck
classes and see if I can still make them better. Based on my driver tests, they are doing nearly everything I want them to do.
However, I'm not sure if all my operations between these two classes and the interface are proper, even though they do give proper results.
For instance:
Is it still (theoretically) okay to return a blank
Card
if there are no more cards to return from the emptyDeck
?Do I still need to maintain my Rule of Three, even though I don't do any memory allocation in either class?
These are my main concerns, but I'd like a review of all the code in case there's something I've overlooked and/or if some parts can be cleaned.
Card.h
#ifndef CARD_H
#define CARD_H
#include <iostream>
#include <string>
class Card
{
private:
unsigned rankValue;
char rank;
char suit;
std::string card;
public:
Card();
Card(char, char);
Card::Card(const Card&);
Card::~Card();
char getRank() const {return rank;}
char getSuit() const {return suit;}
std::string getCard() const {return card;}
bool operator<(const Card &rhs) const {return (rank < rhs.rank);}
bool operator>(const Card &rhs) const {return (rank > rhs.rank);}
bool operator==(const Card &rhs) const {return (suit == rhs.suit);}
bool operator!=(const Card &rhs) const {return (suit != rhs.suit);}
Card& operator=(const Card &obj);
friend std::ostream& operator<<(std::ostream&, const Card&);
};
#endif
Card.cpp
#include "Card.h"
Card::Card() : rankValue(0), rank('*'), suit('*'), card("**") {}
Card::Card(const Card &obj)
{
rankValue = obj.rankValue;
rank = obj.rank;
suit = obj.suit;
card = obj.card;
}
Card::~Card() {}
Card::Card(char rank, char suit)
{
this->rank = rank;
this->suit = suit;
card += rank;
card += suit;
if (rank == 'A')
rankValue = 1;
else if (rank == '2')
rankValue = 2;
else if (rank == '3')
rankValue = 3;
else if (rank == '4')
rankValue = 4;
else if (rank == '5')
rankValue = 5;
else if (rank == '6')
rankValue = 6;
else if (rank == '7')
rankValue = 7;
else if (rank == '8')
rankValue = 8;
else if (rank == '9')
rankValue = 9;
else if (rank == 'T')
rankValue = 10;
else if (rank == 'J')
rankValue = 11;
else if (rank == 'Q')
rankValue = 12;
else if (rank == 'K')
rankValue = 13;
}
Card &Card::operator=(const Card &obj)
{
// if not self-assignment and lhs is a Blank card
if (this != &obj && this->card == "**")
{
this->card = obj.card;
}
return *this;
}
std::ostream& operator<<(std::ostream &out, const Card &aCard)
{
out << '[' << aCard.card << ']';
return out;
}
Deck.h
#ifndef DECK_H
#define DECK_H
#include <array>
#include "Card.h"
class Deck
{
private:
static const unsigned MAX_SIZE = 52;
int topCardIndex;
std::array<Card, MAX_SIZE> cards;
void build();
public:
Deck();
void shuffle();
Card deal();
unsigned size() const {return topCardIndex+1;}
bool empty() const {return topCardIndex == -1;}
friend std::ostream& operator<<(std::ostream&, const Deck&);
};
#endif
Deck.cpp
#include <algorithm>
#include "Deck.h"
Deck::Deck() : topCardIndex(-1)
{
build();
shuffle();
}
void Deck::build()
{
const unsigned NUMBER_OF_RANKS = 13;
const unsigned NUMBER_OF_SUITS = 4;
const char RANKS[NUMBER_OF_RANKS] = {'A','2','3','4','5','6','7','8','9','T','J','Q','K'};
const char SUITS[NUMBER_OF_SUITS] = {'H','D','C','S'};
for (unsigned rank = 0; rank < NUMBER_OF_RANKS; ++rank)
{
for (unsigned suit = 0; suit < NUMBER_OF_SUITS; ++suit)
{
Card newCard(RANKS[rank], SUITS[suit]);
topCardIndex++;
cards[topCardIndex] = newCard;
}
}
}
void Deck::shuffle()
{
topCardIndex = MAX_SIZE-1;
std::random_shuffle(&cards[0], &cards[topCardIndex]);
}
Card Deck::deal()
{
if (empty())
{
std::cerr << "\nDECK IS EMPTY\n";
Card blankCard;
return blankCard;
}
topCardIndex--;
return cards[topCardIndex+1];
}
std::ostream& operator<<(std::ostream &out, const Deck &aDeck)
{
for (unsigned iter = aDeck.size(); iter--> 0;)
{
out << aDeck.cards[iter] << "\n";
}
return out;
}