0

I've tried to find the answer on here, but nothing I am finding is working. I have here a blackjack game. My next step is to start adding a money balance and wager into the game, but before I do that, I am working on my "play again" option. As it stands, when the player plays again, their hand is the same as it was the last round. I believe I need to call a destructor to delete the current hand and deck, and start fresh. Unfortunately I can't figure out how to clear the hand and start with a fresh deck. I tried to call this destructor in my function called play. Here, if the player says they want to play again, I would delete the hand and deck, and they would get reconstructed in main. But I have tried both "deck.Deck::~Deck()" and "deck.~Deck()" for example, and neither is working. I will upload all my code. If anyone has any ideas, please help me out. I know that the destructor is something that should just be called by the complier when it is out of scope, but how toi I start with a fresh hand and deck while the game is still in motion and main is still running? Thank you for your help.

Here is my header file:

//blackjack.h A class to represent a deck of cards
#include <iostream>
#include <string>


class Card { 
  friend class Deck;
  private:
    int card_index; //card number 0 to 51
    Card(int index) { card_index = index; } //made this private so user can't say card at index 100..can use it because of friend class
  public:
    Card() { card_index = 52; }
    char suit() const;
    char value() const;
    std::string str() const;
    int getValue(std::string c);
};

class Deck {
  private:
    Card cards[52];
    int pos;
  public:
    Deck();
    ~Deck();
    Card deal() { return cards[pos++]; };
    void shuffle();
    int size() const { return 52 - pos; };
};

class Hand {
  friend class Deck;
  friend class Card;
  private:
    int handSize;
    int ctotal;
    Card myCards[52];

  public:   
    Hand() { handSize = 1; };
    ~Hand();
    Hand(int n) { handSize = n; };
    void dealFrom(Deck& d);
    void reveal();   
    int total();
    void hit(Deck& d);
};

std::ostream& operator<< (std::ostream& out, const Card& c);

Here is my implementation file:

//blackjack.cpp - Implementations of the Deck and Card classes
#include "blackjack.h"
#include <cstdlib>



char Card::suit() const {
  static char suits[] = { 'H', 'S', 'D', 'C' };
  //return card_index < 52 ? suits[card_index % 4] : 'X';
  return suits[card_index % 4];
}

char Card::value() const {
  static char values[] = 
    { '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A' };
    //return card_index < 52 ? values[card_index / 4] : 'X';
    return values[card_index / 4];
}

std::string Card::str() const {
  std::string s;
  s += value();
  s += suit();
  return s;
}

Deck::Deck() {
  for (int i = 0; i < 52; i ++) {
    cards[i] = Card(i);
  }
  pos = 0;
}

void Deck::shuffle() {
  for (int i = 0; i < 52; i++) {
    int j = rand() % 52;
    Card tmp = cards[i];
    cards[i] = cards[j];
    cards[j] = tmp;
  }
  pos = 0;
}

std::ostream& operator<< (std::ostream& out, const Card& c) {
  out << c.str();
  return out;
}

int Card::getValue(std::string c) {
  char v = c[0];
  int val = v - '0';
  if (val > 9) {
    switch(v){
      case 'T':
      case 'J':
      case 'Q':
      case 'K':
        val = 10;
        break;
      case 'A':
         val = 11;
    }
  }
  return val;

}
void Hand::dealFrom(Deck& d) {
  for(int i = 0; i < handSize; i++)
      myCards[i] = d.deal();
}

void Hand::reveal(){ 
  for (int i = 0; i < handSize; i++) 
    std::cout << myCards[i] << " " << std::endl;
}

void Hand::hit(Deck& d) {
  int index = handSize;
  handSize++;
  myCards[index] = d.deal();

}

int Hand::total() {

  ctotal = 0; //reset card total

  for (int i = 0; i < handSize; i ++) {
    ctotal += myCards[i].getValue(myCards[i].str());
  }

  for (int i = 0; i < handSize; i ++) { //make ace 1 if over 21
    if ( (myCards[i].getValue(myCards[i].str()) == 11) && ctotal > 21 )
      ctotal = ctotal - 10;
  }

  return ctotal;

}

And here is my main program:

#include "blackjack.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

void play(bool& quitGame, bool& quit, Deck& deck, Hand& d,Hand & p) { //function to play again?
  char ans;
  cout << "\nPlay Again? y or n" << endl;
  cin >> ans;
  if (ans == 'y' || ans == 'Y') {
    quitGame = false;
    quit = false;
    deck.Deck::~Deck(); //trying to delete the deck and hand objects before next game
    d.Hand::~Hand();
    p.Hand::~Hand();
    //deck.~Deck();
    //d.~Hand();
    //p.~Hand();

  } else if (ans == 'n' || ans == 'N')
    quitGame = true;
  else {
    cout << "Incorrect response." << endl;
    play(quitGame, quit, deck, d, p);
  }

}

