Przegląd rozdziału
Tablice o stałym rozmiarze (lub tablicami o stałej długości) wymagają, aby długość tablicy była znana w momencie tworzenia instancji i ta długość nie można później zmienić. Tablice w stylu C i std::array są tablicami o stałym rozmiarze. Rozmiar tablic dynamicznych można zmieniać w czasie wykonywania. std::vector jest tablicą dynamiczną.
Długość std::array musi być wyrażeniem stałym. Najczęściej podaną wartością długości będzie literał całkowity, zmienna constexpr lub moduł wyliczający bez zakresu.
std::array jest agregatem. Oznacza to, że nie ma konstruktorów i zamiast tego jest inicjowany przy użyciu inicjalizacji agregowanej.
Zdefiniuj swój std::array jako constexpr, jeśli to możliwe. Jeśli std::array nie jest constexpr, rozważ użycie std::vector .
Użyj odliczenia argumentów szablonu klasy (CTAD), aby kompilator wywnioskował typ i długość std::array z jej inicjatorów.
std::array jest zaimplementowany jako struktura szablonu, której deklaracja wygląda następująco:
template<typename T, std::size_t N> // N is a non-type template parameter
struct array;
Nietypowy parametr szablonu reprezentujący długość tablicy (N) ma typ std::size_t.
Aby uzyskać długość std::array:
- Możemy zapytać
std::array obiekt o jego długość za pomocą size() funkcji składowej (która zwraca długość jako bez znaku size_type). - W C++17 możemy użyć
std::size() nieelementowej (która for std::array po prostu wywołuje funkcję składową size() , zwracając w ten sposób długość bez znaku size_type). - W C++20 możemy użyć
std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwyklestd::ptrdiff_t).
Wszystkie trzy funkcje zwrócą długość jako wartość constexpr, z wyjątkiem sytuacji, gdy zostaną wywołane w std::array przekazanej przez referencję. Ten defekt został rozwiązany w C++23 przez P2280.
Aby zaindeksować std::array:
- Użyj operatora indeksu dolnego (
operator[]). W tym przypadku nie jest wykonywane żadne sprawdzanie granic, a przekazanie nieprawidłowego indeksu spowoduje niezdefiniowane zachowanie. - Użyj
at() funkcji składowej, która wykonuje indeks dolny ze sprawdzaniem granic w czasie wykonywania. Zalecamy unikać tę funkcję, ponieważ zazwyczaj chcemy sprawdzić granice przed indeksowaniem lub chcemy sprawdzić granice w czasie kompilacji. - Użyj
std::get() szablonu funkcji, który przyjmuje indeks jako argument szablonu niebędący typem i sprawdza granice w czasie kompilacji.
Możesz przekazywać std::array z różnymi typami elementów i długościami do funkcji, używając szablonu funkcji z parametrem szablonu deklaracja template <typename T, std::size_t N>. Lub w C++20 użyj template <typename T, auto N>.
Zwrócenie std::array by wartością spowoduje utworzenie kopii tablicy i wszystkich elementów, ale może to być w porządku, jeśli tablica jest mała, a kopiowanie elementów nie jest drogie. W niektórych kontekstach lepszym wyborem może być użycie parametru out.
Podczas inicjalizacji std::array w przypadku struktury, klasy lub tablicy i bez podawania typu elementu przy każdym inicjatorze, będziesz potrzebować dodatkowej pary nawiasów klamrowych, aby kompilator właściwie zinterpretował to, co należy zainicjować. Jest to artefakt inicjowania agregacji, a inne standardowe typy kontenerów bibliotecznych (które używają konstruktorów list) nie wymagają w takich przypadkach podwójnych nawiasów.
Agregaty w C++ obsługują koncepcję zwaną elizja nawiasów, która określa pewne zasady dotyczące sytuacji, gdy może występować wiele nawiasów klamrowych. ogólnie rzecz biorąc, można pominąć nawiasy klamrowe podczas inicjowania std::array za pomocą wartości skalarnych (pojedynczych) lub podczas inicjowania z typami klasowymi lub tablicami, gdzie typ jest jawnie nazwany przy każdym elemencie.
Nie możesz mieć tablicy odniesień, ale możesz mieć tablicę std::reference_wrapper, która zachowuje się jak modyfikowalne odwołanie do wartości.
Jest kilka rzeczy, o których warto pamiętać std::reference_wrapper:
Operator= ponowne osadzenie a std::reference_wrapper (zmiana obiektu, do którego się odwołuje).std::reference_wrapper<T> będzie niejawnie konwertowana do T&.- Klasa
get() funkcji składowej, której można użyć do uzyskania T&. Jest to przydatne, gdy chcemy zaktualizować wartość obiektu, do którego się odwołujemy.
Klasa std::ref() i std::cref() funkcje zostały udostępnione jako skróty do tworzenia std::reference_wrapper i const std::reference_wrapper owinięte obiektów.
Użyj static_assert jeśli to możliwe, upewnij się, constexpr std::array przy użyciu CTAD ma poprawną liczbę inicjatorów.
Tablice w stylu C zostały odziedziczone z języka C i są wbudowane w rdzeń języka C++. Ponieważ są częścią języka podstawowego, tablice w stylu C mają własną składnię deklaracji specjalnych. W deklaracji tablicy w stylu C używamy nawiasów kwadratowych ([]), aby poinformować kompilator, że zadeklarowany obiekt jest tablicą w stylu C. Wewnątrz nawiasów kwadratowych możemy opcjonalnie podać długość tablicy, która jest wartością całkowitą typu std::size_t, która informuje kompilator, ile elementów znajduje się w tablicy. Długość tablicy w stylu C musi być wyrażeniem stałym.
Tablice w stylu C są agregowane, co oznacza, że można je inicjować przy użyciu inicjalizacji agregowanej. Używając listy inicjatorów do inicjowania wszystkich elementów tablicy w stylu C, lepiej pominąć długość i pozwolić kompilatorowi obliczyć długość tablicy.
Tablice w stylu C można indeksować za pomocą operator[]. Indeks tablicy w stylu C może być liczbą całkowitą ze znakiem lub bez znaku, albo wyliczeniem bez zakresu. Oznacza to, że tablice w stylu C nie podlegają wszystkim problemom z indeksowaniem konwersji znaków, które występują w standardowych klasach kontenerów bibliotek!
Tablice w stylu C mogą być const lub constexpr.
Aby uzyskać długość tablicy w stylu C:
- W C++17 możemy użyć
std::size() funkcji niebędącej składową, która zwraca długość jako unsigned std::size_t. - W C++20 możemy użyć
std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwyklestd::ptrdiff_t).
W większości przypadków, gdy w wyrażeniu używana jest tablica w stylu C, tablica zostanie niejawnie przekonwertowana na wskaźnik do typu elementu, zainicjowany adresem pierwszego elementu (z indeksem 0). Potocznie nazywa się to rozpadem tablicy (lub po prostu w skrócie rozpadu).
Arytmetyka wskaźników to funkcja, która pozwala nam zastosować pewne operatory arytmetyczne na liczbach całkowitych (dodawanie, odejmowanie, zwiększanie lub zmniejszanie) do wskaźnika w celu wygenerowania nowego adresu pamięci. Biorąc pod uwagę pewien wskaźnik ptr, ptr + 1 zwraca adres następnego obiektu w pamięci (w zależności od typu, na który wskazuje).
Użyj indeksu dolnego podczas indeksowania od początku tablicy (element 0), tak aby indeksy tablicy pokrywały się z elementem.
Użyj arytmetyki wskaźników podczas wykonywania względnych pozycjonowanie z danego elementu.
Ciągi w stylu C to po prostu tablice w stylu C, których typ elementu to char lub const char. W związku z tym ciągi w stylu C będą się rozpadać.
Klasa wymiar tablicy to liczba indeksów potrzebnych do wybrania elementu.
Tablica zawierająca tylko jeden wymiar nazywana jest tablicą jednowymiarową lub a tablicą jednowymiarową (czasami w skrócie 1d tablica). Tablica tablic nazywana jest tablicą dwuwymiarową (czasami w skrócie tablicą 2d), ponieważ ma dwa indeksy dolne. Tablice z więcej niż jednym wymiarem nazywane są tablicami wielowymiarowymi. Spłaszczanie tablica to proces zmniejszania wymiarowości tablicy (często do jednego wymiaru).
W C++23 std::mdspan to widok zapewniający wielowymiarowy interfejs tablicy dla ciągłej sekwencji elementów.
Czas quizu
Pytanie nr 1
Co jest nie tak z każdym z tych fragmentów i jak to naprawić?
A)
#include <array>
#include <iostream>
int main()
{
std::array arr { 0, 1, 2, 3 };
for (std::size_t count{ 0 }; count <= std::size(arr); ++count)
{
std::cout << arr[count] << ' ';
}
std::cout << '\n';
return 0;
}
Pokaż rozwiązanie
Pętla for zawiera błąd o jeden i próbuje uzyskać dostęp do elementu tablicy o indeksie 4, który nie istnieje.
Rozwiązanie: warunek w pętli for powinien użyć < zamiast <=.
B)
#include <iostream>
void printArray(int array[])
{
for (int element : array)
{
std::cout << element << ' ';
}
}
int main()
{
int array[] { 9, 7, 5, 3, 1 };
printArray(array);
std::cout << '\n';
return 0;
}
Pokaż rozwiązanie
array zamienia się na wskaźnik, gdy tak się stanie zostaje przekazany do printArray(). Pętle for oparte na zakresach nie mogą działać ze wskaźnikiem do tablicy, ponieważ rozmiar tablicy nie jest znany.
Rozwiązanie: zamiast tego użyj std::array , który nie zanika.
C)
#include <array>
#include <iostream>
int main()
{
std::cout << "Enter the number of test scores: ";
std::size_t length{};
std::cin >> length;
std::array<int, length> scores;
for (std::size_t i { 0 } ; i < length; ++i)
{
std::cout << "Enter score " << i << ": ";
std::cin >> scores[i];
}
return 0;
}
Pokaż rozwiązanie
length nie jest wyrażeniem stałym i nie można go użyć do zdefiniowania długości std::array.
Rozwiązanie: użyj std::vector .
Pytanie nr 2
W tym quizie będziemy uruchom sklep z eliksirami Roscoe, najlepszy sklep z eliksirami w kraju! To będzie większe wyzwanie.
Zaimplementuj program, który wyświetli następujące wyniki:
Welcome to Roscoe's potion emporium!
Enter your name: Alex
Hello, Alex, you have 85 gold.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: a
That is an invalid input. Try again: 3
You purchased a potion of invisibility. You have 35 gold left.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 4
That is an invalid input. Try again: 2
You purchased a potion of speed. You have 23 gold left.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 2
You purchased a potion of speed. You have 11 gold left.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: 4
You can not afford that.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Enter the number of the potion you'd like to buy, or 'q' to quit: q
Your inventory contains:
2x potion of speed
1x potion of invisibility
You escaped with 11 gold remaining.
Thanks for shopping at Roscoe's potion emporium!
Gracz zaczyna z losową ilością złota od 80 do 120.
Brzmi zabawnie? Zróbmy to! Ponieważ trudno będzie wdrożyć to wszystko na raz, będziemy rozwijać to stopniowo.
> Krok #1
Utwórz Potion przestrzeń nazw zawierająca wyliczenie o nazwie Type zawierające typy mikstur. Utwórz dwa std::array: jakiś int tablica do przechowywania kosztów mikstur, oraz a std::string_view tablica do przechowywania nazw mikstur.
Napisz także funkcję o nazwie shop() który wylicza poprzez listę Potions i drukuje ich numery, nazwy i koszt.
Program powinien wypisać co następuje:
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Pokaż wskazówkę
Pokaż rozwiązanie
#include <array>
#include <iostream>
#include <string_view>
namespace Potion
{
enum Type
{
healing,
mana,
speed,
invisibility,
max_potions
};
constexpr std::array types { healing, mana, speed, invisibility }; // An array of our enumerators
// We could put these in a struct, but since we only have two attributes we'll keep them separate for now
// We will explicitly define the element type so we don't have to use the sv suffix
constexpr std::array<std::string_view, max_potions> name { "healing", "mana", "speed", "invisibility" };
constexpr std::array cost { 20, 30, 12, 50 };
static_assert(std::size(types) == max_potions); // ensure 'all' contains the correct number of enumerators
static_assert(std::size(cost) == max_potions);
static_assert(std::size(name) == max_potions);
}
void shop()
{
std::cout << "Here is our selection for today:\n";
for (auto p: Potion::types)
std::cout << p << ") " << Potion::name[p] << " costs " << Potion::cost[p] << '\n';
}
int main()
{
shop();
return 0;
}
> Krok #2
Utwórz Player klasa do przechowywania imienia gracza, ekwipunku mikstur i złota. Dodaj tekst wprowadzający i pożegnalny dotyczący emporium Roscoe. Zdobądź imię gracza i losuj jego złoto.
Wykorzystaj na lekcji plik „Random.h”. 8.15 -- Globalne liczby losowe (Random.h) aby ułatwić randomizację.
Program powinien wypisać co następuje:
Welcome to Roscoe's potion emporium!
Enter your name: Alex
Hello, Alex, you have 84 gold.
Here is our selection for today:
0) healing costs 20
1) mana costs 30
2) speed costs 12
3) invisibility costs 50
Thanks for shopping at Roscoe's potion emporium!
Pokaż rozwiązanie
#include <array>
#include <iostream>
#include <string_view>
#include "Random.h"
namespace Potion
{
enum Type
{
healing,
mana,
speed,
invisibility,
max_potions
};
constexpr std::array types { healing, mana, speed, invisibility }; // An array of our enumerators
// We could put these in a struct, but since we only have two attributes we'll keep them separate for now
// We will explicitly define the element type so we don't have to use the sv suffix
constexpr std::array<std::string_view, max_potions> name { "healing", "mana", "speed", "invisibility" };
constexpr std::array cost { 20, 30, 12, 50 };
static_assert(std::size(types) == max_potions); // ensure 'all' contains the correct number of enumerators
static_assert(std::size(cost) == max_potions);
static_assert(std::size(name) == max_potions);
}
class Player
{
private:
static constexpr int s_minStartingGold { 80 };
static constexpr int s_maxStartingGold { 120 };
std::string m_name {};
int m_gold {};
std::array<int, Potion::max_potions> m_inventory { };
public:
explicit Player(std::string_view name) :
m_name { name },
m_gold { Random::get(s_minStartingGold, s_maxStartingGold) }
{
}
int gold() const { return m_gold; }
int inventory(Potion::Type p) const { return m_inventory[p]; }
};
void shop()
{
std::cout << "Here is our selection for today:\n";
for (auto p: Potion::types)
std::cout << p << ") " << Potion::name[p] << " costs " << Potion::cost[p] << '\n';
}
int main()
{
std::cout << "Welcome to Roscoe's potion emporium!\n";
std::cout << "Enter your name: ";
std::string name{};
std::getline(std::cin >> std::ws, name); // read a full line of text into name
Player player { name };
std::cout << "Hello, " << name << ", you have " << player.gold() << " gold.\n\n";
shop();
std::cout << "\nThanks for shopping at Roscoe's potion emporium!\n";
return 0;
}
> Krok #3
Dodaj możliwość zakupu mikstur i obsługi nieprawidłowych danych wejściowych (każde obce dane wejściowe traktuj jako awarię). Wydrukuj ekwipunek gracza po jego wyjściu. Po tym kroku program powinien być gotowy.
Upewnij się, że testujesz następujące przypadki:
- Użytkownik wprowadza nieprawidłowy numer mikstury (np. „d”)
- Użytkownik wprowadza prawidłowy numer mikstury, ale z dodatkowymi danymi (np.
2d, 25)
Na lekcji omówimy obsługę nieprawidłowych danych wejściowych 9.5 — std::cin i obsługa nieprawidłowych danych wejściowych.
Pokaż wskazówkę
Wskazówka: użytkownik może wprowadzić liczbę lub „q”, dlatego wyodrębnij dane wprowadzone przez użytkownika, aby wpisać
char.
Aby przekonwertować znak liczby ASCII na liczbę typu int (np. '5' Do 5), możesz użyć następujących opcji:
int charNumToInt(char c)
{
return c - '0';
}
Pokaż wskazówkę
Wskazówka: Napisz funkcję obsługującą dane wprowadzane przez użytkownika. Powinien zwrócić Potion::Type wybrany przez użytkownika. Jeśli zamiast tego użytkownik zakończy działanie, funkcja może powrócić Potion::max_potions. Będziesz musiał static_cast wprowadzić dane użytkownika do a Potion::Type.
Pokaż rozwiązanie
#include <array>
#include <iostream>
#include <limits> // for std::numeric_limits
#include <string_view>
#include "Random.h"
namespace Potion
{
enum Type
{
healing,
mana,
speed,
invisibility,
max_potions
};
constexpr std::array types { healing, mana, speed, invisibility }; // An array of our enumerators
// We could put these in a struct, but since we only have two attributes we'll keep them separate for now
// We will explicitly define the element type so we don't have to use the sv suffix
constexpr std::array<std::string_view, max_potions> name { "healing", "mana", "speed", "invisibility" };
constexpr std::array cost { 20, 30, 12, 50 };
static_assert(std::size(types) == max_potions); // ensure 'all' contains the correct number of enumerators
static_assert(std::size(cost) == max_potions);
static_assert(std::size(name) == max_potions);
}
class Player
{
private:
static constexpr int s_minStartingGold { 80 };
static constexpr int s_maxStartingGold { 120 };
std::string m_name {};
int m_gold {};
std::array<int, Potion::max_potions> m_inventory { };
public:
explicit Player(std::string_view name) :
m_name { name },
m_gold { Random::get(s_minStartingGold, s_maxStartingGold) }
{
}
// returns false if can't afford, true if purchased
bool buy(Potion::Type type)
{
if (m_gold < Potion::cost[type])
return false;
m_gold -= Potion::cost[type];
++m_inventory[type];
return true;
}
int gold() const { return m_gold; }
int inventory(Potion::Type p) const { return m_inventory[p]; }
};
void ignoreLine()
{
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
int charNumToInt(char c)
{
return c - '0';
}
Potion::Type whichPotion()
{
std::cout << "Enter the number of the potion you'd like to buy, or 'q' to quit: ";
char input{};
while (true)
{
std::cin >> input;
if (!std::cin)
{
std::cin.clear(); // put us back in 'normal' operation mode
ignoreLine(); // and remove the bad input
continue;
}
// If there is extraneous input, treat as failure case
if (!std::cin.eof() && std::cin.peek() != '\n')
{
std::cout << "I didn't understand what you said. Try again: ";
ignoreLine(); // ignore any extraneous input
continue;
}
if (input == 'q')
return Potion::max_potions;
// Convert the char to a number and see if it's a valid potion selection
int val { charNumToInt(input) };
if (val >= 0 && val < Potion::max_potions)
return static_cast<Potion::Type>(val);
// It wasn't a valid potion selection
std::cout << "I didn't understand what you said. Try again: ";
ignoreLine();
}
}
void shop(Player &player)
{
while (true)
{
std::cout << "Here is our selection for today:\n";
for (auto p: Potion::types)
std::cout << p << ") " << Potion::name[p] << " costs " << Potion::cost[p] << '\n';
Potion::Type which { whichPotion() };
if (which == Potion::max_potions)
return;
bool success { player.buy(which) };
if (!success)
std::cout << "You can not afford that.\n\n";
else
std::cout << "You purchased a potion of " << Potion::name[which] << ". You have " << player.gold() << " gold left.\n\n";
}
}
void printInventory(Player& player)
{
std::cout << "Your inventory contains: \n";
for (auto p: Potion::types)
{
if (player.inventory(p) > 0)
std::cout << player.inventory(p) << "x potion of " << Potion::name[p] << '\n';
}
std::cout << "You escaped with " << player.gold() << " gold remaining.\n";
}
int main()
{
std::cout << "Welcome to Roscoe's potion emporium!\n";
std::cout << "Enter your name: ";
std::string name{};
std::cin >> name;
Player player { name };
std::cout << "Hello, " << name << ", you have " << player.gold() << " gold.\n\n";
shop(player);
std::cout << '\n';
printInventory(player);
std::cout << "\nThanks for shopping at Roscoe's potion emporium!\n";
return 0;
}
Pytanie nr 3
Załóżmy, że chcemy napisać grę karcianą wykorzystującą standardową talię kart. Aby to zrobić, będziemy potrzebować sposobu na przedstawienie tych kart i talii kart. Zbudujmy tę funkcjonalność.
Wykorzystamy go w następnym pytaniu quizu, aby faktycznie zaimplementować grę.
> Krok #1
Talia kart składa się z 52 unikalnych kart (13 szeregów kart po 4 kolory). Twórz wyliczenia dla rang kart (as, 2, 3, 4, 5, 6, 7, 8, 9, 10, walet, dama, król) i kolorów (trefl, karo, kier, pik).
Pokaż rozwiązanie
// Because identifiers can't start with a number, we'll use a "rank_" prefix for these
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
// We'll also prefix these for consistency
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
> Krok #2
Każda karta będzie reprezentowana przez strukturę o nazwie Card który zawiera rangę i członka koloru. Utwórz strukturę i przenieś do niej wyliczenia.
Pokaż rozwiązanie
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
Rank rank{};
Suit suit{};
};
> Krok #3
Następnie dodajmy kilka przydatnych funkcji do naszej struktury Card. Po pierwsze, przeciążenie operator<< aby wydrukować rangę i kolor karty w postaci dwuliterowego kodu (np. walet pik zostanie wydrukowany jako JS). Można to zrobić wypełniając następującą funkcję:
struct Card
{
// Your other stuff here
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
out << // print your card rank and suit here
return out;
}
};
Po drugie dodaj funkcję zwracającą wartość karty. Traktuj asa jako wartość 11. Na koniec dodaj a std::array rangi i koloru (o nazwie allRanks i allSuits odpowiednio), aby można było je iterować. Ponieważ są one częścią struktury (nie przestrzeni nazw), uczyń je statycznymi, aby były tworzone tylko raz (nie z każdym obiektem).
Poniższe powinno się skompilować:
int main()
{
// Print one card
Card card { Card::rank_5, Card::suit_heart };
std::cout << card << '\n';
// Print all cards
for (auto suit : Card::allSuits)
for (auto rank : Card::allRanks)
std::cout << Card { rank, suit } << ' ';
std::cout << '\n';
return 0;
}
i wygenerowany zostanie następujący wynik:
5H
AC 2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC AD 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD AH 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH KH AS 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS
Pokaż rozwiązanie
#include <array>
#include <iostream>
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
// These need to be static so they are only created once per program, not once per Card
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
int main()
{
// Print one card
Card card { Card::rank_5, Card::suit_heart };
std::cout << card << '\n';
// Print all cards
for (auto suit : Card::allSuits)
for (auto rank : Card::allRanks)
std::cout << Card { rank, suit } << ' ';
std::cout << '\n';
return 0;
}
> Krok 4
Następnie stwórzmy naszą talię kart. Utwórz klasę o nazwie Deck zawierającą a std::array Kart. Możesz założyć, że talia składa się z 52 kart.
Talia powinna spełniać trzy funkcje:
Po pierwsze, konstruktor domyślny powinien zainicjować tablicę kart. Aby przeglądać wszystkie kolory i stopnie, możesz użyć pętli for ranged podobnej do tej w funkcji main() z poprzedniego przykładu.
Po drugie, dodaj a dealCard() funkcja, która zwraca następną kartę w talii według wartości. Od std::array jest tablicą o stałym rozmiarze, zastanów się, w jaki sposób będziesz śledzić, gdzie znajduje się następna karta. Ta funkcja powinna zostać potwierdzona, jeśli zostanie wywołana, gdy Deck przejdzie przez wszystkie karty.
Po trzecie napisz A shuffle() funkcja członkowska, która tasuje talię. Aby to ułatwić, skorzystamy z pomocy std::shuffle:
#include <algorithm> // for std::shuffle
#include "Random.h" // for Random::mt
// Put this line in your shuffle function to shuffle m_cards using the Random::mt Mersenne Twister
// This will rearrange all the Cards in the deck randomly
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
Klasa shuffle() funkcja powinna również zostać zresetowana, jednakże śledzisz miejsce, w którym znajduje się następna karta na początek talii.
Powinien uruchomić się następujący program:
int main()
{
Deck deck{};
std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';
deck.shuffle();
std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';
return 0;
}
i otrzymasz następujący wynik (ostatnie 3 karty powinny zostać losowane):
AC 2C 3C
2H 7H 9C
Pokaż rozwiązanie
#include <algorithm> // for std::shuffle
#include <array>
#include <cassert>
#include <iostream>
#include "Random.h"
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
class Deck
{
private:
std::array<Card, 52> m_cards {};
std::size_t m_nextCardIndex { 0 };
public:
Deck()
{
std::size_t count { 0 };
for (auto suit: Card::allSuits)
for (auto rank: Card::allRanks)
m_cards[count++] = Card{rank, suit};
}
void shuffle()
{
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
m_nextCardIndex = 0;
}
Card dealCard()
{
assert(m_nextCardIndex != 52 && "Deck::dealCard ran out of cards");
return m_cards[m_nextCardIndex++];
}
};
int main()
{
Deck deck{};
std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';
deck.shuffle();
std::cout << deck.dealCard() << ' ' << deck.dealCard() << ' ' << deck.dealCard() << '\n';
return 0;
}
Pytanie nr 4
W porządku, teraz użyjmy naszej karty i talii, aby wdrożyć uproszczoną wersję blackjacka! Jeśli nie znasz jeszcze Blackjacka, w Wikipedii znajdziesz artykuł na temat Blackjack , który zawiera podsumowanie.
Oto zasady naszej wersji Blackjacka:
- Kurier otrzymuje na początek jedną kartę (w prawdziwym życiu krupier otrzymuje dwie, ale jedna jest zakryta, więc w tym momencie nie ma to znaczenia).
- Gracz otrzymuje dwie karty do rozdania. start.
- Gracz zaczyna.
- Gracz może wielokrotnie „trafić” lub „wstać”.
- Jeśli gracz „wstanie”, jego tura dobiega końca, a jego wynik jest obliczany na podstawie kart, które otrzymał.
- Jeśli gracz „trafia”, otrzymuje kolejną kartę, a wartość tej karty jest dodawana do jego całkowitego wyniku.
- Zwykle as liczy się jako 1 lub 11 (w zależności od tego, co jest lepsze dla całkowitego wyniku). Dla uproszczenia policzymy to tutaj jako 11.
- Jeśli gracz przekroczy wynik 21, odpada i natychmiast przegrywa.
- Gdy gracz skończy, nadchodzi kolej krupiera.
- Krucznik wielokrotnie dobiera, aż osiągnie wynik 17 lub więcej, w którym to momencie musi przestać dobierać.
- Jeśli krupier przekroczy wynik 17 21, odpada i gracz natychmiast wygrywa.
- W przeciwnym razie, jeśli gracz ma wyższy wynik niż krupier, wygrywa. W przeciwnym razie gracz przegrywa (dla uproszczenia uznamy remis za wygraną krupiera).
W naszej uproszczonej wersji Blackjacka nie będziemy śledzić, jakie konkretne karty otrzymali gracz i krupier. Będziemy śledzić jedynie sumę wartości kart, które otrzymali gracz i krupier. To upraszcza sprawę.
Zacznij od kodu, który napisałeś w poprzednim quizie (lub skorzystaj z naszego rozwiązania referencyjnego).
> Krok #1
Utwórz strukturę o nazwie Player która będzie reprezentować uczestnika naszej gry (albo krupiera, albo gracza). Ponieważ w tej grze interesuje nas tylko wynik gracza, ta struktura potrzebuje tylko jednego członka.
Napisz funkcję, która (ostatecznie) rozegra rundę blackjacka. Na razie ta funkcja powinna losować jedną losową kartę dla krupiera i dwie losowe karty dla gracza. Powinien zwrócić wartość bool wskazującą, kto ma większy wynik.
Kod powinien wyprowadzić następującą treść:
The dealer is showing: 10
You have score: 13
You win!
The dealer is showing: 10
You have score: 8
You lose!
Pokaż rozwiązanie
#include <algorithm> // for std::shuffle
#include <array>
#include <cassert>
#include <iostream>
#include "Random.h"
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
class Deck
{
private:
std::array<Card, 52> m_cards {};
std::size_t m_nextCardIndex { 0 };
public:
Deck()
{
std::size_t count { 0 };
for (auto suit: Card::allSuits)
for (auto rank: Card::allRanks)
m_cards[count++] = Card{rank, suit};
}
void shuffle()
{
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
m_nextCardIndex = 0;
}
Card dealCard()
{
assert(m_nextCardIndex != 52 && "Deck::dealCard ran out of cards");
return m_cards[m_nextCardIndex++];
}
};
struct Player
{
int score{};
};
bool playBlackjack()
{
Deck deck{};
deck.shuffle();
Player dealer{ deck.dealCard().value() };
std::cout << "The dealer is showing: " << dealer.score << '\n';
Player player { deck.dealCard().value() + deck.dealCard().value() };
std::cout << "You have score: " << player.score << '\n';
return (player.score > dealer.score);
}
int main()
{
if (playBlackjack())
{
std::cout << "You win!\n";
}
else
{
std::cout << "You lose!\n";
}
return 0;
}
> Krok #2
Dodaj Settings przestrzeń nazw zawierającą dwie stałe: wartość, powyżej której gracz odpada, oraz wartość, przy której krupier musi przestać dobierać karty.
Dodaj logikę obsługującą turę krupiera. Krupier będzie dobierać karty, dopóki nie osiągnie 17, po czym musi się zatrzymać. Jeśli odpadną, gracz wygrywa.
Oto przykładowe wyniki:
The dealer is showing: 8
You have score: 9
The dealer flips a 4D. They now have: 12
The dealer flips a JS. They now have: 22
The dealer went bust!
You win!
The dealer is showing: 6
You have score: 13
The dealer flips a 3D. They now have: 9
The dealer flips a 3H. They now have: 12
The dealer flips a 9S. They now have: 21
You lose!
The dealer is showing: 7
You have score: 21
The dealer flips a JC. They now have: 17
You win!
Pokaż rozwiązanie
#include <algorithm> // for std::shuffle
#include <array>
#include <cassert>
#include <iostream>
#include "Random.h"
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
class Deck
{
private:
std::array<Card, 52> m_cards {};
std::size_t m_nextCardIndex { 0 };
public:
Deck()
{
std::size_t count { 0 };
for (auto suit: Card::allSuits)
for (auto rank: Card::allRanks)
m_cards[count++] = Card{rank, suit};
}
void shuffle()
{
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
m_nextCardIndex = 0;
}
Card dealCard()
{
assert(m_nextCardIndex != 52 && "Deck::dealCard ran out of cards");
return m_cards[m_nextCardIndex++];
}
};
struct Player
{
int score{};
};
namespace Settings
{
// Maximum score before losing.
constexpr int bust{ 21 };
// Minium score that the dealer has to have.
constexpr int dealerStopsAt{ 17 };
}
// Returns true if the dealer went bust. False otherwise.
bool dealerTurn(Deck& deck, Player& dealer)
{
while (dealer.score < Settings::dealerStopsAt)
{
Card card { deck.dealCard() };
dealer.score += card.value();
std::cout << "The dealer flips a " << card << ". They now have: " << dealer.score << '\n';
}
if (dealer.score > Settings::bust)
{
std::cout << "The dealer went bust!\n";
return true;
}
return false;
}
bool playBlackjack()
{
Deck deck{};
deck.shuffle();
Player dealer{ deck.dealCard().value() };
std::cout << "The dealer is showing: " << dealer.score << '\n';
Player player { deck.dealCard().value() + deck.dealCard().value() };
std::cout << "You have score: " << player.score << '\n';
if (dealerTurn(deck, dealer))
return true;
return (player.score > dealer.score);
}
int main()
{
if (playBlackjack())
{
std::cout << "You win!\n";
}
else
{
std::cout << "You lose!\n";
}
return 0;
}
> Krok #3
Na koniec dodaj logikę dla tury gracza. To zakończy grę.
Oto przykładowe wyniki:
The dealer is showing: 2
You have score: 14
(h) to hit, or (s) to stand: h
You were dealt KH. You now have: 24
You went bust!
You lose!
The dealer is showing: 10
You have score: 9
(h) to hit, or (s) to stand: h
You were dealt TH. You now have: 19
(h) to hit, or (s) to stand: s
The dealer flips a 3D. They now have: 13
The dealer flips a 7H. They now have: 20
You lose!
The dealer is showing: 7
You have score: 12
(h) to hit, or (s) to stand: h
You were dealt 7S. You now have: 19
(h) to hit, or (s) to stand: h
You were dealt 2D. You now have: 21
(h) to hit, or (s) to stand: s
The dealer flips a 6H. They now have: 13
The dealer flips a QC. They now have: 23
The dealer went bust!
You win!
Pokaż rozwiązanie
#include <algorithm> // for std::shuffle
#include <array>
#include <cassert>
#include <iostream>
#include "Random.h"
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
class Deck
{
private:
std::array<Card, 52> m_cards {};
std::size_t m_nextCardIndex { 0 };
public:
Deck()
{
std::size_t count { 0 };
for (auto suit: Card::allSuits)
for (auto rank: Card::allRanks)
m_cards[count++] = Card{rank, suit};
}
void shuffle()
{
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
m_nextCardIndex = 0;
}
Card dealCard()
{
assert(m_nextCardIndex != 52 && "Deck::dealCard ran out of cards");
return m_cards[m_nextCardIndex++];
}
};
struct Player
{
int score{};
};
namespace Settings
{
// Maximum score before losing.
constexpr int bust{ 21 };
// Minium score that the dealer has to have.
constexpr int dealerStopsAt{ 17 };
}
bool playerWantsHit()
{
while (true)
{
std::cout << "(h) to hit, or (s) to stand: ";
char ch{};
std::cin >> ch;
switch (ch)
{
case 'h':
return true;
case 's':
return false;
}
}
}
// Returns true if the player went bust. False otherwise.
bool playerTurn(Deck& deck, Player& player)
{
while (player.score < Settings::bust && playerWantsHit())
{
Card card { deck.dealCard() };
player.score += card.value();
std::cout << "You were dealt " << card << ". You now have: " << player.score << '\n';
}
if (player.score > Settings::bust)
{
std::cout << "You went bust!\n";
return true;
}
return false;
}
// Returns true if the dealer went bust. False otherwise.
bool dealerTurn(Deck& deck, Player& dealer)
{
while (dealer.score < Settings::dealerStopsAt)
{
Card card { deck.dealCard() };
dealer.score += card.value();
std::cout << "The dealer flips a " << card << ". They now have: " << dealer.score << '\n';
}
if (dealer.score > Settings::bust)
{
std::cout << "The dealer went bust!\n";
return true;
}
return false;
}
bool playBlackjack()
{
Deck deck{};
deck.shuffle();
Player dealer{ deck.dealCard().value() };
std::cout << "The dealer is showing: " << dealer.score << '\n';
Player player { deck.dealCard().value() + deck.dealCard().value() };
std::cout << "You have score: " << player.score << '\n';
if (playerTurn(deck, player))
return false;
if (dealerTurn(deck, dealer))
return true;
return (player.score > dealer.score);
}
int main()
{
if (playBlackjack())
{
std::cout << "You win!\n";
}
else
{
std::cout << "You lose!\n";
}
return 0;
}
Pytanie #5
a) Opisz, w jaki sposób możesz zmodyfikować powyższy program, aby obsługiwał przypadek, w którym asy mogą być równe 1 lub 11.
Ważne jest, aby pamiętać, że śledzimy tylko sumę kart, a nie konkretne karty posiadane przez użytkownika.
Pokaż rozwiązanie
Jednym ze sposobów byłoby śledzenie, ile asy, które gracz i krupier otrzymali (w strukturze Player , jako liczba całkowita). Jeśli gracz lub krupier przekroczy 21, a jego licznik asów będzie większy od zera, możesz zmniejszyć jego wynik o 10 (przeliczyć asa z 11 punktów na 1 punkt) i zmniejszyć licznik asów. Można to zrobić tyle razy, ile potrzeba, aż licznik asów osiągnie zero.
b) W prawdziwym blackjacku, jeśli gracz i krupier mają ten sam wynik (a gracz nie przegrał), wynikiem jest remis i żaden nie wygrywa. Opisz, jak zmodyfikowałbyś powyższy program, aby to uwzględnić.
Pokaż rozwiązanie
Nasza wersja playBlackjack() obecnie zwraca wartość bool wskazującą, czy gracz wygrał, czy nie. Będziemy musieli zaktualizować tę funkcję, aby uzyskać trzy możliwości: wygrana krupiera, wygrana gracza, remis. Najlepszym sposobem na osiągnięcie tego byłoby zdefiniowanie wyliczenia dla tych trzech opcji i umożliwienie funkcji zwrócenia odpowiedniego modułu wyliczającego.
c) Dodatkowy kredyt: zaimplementuj powyższe dwa pomysły w swojej grze w blackjacka. Pamiętaj, że będziesz musiał pokazać początkową kartę krupiera i początkowe dwie karty gracza, aby wiedzieli, czy ma asa, czy nie.
Oto przykładowy wynik:
The dealer is showing JH (10)
You are showing AH 7D (18)
(h) to hit, or (s) to stand: h
You were dealt JD. You now have: 18
(h) to hit, or (s) to stand: s
The dealer flips a 6C. They now have: 16
The dealer flips a AD. They now have: 17
You win!
Pokaż rozwiązanie
#include <algorithm> // for std::shuffle
#include <array>
#include <cassert>
#include <iostream>
#include "Random.h"
namespace Settings
{
// Maximum score before losing.
constexpr int bust{ 21 };
// Minium score that the dealer has to have.
constexpr int dealerStopsAt{ 17 };
}
struct Card
{
enum Rank
{
rank_ace,
rank_2,
rank_3,
rank_4,
rank_5,
rank_6,
rank_7,
rank_8,
rank_9,
rank_10,
rank_jack,
rank_queen,
rank_king,
max_ranks
};
enum Suit
{
suit_club,
suit_diamond,
suit_heart,
suit_spade,
max_suits
};
static constexpr std::array allRanks { rank_ace, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, rank_9, rank_10, rank_jack, rank_queen, rank_king };
static constexpr std::array allSuits { suit_club, suit_diamond, suit_heart, suit_spade };
Rank rank{};
Suit suit{};
friend std::ostream& operator<<(std::ostream& out, const Card &card)
{
static constexpr std::array ranks { 'A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K' };
static constexpr std::array suits { 'C', 'D', 'H', 'S' };
out << ranks[card.rank] << suits[card.suit];
return out;
}
int value() const
{
static constexpr std::array rankValues { 11, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
return rankValues[rank];
}
};
class Deck
{
private:
std::array<Card, 52> m_cards {};
std::size_t m_nextCardIndex { 0 };
public:
Deck()
{
std::size_t count { 0 };
for (auto suit: Card::allSuits)
for (auto rank: Card::allRanks)
m_cards[count++] = Card{rank, suit};
}
void shuffle()
{
std::shuffle(m_cards.begin(), m_cards.end(), Random::mt);
m_nextCardIndex = 0;
}
Card dealCard()
{
assert(m_nextCardIndex != 52 && "Deck::dealCard ran out of cards");
return m_cards[m_nextCardIndex++];
}
};
class Player
{
private:
int m_score{ };
int m_ace11Count { 0 }; // how many aces worth 11 points the player has
public:
// We'll use a function to add the card to the player's score
// Since we now need to count aces
void addToScore(Card card)
{
m_score += card.value();
if (card.rank == Card::rank_ace)
++m_ace11Count; // aces start at 11 points
consumeAces();
}
// Decrease aceCount by 1 and
void consumeAces()
{
// If the player would bust, see if we can switch aces from 11 points to 1
while (m_score > Settings::bust && m_ace11Count > 0)
{
m_score -= 10;
--m_ace11Count;
}
}
int score() { return m_score; }
};
bool playerWantsHit()
{
while (true)
{
std::cout << "(h) to hit, or (s) to stand: ";
char ch{};
std::cin >> ch;
switch (ch)
{
case 'h':
return true;
case 's':
return false;
}
}
}
// Returns true if the player went bust. False otherwise.
bool playerTurn(Deck& deck, Player& player)
{
while (player.score() < Settings::bust && playerWantsHit())
{
Card card { deck.dealCard() };
player.addToScore(card);
std::cout << "You were dealt " << card << ". You now have: " << player.score() << '\n';
}
if (player.score() > Settings::bust)
{
std::cout << "You went bust!\n";
return true;
}
return false;
}
// Returns true if the dealer went bust. False otherwise.
bool dealerTurn(Deck& deck, Player& dealer)
{
while (dealer.score() < Settings::dealerStopsAt)
{
Card card { deck.dealCard() };
dealer.addToScore(card);
std::cout << "The dealer flips a " << card << ". They now have: " << dealer.score() << '\n';
}
if (dealer.score() > Settings::bust)
{
std::cout << "The dealer went bust!\n";
return true;
}
return false;
}
enum class GameResult
{
playerWon,
dealerWon,
tie
};
GameResult playBlackjack()
{
Deck deck{};
deck.shuffle();
Player dealer{};
Card card1 { deck.dealCard() };
dealer.addToScore(card1);
std::cout << "The dealer is showing " << card1 << " (" << dealer.score() << ")\n";
Player player{};
Card card2 { deck.dealCard() };
Card card3 { deck.dealCard() };
player.addToScore(card2);
player.addToScore(card3);
std::cout << "You are showing " << card2 << ' ' << card3 << " (" << player.score() << ")\n";
if (playerTurn(deck, player)) // if player busted
return GameResult::dealerWon;
if (dealerTurn(deck, dealer)) // if dealer busted
return GameResult::playerWon;
if (player.score() == dealer.score())
return GameResult::tie;
return (player.score() > dealer.score() ? GameResult::playerWon : GameResult::dealerWon);
}
int main()
{
switch (playBlackjack())
{
case GameResult::playerWon:
std::cout << "You win!\n";
return 0;
case GameResult::dealerWon:
std::cout << "You lose!\n";
return 0;
case GameResult::tie:
std::cout << "It's a tie.\n";
return 0;
}
return 0;
}