I never programmed in my life, and I am currently self teaching myself some C++ by reading books, online free classes and googling. After beginning to read about OOP and classes, and after seeing that a Blackjack game would be something simple to implement using the console, I created the following program to play Blackjack on the console (No graphics, just text narrating what's going on).
Features:
- Aces can be worth 1 or 11, depending on what's better for the score.
- Dealer forced to hit if it has a soft 17 (score of 17 with at least one Ace).
Not implemented:
- Betting system - The player ether lose, tie or win.
- Split system - The player can't split his hand if dealt a pair.
card.h
#ifndef CARD_H
#define CARD_H
#include <iostream>
class Card
{
public:
enum CardSuit
{
CS_S,
CS_D,
CS_C,
CS_H,
CS_MAX
};
enum CardRank
{
CR_2,
CR_3,
CR_4,
CR_5,
CR_6,
CR_7,
CR_8,
CR_9,
CR_T,
CR_J,
CR_Q,
CR_K,
CR_A,
CR_MAX
};
private:
CardSuit m_suit;
CardRank m_rank;
public:
Card(CardSuit suit = CS_S, CardRank rank = CR_A)
: m_suit {suit},
m_rank {rank}
{ }
void printCard() const;
int getCardValue() const;
};
#endif
card.cpp
#include "card.h"
void Card::printCard() const
{
switch (m_rank)
{
case CR_2: std::cout << '2'; break;
case CR_3: std::cout << '3'; break;
case CR_4: std::cout << '4'; break;
case CR_5: std::cout << '5'; break;
case CR_6: std::cout << '6'; break;
case CR_7: std::cout << '7'; break;
case CR_8: std::cout << '8'; break;
case CR_9: std::cout << '9'; break;
case CR_T: std::cout << 'T'; break;
case CR_J: std::cout << 'J'; break;
case CR_Q: std::cout << 'Q'; break;
case CR_K: std::cout << 'K'; break;
case CR_A: std::cout << 'A'; break;
}
switch (m_suit)
{
case CS_S: std::cout << 'S'; break;
case CS_D: std::cout << 'D'; break;
case CS_C: std::cout << 'C'; break;
case CS_H: std::cout << 'H'; break;
}
}
int Card::getCardValue() const
{
switch (m_rank)
{
case CR_2: return 2;
case CR_3: return 3;
case CR_4: return 4;
case CR_5: return 5;
case CR_6: return 6;
case CR_7: return 7;
case CR_8: return 8;
case CR_9: return 9;
case CR_T: return 10;
case CR_J: return 10;
case CR_Q: return 10;
case CR_K: return 10;
case CR_A: return 11;
}
return 0;
}
deck.h
#ifndef DECK_H
#define DECK_H
#include "card.h"
#include <array>
#include <vector>
#include <iostream>
class Deck
{
private:
std::array<Card, 52> m_card;
int m_cardIndex;
void swapCard(Card &a, Card &b);
inline Card* dealCard();
public:
std::vector<Card*> m_playerHand;
std::vector<Card*> m_dealerHand;
Deck() : m_cardIndex {0}, m_playerHand {}, m_dealerHand {}
{
int index {0};
for (int iii {0}; iii < Card::CS_MAX; ++iii)
{
for (int jjj {0}; jjj < Card::CR_MAX; ++jjj)
{
m_card[index] = Card(static_cast<Card::CardSuit>(iii), static_cast<Card::CardRank>(jjj));
++index;
}
}
}
void printDeck() const;
void shuffleDeck(int xTimes);
void dealPlayer();
void dealDealer();
};
inline Card* Deck::dealCard()
{
return &m_card[m_cardIndex++];
}
#endif
deck.cpp
#include "deck.h"
#include <random>
#include <chrono>
namespace Rng
{
const auto seed {std::chrono::high_resolution_clock::now().time_since_epoch().count()};
std::mt19937 mt {static_cast<unsigned long int>(seed)};
int rng(int min, int max)
{
std::uniform_int_distribution<> rng {min, max};
return rng(mt);
}
}
void Deck::swapCard(Card &a, Card &b)
{
Card temp {a};
a = b;
b = temp;
}
void Deck::printDeck() const
{
for (int iii {0}; iii < 52; ++iii)
{
m_card[iii].printCard();
if (((iii + 1) % 13 == 0) && iii != 0)
std::cout << '\n';
else
std::cout << ' ';
}
}
void Deck::shuffleDeck(int xTimes = 1)
{
for (int iii {0}; iii < xTimes; ++iii)
{
for (int jjj {0}; jjj < 52; ++jjj)
{
swapCard(m_card[jjj], m_card[Rng::rng(0, 51)]);
}
}
m_cardIndex = 0;
m_playerHand.clear();
m_dealerHand.clear();
}
void Deck::dealPlayer()
{
int index {static_cast<int>(m_playerHand.size())};
m_playerHand.resize(index + 1);
m_playerHand[index] = dealCard();
}
void Deck::dealDealer()
{
int index {static_cast<int>(m_dealerHand.size())};
m_dealerHand.resize(index + 1);
m_dealerHand[index] = dealCard();
}
main.cpp
#include "card.h"
#include "deck.h"
#include <iostream>
#include <vector>
#include <string>
int getPoints(std::vector<Card*> &hand)
{
int score {0};
int acesCount {0};
for (auto &card : hand)
{
score += card->getCardValue();
if (card->getCardValue() == 11)
++acesCount;
}
if (score > 21 && acesCount != 0)
{
do
{
score -= 10;
--acesCount;
} while (score > 21 && acesCount > 0);
}
return score;
}
void playGame(Deck &gameDeck)
{
gameDeck.shuffleDeck(20);
gameDeck.dealPlayer();
gameDeck.dealDealer();
gameDeck.dealPlayer();
gameDeck.dealDealer();
std::cout << "You were dealt |";
gameDeck.m_playerHand[0]->printCard();
std::cout << "| |";
gameDeck.m_playerHand[1]->printCard();
std::cout << "|\nDealer was dealt |";
gameDeck.m_dealerHand[0]->printCard();
std::cout << "| and a card facing down\nThe dealer peaks at the hole card.\n";
int playerScore {getPoints(gameDeck.m_playerHand)};
int dealerScore {getPoints(gameDeck.m_dealerHand)};
if (playerScore == 21 && dealerScore != 21)
{
std::cout << "You have a Blackjack!\n"
"You win the game.";
return;
}
else if (dealerScore == 21 && playerScore != 21)
{
std::cout << "The dealer flips the hole card to reveal "
"a Blackjack with cards |";
gameDeck.m_dealerHand[0]->printCard();
std::cout << "| and |";
gameDeck.m_dealerHand[1]->printCard();
std::cout << "|\nYou lose the game.\n";
return;
}
else if (playerScore == 21 && dealerScore == 21)
{
std::cout << "You have a Blackjack.\n"
"The dealer flips the hole card to reveal"
"a Blackjack with cards |";
gameDeck.m_dealerHand[0]->printCard();
std::cout << "| and |";
gameDeck.m_dealerHand[1]->printCard();
std::cout << "|\nThe game is a tie\n.";
return;
}
// Player Hit loop:
bool exitPlayerLoop {false};
while (!exitPlayerLoop)
{
std::cout << "Choose your action: [H]it or [S]tand\n";
std::string action {};
std::cin >> action;
switch (action.front())
{
case 'h':
case 'H':
{
gameDeck.dealPlayer();
std::cout << "You were dealt a |";
gameDeck.m_playerHand[gameDeck.m_playerHand.size() - 1]->printCard();
std::cout << "|\nYour hand is";
for (auto &card : gameDeck.m_playerHand)
{
std::cout << " |";
card->printCard();
std::cout << '|';
}
std::cout << '\n';
playerScore = getPoints(gameDeck.m_playerHand);
if (playerScore > 21)
{
std::cout << "You busted. You lose the game.\n\n";
return;
}
}
break;
case 's':
case 'S':
{
std::cout << "You stood. Your hand is";
for (auto &card : gameDeck.m_playerHand)
{
std::cout << " |";
card->printCard();
std::cout << '|';
}
exitPlayerLoop = true;
}
break;
default:
std::cout << "Invalid input\n";
}
}
std::cout << "\nThe dealer flips the hole card. It reveals a |";
gameDeck.m_dealerHand[1]->printCard();
std::cout << "|\n";
// Dealer hit loop:
bool exitDealerLoop {false};
while (!exitDealerLoop)
{
int dealerAcesCount {0};
for (auto &card : gameDeck.m_dealerHand)
{
if (card->getCardValue() == 11)
++dealerAcesCount;
}
dealerScore = getPoints(gameDeck.m_dealerHand);
if (dealerScore < 17 || (dealerScore == 17 && dealerAcesCount > 0))
{
gameDeck.dealDealer();
std::cout << "Dealer hits and was dealt a |";
gameDeck.m_dealerHand[gameDeck.m_dealerHand.size() - 1]->printCard();
std::cout << "|\n";
continue;
}
std::cout << "Dealer hand:";
for (auto &card : gameDeck.m_dealerHand)
{
std::cout << " |";
card->printCard();
std::cout << '|';
}
std::cout << '\n';
exitDealerLoop = true;
}
playerScore = getPoints(gameDeck.m_playerHand);
dealerScore = getPoints(gameDeck.m_dealerHand);
if (dealerScore > 21)
{
std::cout << "The dealer busted. You win the game!\n\n";
}
else if (playerScore > dealerScore)
{
std::cout << "You win the game!\n\n";
}
else if (playerScore < dealerScore)
{
std::cout << "You lose the game.\n\n";
}
else if (playerScore == dealerScore)
{
std::cout << "The game is a draw.\n\n";
}
return;
}
int main()
{
std::cout << " Welcome to Blackjack!\n"
"* * RULES: Dealer must hit soft 17 * *\n\n";
Deck gameDeck;
bool exitLoop {false};
while (!exitLoop)
{
playGame(gameDeck);
bool validChoice = false;
while (!validChoice)
{
std::cout << "Play again? <Y/N>\n";
std::string choice {};
std::cin >> choice;
switch (choice.front())
{
case 'y':
case 'Y':
validChoice = true;
break;
case 'n':
case 'N':
validChoice = true;
exitLoop = true;
break;
default:
std::cout << "Invalid choice.\n";
}
}
}
std::cout << "\nThank you for playing!\n";
return 0;
}
I tried to put in functions the methods that were used more than once only, hence the long void playGame(Deck &gameDeck)
function.
Considering that I am just beginning to learn the language, I want some improvement advice:
What are my bad habits? What are my rookie mistakes?
Should I separate the huge void playGame(Deck &gameDeck)
function into smaller ones?
Am I doing the multi file division the right way? I aimed for a file and header per class.
Anything you guys would change to this particular program?
Any advice to me in general?
PS: Can I move those enums out of the header file? I tried but with no success. They look ugly in a header file...
EDIT: I lied about never programming in my life. I did some RPGs with Rpg Maker 2000 during my youth. It had a basic scripting language.