void reveal(Hand& d, Hand& p) { //function to reveal the hand
  cout << "Your hand is: " << endl;
      p.reveal();
      cout << "Your total is: " << endl;
      cout << p.total() << endl;
      cout << endl;
      cout << "Dealer's hand is: " << endl;
      d.reveal();
      cout << "Dealer's total is: " << endl;
      cout << d.total() << endl;
}
void autoComplete(Hand& d, Hand& p, bool& quit, bool& quitGame, Deck& deck) { //function to check for blackjack and over 21
  if (p.total() == 21 && d.total() == 21) {
    cout << "You and dealer both hit blackjack. You tied." << endl;
    quit = true;
    play(quitGame, quit, deck, d, p);
  } else if(p.total() == 21) {
    cout << "Congratulations, you hit blackjack. You win!" << endl;
    quit = true;
    play(quitGame, quit, deck, d, p);
  } else if(d.total() == 21) {
    cout << "Sorry. Dealer hit blackjack. You lose." << endl;
    quit = true;
    play(quitGame, quit, deck, d, p);    
  } else if (p.total() > 21 && d.total() > 21) {
    cout << "You and dealer both passed 21. Game is a tie." << endl;
    quit = true;
    play(quitGame, quit, deck, d, p);
  } else if (p.total() > 21 && d.total() < 21) {
    cout << "You passed 21. You lose.";
    quit = true;
    play(quitGame, quit, deck, d, p);
  } else if (d.total() > 21 && p.total() < 21) {
    cout << "Dealer passed 21. You win.";
    quit = true;
    play(quitGame, quit, deck, d, p);
  }
}

