Section 4 - Improving Deck with vector

7B.4.1 Moving Cards from Arrays to vectors

Now that we know about vectors, let's take a critical look at our Deck class more closely. Its primary member is the array of Cards:

// class Deck prototype ----------------------------------------
class Deck
{
  // six full decks is enough for about any game
  static const int MAX_CARDS_PER_DECK = 6 * 52;
  static Card masterPack[52];  // one 52-card master for initializing decks

private:
  Card cards[MAX_CARDS_PER_DECK];
  int topCard;
  int numPacks;

You can already see an annoyance. We have to decide how large an array to allocate. If you think that the fix for this is to use a dynamic array, remember how much more trouble a dynamic array can be and how it is frought with memory leak dangers. Also, there is this "extra" member, topCard, which we seem to need but wish we didn't. Let's look at some of the implementation that resulted from the choice of array.

When we load up the deck in init() one of the double nested loops is:

// then transfer these values to our deck
for (pack = 0; pack < numPacks; pack++)
  for (k = 0; k < 52; k++)
    cards[pack*52 + k] = masterPack[k]

The index expression in the last line is ugly. We're trying to turn two nested index values into a single index that tells us where the highest unused location is. This is programming 101, and you can do this in your sleep, but that doesn't make the sleep any more pleasant. A vector doesn't require such computation; we just use push_back() and we're done. Where else do cards[] and topCard come up?

Here is a fragment from dealCard():

if (topCard == 0)
  return errorReturn;
else
  return cards[--topCard];

It's not horrible, but we have to remember to decrement topCard before we return. Not only that, but we can't use topCard-- (as it would give the wrong answer, obviously). Even though we know how and why, it would be nice to use a vector instead of an array, since push_back() and pop_back() present no such difficulties.

There are several places where the array nature of cards[] creates micro-challenges which increase the chance for error. So let's switch to vectors.

7B.4.2 A Vector of Cards

Inside Deck, we use a vector of cards, not an array, and when we want to deal out a card we simply call the vector's back() and pop_back() to remove it from the deck.  The card will be gone and there is no need to do any bookkeeping on topCard. In fact, we no longer need topCard. That's already a plus.

Here is the start of the new prototype for our improved Deck:

// class Deck prototype -----------------------
class Deck
{
private:
  vector<Card> cards;
  int numPacks;

Already, it's cleaner. No array dimensioning, no MAX_CARDS_PER_DECK and no topCard. cards is now an (empty) vector, and it will grow and shrink though calls to push_back() and pop_back().

Let's see how this simplifies some of the implementation. The init() method's double nested loop now looks like this:

cards.clear();
// then transfer these values to our deck
for (pack = 0; pack < numPacks; pack++)
  for (k = 0; k < 52; k++)
    cards.push_back( masterPack[k] );

Nicely done, right? What about dealCard()?

Card Deck::dealCard()
{
  // always deal the top card (last in)
  Card errorReturn(0, Card::spades); // force errorFlag in return, in rare cases
  Card retCard;

  if (cards.size() == 0)
    return errorReturn;

  // else pop card from back
  retCard = cards.back();
  cards.pop_back();

  return retCard;
}

Here is a summary of what we did, and what we still would need to do to completely transform Deck to use a vector instead of an array:

7B.4.3 A Complete Listing

Here is a full listing of the transformed Deck class, along with Card and a test main()

// ======  main()  Foothill.cpp =================

// CS 2B - LOCEFF
// Client driver for Card, Hand, Deck classes ============

#include "CardSupportVec.h"

#define MAX_HANDS 10
// main client --------------------------------------------------------
int main()
{
  int k, numHands;
  Deck deck(1);
  Hand hands[MAX_HANDS];

  // get the input from the user --------------------------
  do
  {
    cout << "How many hands? (1 - " << MAX_HANDS << ", please): ";
    cin >> numHands;
  }
  while (numHands < 1 || numHands > MAX_HANDS);

  // deal deck, unshuffled --------------------------------
  while (deck.getNumCards() > 0)
  {
    for (k = 0; k < numHands; k++)
    {
      if (deck.getNumCards() == 0)
      break;
      hands[k].takeCard( deck.dealCard() );
    }
  }

  cout << "Here are our hands, from unshuffled deck:" << endl;
  for (k = 0; k < numHands; k++)
  {
    cout << hands[k].toString() << endl << endl;
  }
  cout << endl;

  // restock, deal deck, shuffled --------------------------
  deck.init(1);
  deck.shuffle();

  // clear hands
  for (k = 0; k < numHands; k++)
    hands[k].resetHand();

  while (deck.getNumCards() > 0)
  {
    for (k = 0; k < numHands; k++)
    {
      if (deck.getNumCards() == 0)
      break;
      hands[k].takeCard( deck.dealCard() );
    }
  }

  cout << "Here are our hands, from SHUFFLED deck:" << endl;
  for (k = 0; k < numHands; k++)
  {
    cout << hands[k].toString() << endl << endl;
  }

  return 0;
}

// ======  CardSupportVec.h  ===============

// CS 2B - LOCEFF
// CardSupportVec.h Header File

#pragma once

#include <iostream>
#include <ctime>
#include <string>
#include <cctype>
#include <vector>

using namespace std;

// class Card prototype ----------------------------------------
class Card
{
public:
  enum Suit { clubs, diamonds, hearts, spades };

private:
  char value;
  Suit suit;
  bool errorFlag;

public:
  Card(char value = 'A', Suit suit = spades);
  string toString();
  bool set(char value = 'A', Suit suit = spades);
  char getVal() { return value; }
  Suit getSuit() { return suit; }
  bool getErrorFlag() { return errorFlag; }
  bool equals(Card card);

