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:
enums for theRankandSuitinstead of arrays- a
namespacefor theenums andCardclass to_string()s for representing the Ranks and Suits asstd::strings inoperator<<- no longer having
operator<<as afriend - two
std::vectors for theDeckclass (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
enums in case the user doesn't know which Suits are in which order (when filling the deck with cards)? - Although
enums 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; }