int main() {
  srand(time(0));

  char response; // variable to hit or stand
  bool quit = false; //variable to end the current round
  bool quitGame = false; //variable to play game again

  while (quitGame == false) { //while the player wants to continue playing

    Deck deck; //create deck
    Hand p(2); //player's hand
    Hand d(2); //dealer's hand
    deck.shuffle(); //shuffle deck
    p.dealFrom(deck); //deal from deck
    d.dealFrom(deck);

    while (quit == false) { //while the round isn't over
      reveal(d, p); //reveal the cards
      autoComplete(d, p, quit, quitGame, deck); //check for blackjack and over 21


      if(p.total() < 21 && quit == false) { //if games not over and player is under 21
        cout << "Press 'h' to hit or 's' to stand." << endl;
        cin >> response; 
      }

      if (response == 'h') {
        cout << " " << endl;
        p.hit(deck);
        if (d.total() < 17) //if the dealer hasn't hit 17, dealer hits deck
          d.hit(deck);
      }
      if (response == 's') {
        cout << " " << endl;
        while (d.total() < 17){ //if the dealer hasn't hit 17, keep hitting deck
          d.hit(deck);
        }
        if (d.total() < 21 && p.total() < 21) {
          if (d.total() > p.total() && quit == false) { //if dealers total is higher than players total
            reveal(d, p);
            cout << "\nDealer wins!" << endl;
            quit = true;
            play(quitGame, quit, deck, d, p);
          } else if (p.total() > d.total() && quit == false) { //if players total is higher than dealers total
            reveal(d, p);
            cout << "\nYou win!" << endl;
            quit = true;
            play(quitGame, quit, deck, d, p);
          } else if (p.total() == d.total() && quit == false) { //if dealers total equals players total
            reveal(d, p);
            cout << "\nYou tied." << endl;
            quit = true;
            play(quitGame, quit, deck, d, p);
          }
        }
      } 
    }
  }    

  return 0;

}
  • 8
    You do not directly call the destructor. Are you trying to "reset" those objects? If so, you could copy-assign new objects to your variables `deck = Deck();`. This would have the effect of destroying the previous `Deck` object, while creating a new one and assigning it back to the `deck` variable. – Cory Kramer Dec 17 '19 at 16:08
  • 4
    You typically do not call destructors manually, unless you are also using placement new (which is something you very rarely need to do). – François Andrieux Dec 17 '19 at 16:08
  • [This question & answer](https://stackoverflow.com/questions/14187006/is-calling-destructor-manually-always-a-sign-of-bad-design) might provide some helpful advice. Needing to explicitly call a destructor is a sign that you've worked yourself into a corner from bad design, or that you have a *very specific* reason for doing it. – Romen Dec 17 '19 at 16:08
  • 1
    A destructor is used to clean up resources owned by a class, not to reset them. Your classes don't have any data needs to be cleaned up, so you don't actually need destructors. – Lukas-T Dec 17 '19 at 16:10
  • 2
    Odd that "play" doesn't actually play (that happens in `main()`, which honestly is the *real* problem leading down this rabbit hole). The monetary data should be provided by-reference from `main()` into `play`, the actual deck, hand management, etc, should all be driven from `play`. I.e. `main` should just control the program overall. Playing any single deal, including deck shuffling, dealing, wagering (with money provided by reference from `main()`), etc, should happen in `play()` and its support functions. – WhozCraig Dec 17 '19 at 16:10
  • I don't see a definition of your destructor anywhere - only a declaration. – sepp2k Dec 17 '19 at 16:22
  • Important to note, too: *If* you call a destructor explicitly, then you destroy the object and it ceases to exist. If you call any other function *afterwards* (on the destroyed object!), you end up in the land of undefined behaviour. At least, though, the destructor will be called when the object runs out of scope normally or when you `delete` it, if dynamically allocated (if you don't: memory leak!). To avoid UB, you'll need to re-construct a new object exactly at where the previous one died, coming into play again the already mentioned placement new. – Aconcagua Dec 17 '19 at 16:26
  • Thank you all so much! Reassigning the constructor worked. – Nicole Romeo Dec 18 '19 at 02:49

3 Answers3

6

The syntax:

Class obj;
obj.~Class();

will call the destructor on that object. However, you should NOT do this. As an example:

#include <cstdio>

class Foo {
public:
    Foo() {}
    ~Foo() {
        printf("Destroya\n");
        if (foo_m) delete foo_m;
    }
private:
    int* foo_m = new int(42);
};

int main() {
    Foo foo;
    foo.~Foo();
}

Link to code

As you can see from the output, the destructor gets called twice. The first time when we manually "destroy" an object, and again when it actually goes off the stack.

Almost never call a destructor manually. The object doesn't go away, you'll just break its state and get undefined behavior. What you really want is probably something like a clear() or reset() function to re-initialize the state. Or as suggested in the comments, you can move assign a default object into your already existing one to reset the state without writing extra functions.

sweenish
  • 4,793
  • 3
  • 12
  • 23
  • Nah, it's not a [war ship](https://en.wikipedia.org/wiki/Destroyer), it's a 'destructor'. Still interesting, usually people do the other way round, deducing from the latter the verb 'to destruct' ;) – Aconcagua Dec 17 '19 at 16:45
  • Yeah, caught and changed. Too many comics. Leaving the print statement as-is, though. That was intentional. – sweenish Dec 17 '19 at 16:47
  • 2
    "Never" is a tidbit wrong insofar as with placement new there exists an exception where you may, and actually _need_ to call the destructor. Or in some other corner cases (for example, the standard library may do that when you remove an element from a container, as it wants to keep the storage independently but must assert that objecs are destroyed). Still +1 though, good answer overall. – Damon Dec 17 '19 at 16:49
  • 2
    Upticked, (but never say never; there are [instances](https://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new) where manual destructors are not only allowed, they're *required*). – WhozCraig Dec 17 '19 at 16:50
  • 1
    *'The object doesn't go away'* – well, actually (in standard's wording), it *does* (or in other words, the object isn't alive any more), solely it's storage remains. – Aconcagua Dec 17 '19 at 16:57
  • Also good to know. I never looked up destructors in the standard, I made that statement based on the observation that the object can still technically be called after a manual destructor call. And that the destructor gets called again when it's supposed to be. – sweenish Dec 17 '19 at 17:19
2

The destructor isn't generally something you call manually. For memory on the stack, the destructor is called automatically when an object goes out of scope. For memory on the heap, the destructor is called when you use delete. In this case you don't actually want to delete the hand, you just want to reset it. deck = Deck() will 'reset' it and give you a default constructed Deck object.

Brandon Lyons
  • 473
  • 3
  • 10
0

how [do] I start with a fresh hand and deck while the game is still in motion and main is still running

Fundamentally, you need to call play in only one place: outside the inner loop ("while the round isn't over") of main. Besides all that repeated code, your issue is that when the player wants to play again, you set both quit and quitGame to false, so that inner loop never terminates and you deck never gets reshuffled.

Other issues, left as an exercise for the reader: response can be read before it is assigned a value. Your shuffle routine is not very good, because many of the cards won't be moved.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56