As this new approach differs from my previous revisions, it shouldn't need references to them.
After receiving much guidance from CR chat, I ended up utilizing these things:
enum
s for theRank
andSuit
instead of arrays- a
namespace
for theenum
s andCard
class to_string()
s for representing the Ranks and Suits asstd::string
s inoperator<<
- no longer having
operator<<
as afriend
- two
std::vector
s for theDeck
class (for retaining the original deck when resetting)
I'd like to know if there's still more that can be done, and I have some questions:
- Is using two vectors an effective way of preserving the deck (as opposed to giving the consuming code the responsibility to return the cards to the deck)?
- Should something else be done with the
enum
s in case the user doesn't know which Suits are in which order (when filling the deck with cards)? - Although
enum
s are built-in, isconst&
still good forCard
's constructor? - Is having the user call
empty()
on the deck good enough so as to avoid complications with exception-handling within the class? - Could the two
to_string()
s for Rank and Suit be shortened while still resembling a look-up table?
Any other feedback is appreciated as well. Beyond these aspects, I desire overall maintainability for adapting to different games (such as adding a Joker). As such, I don't want to hurt the base design with features only associated with a few games.
Card.h
#ifndef CARD_H
#define CARD_H
#include <iostream>
#include <string>
namespace card
{
enum Rank {Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
enum Suit {Clubs,Diamonds,Hearts,Spades};
class Card
{
private:
Rank m_rank;
Suit m_suit;
std::string to_string(Rank const&) const;
std::string to_string(Suit const&) const;
public:
Card(Rank const&, Suit const&);
std::string to_string() const;
Rank rank() const {return m_rank;}
Suit suit() const {return m_suit;}
};
}
card::Rank& operator++(card::Rank&);
card::Suit& operator++(card::Suit&);
std::ostream& operator<<(std::ostream&, card::Card const&);
#endif
Card.cpp
#include "Card.h"
#include <stdexcept>
card::Card::Card(Rank const& rank, Suit const& suit)
: m_rank(rank), m_suit(suit) {}
std::string card::Card::to_string(Rank const& rank) const
{
switch (rank)
{
case Ace: return "A";
case Two: return "2";
case Three: return "3";
case Four: return "4";
case Five: return "5";
case Six: return "6";
case Seven: return "7";
case Eight: return "8";
case Nine: return "9";
case Ten: return "T";
case Jack: return "J";
case Queen: return "Q";
case King: return "K";
default: throw std::logic_error("Invalid Rank");
}
}
std::string card::Card::to_string(Suit const& suit) const
{
switch (suit)
{
case Clubs: return "C";
case Diamonds: return "D";
case Hearts: return "H";
case Spades: return "S";
default: throw std::logic_error("Invalid Suit");
}
}
std::string card::Card::to_string() const
{
return ("[" + to_string(m_rank) + to_string(m_suit) + "]");
}
card::Rank& operator++(card::Rank& r)
{
return r = card::Rank(static_cast<int>(r)+1);
}
card::Suit& operator++(card::Suit& s)
{
return s = card::Suit(static_cast<int>(s)+1);
}
std::ostream& operator<<(std::ostream& out, card::Card const& obj)
{
return out << obj.to_string();
}
Deck.h
#ifndef DECK_H
#define DECK_H
#include <vector>
class Deck
{
private:
std::vector<card::Card> m_original; // retains original cards when resetting deck
std::vector<card::Card> m_playable; // cards are drawn from this one
public:
typedef std::vector<card::Card>::size_type SizeType;
void reset();
void shuffle();
void add(card::Card const&);
card::Card draw();
SizeType size() const {return m_playable.size();}
bool empty() const {return m_playable.empty();}
};
#endif
Deck.cpp
#include "Card.h"
#include "Deck.h"
#include <algorithm>
void Deck::reset()
{
// is this already exception-safe by itself?
m_playable = m_original;
}
void Deck::shuffle()
{
// my tests tell me that an empty vector will not cause this to fail
std::random_shuffle(m_playable.begin(), m_playable.end());
}
void Deck::add(card::Card const& card)
{
// new cards always added to both vectors
m_original.push_back(card);
m_playable.push_back(card);
}
card::Card Deck::draw()
{
// still not exception-safe, unless not needed if handled in client
card::Card topCard(m_playable.back());
m_playable.pop_back();
return topCard;
}