  // helpers
private:
  bool isValid(char value, Suit suit);
};

// class Hand prototype ----------------------------------------
class Hand
{
public:
  // need this in-line for array declaration
  static const int MAX_CARDS_PER_HAND = 50;   // that should be enough for any game

private:
  Card myCards[MAX_CARDS_PER_HAND];
  int numCards;

public:
  Hand() { resetHand(); }

  // mutators
  void resetHand() { numCards = 0; }
  bool takeCard(Card card);
  Card playCard();

  // accessors
  string toString();
  int getNumCards() { return numCards; }
  Card inspectCard(int k);
};

// class Deck prototype ----------------------------------------
class Deck
{
  static Card masterPack[52];  // one 52-card master for initializing decks

private:
  vector<Card> cards;
  int numPacks;

public:
  Deck(int numPacks = 1);
  void init(int numPacks = 1);
  int getNumCards() { return cards.size(); }
  void shuffle();
  Card dealCard();
  Card inspectCard(int k);
  string toString();

private:
  static void allocateMasterPack();
  bool floatLargestToTop(int top);
  void swap(Card &a, Card &b);
};

// ======  CardSupportVec.cpp  ===============

// CS 2B - LOCEFF
// CardSupportVec.cpp source file

#include "CardSupportVec.h"

// beginning of Card method definitions ----------------------------------------
// constructor
Card::Card(char value, Suit suit)
{
  // because mutator sets errorFlag, we don't have to test
  set(value, suit);
}

// stringizer
string Card::toString()
{
  string retVal;
  char strVal[2];

  if (errorFlag)
    return "** illegal **";

  // convert char to a CString, then string
  strVal[0] = value;
  strVal[1] = '\0';
  retVal = string(strVal);

  if (suit == spades)
    retVal += " of Spades";
  else if (suit == hearts)
    retVal += " of Hearts";
  else if (suit == diamonds)
    retVal += " of Diamonds";
  else if (suit == clubs)
    retVal += " of Clubs";

  return retVal;
}

// mutator
bool Card::set(char value, Suit suit)
{
  char upVal;

  // convert to uppercase to simplify (may need to #include <cctype>)
  upVal = toupper((int)value);

  if ( !isValid(upVal, suit))
  {
    errorFlag = true;
    return false;
  }

  // else implied
  errorFlag = false;
  this->value = upVal;
  this->suit = suit;
  return true;
}

// helper
bool Card::isValid(char value, Suit suit)
{
  char upVal;

  // convert to uppercase to simplify (need #include <cctype>)
  upVal = toupper((int)value);

  // check for validity
  if (
  upVal == 'A' || upVal == 'K'
  || upVal == 'Q' || upVal == 'J'
  || upVal == 'T'
  || (upVal >= '2' && upVal <= '9')
  )
    return true;
  else
    return false;
}

bool Card::equals(Card card)
{
  if (this->value != card.value)
    return false;
  if (this->suit != card.suit)
    return false;
  if (this->errorFlag != card.errorFlag)
    return false;
  return true;
}

// end of Card method definitions ----------------------------------------

// beginning of Hand method definitions ----------------------------------------
bool Hand::takeCard(Card card)
{
  if (numCards >= MAX_CARDS_PER_HAND)
    return false;

  // don't just assign:  mutator assures active/undeleted
  myCards[numCards++].set( card.getVal(), card.getSuit() );
  return true;
}

Card Hand::playCard()
{
  // always play highest card in array.  client will prepare this position.
  // in rare case that client tries to play from a spent hand, return error

  Card errorReturn(0, Card::spades); // in rare cases

  if (numCards == 0)
    return errorReturn;
  else
    return myCards[--numCards];
}

Card Hand::inspectCard(int k)
{
  // return copy of card at position k.
  // if client tries to access out-of-bounds card, return error

  Card errorReturn(0, Card::spades); // force errorFlag in return, in rare cases

  if (k < 0 || k >= numCards)
    return errorReturn;
  else
    return myCards[k];
}

string Hand::toString()
{
  int k;
  string retVal = "Hand =  ( ";

  for (k = 0; k < numCards; k++)
  {
    retVal += myCards[k].toString();
    if (k < numCards - 1)
    retVal += ", ";
  }
  retVal += " )";
  return retVal;
}
// end of Hand method definitions ------------------------------------

// beginning of Deck method definitions ------------------------------
Card Deck::masterPack[52];  // static definition

Deck::Deck(int numPacks)
{
  // place cards in the deck, in perfect order
  allocateMasterPack();
  init(numPacks);
}

void Deck::allocateMasterPack()
{
  int k, j;
  Card::Suit st;
  char val;

  // we're in a static method; only needed once per program:  good for whole class
  static bool firstTime = true;
  if ( !firstTime )
    return;
  firstTime = false;

  // first load an array of cards with values
  for (k = 0; k < 4; k++)
  {
    // set the suit for this loop pass
    st = (Card::Suit)k;

    // now set all the values for this suit
    masterPack[13*k].set('A', st);
    for (val='2', j = 1; val<='9'; val++, j++)
    masterPack[13*k + j].set(val, st);
    masterPack[13*k+9].set('T', st);
    masterPack[13*k+10].set('J', st);
    masterPack[13*k+11].set('Q', st);
    masterPack[13*k+12].set('K', st);
  }
}

// set deck from 1 to 6 packs, perfecly ordered
void Deck::init(int numPacks)
{
  int k, pack;

  if (numPacks < 1 || numPacks > 6)
    numPacks = 1;

  // when initing, reset the random num generator for shuffling
  srand(time(NULL));

  cards.clear();
  // then transfer these values to our deck
  for (pack = 0; pack < numPacks; pack++)
    for (k = 0; k < 52; k++)
      cards.push_back( masterPack[k] );

  this->numPacks = numPacks;
}

void Deck::shuffle()
{
  Card tempCard;
  int k, randInt, topCard;

  // for loop efficiency
  topCard = cards.size();

  for (k = 0; k < topCard; k++)
  {
    randInt = rand() % topCard;

    // swap cards k and randInt (sometimes k == randInt:  okay)
    tempCard = cards[k];
    cards[k] = cards[randInt];
    cards[randInt] = tempCard;
  }
}

Card Deck::dealCard()
{
  // always deal the top card (last in).
  Card errorReturn(0, Card::spades); // force errorFlag in return, in rare cases
  Card retCard;

  if (cards.size() == 0)
    return errorReturn;

  // else pop card from back
  retCard = cards.back();
  cards.pop_back();

  return retCard;
}

Card Deck::inspectCard(int k)
{
  // return copy of card at position k.
  // if client tries to access out-of-bounds card, return error

  Card errorReturn(0, Card::spades); // force errorFlag in return, in rare cases

  if (k < 0 || k >= cards.size())
    return errorReturn;
  else
    return cards[k];
}
// end of Hand method definitions ------------------------------------