C++ 配列でカードのデッキを表す

Abdul Mateen 2023年10月12日
  1. カードのデッキ
  2. C++ での個々のカード表現
  3. C++ でのデッキ オブ カード配列表現
C++ 配列でカードのデッキを表す

このチュートリアルでは、C++ 配列を使用してカードのデッキを表現します。

最初に、カード デッキの準備について説明し、次に C++ でカードを表現する方法について説明します。 最後に、カードのデッキを表現するための実用的な例を紹介します。

カードのデッキ

カードの標準的なデッキには、ハート、クラブ、スペード、ダイヤモンドの 4つのスイートまたはタイプがあります。 各スイートには、エース、2、3、4、5、6、7、8、9、10、ジャック、クイーン、キングの 13 枚のカードがあります。 したがって、デッキ全体には合計 52 枚のカードがあります。

カードには多くのゲームがあります。 ただし、これについては詳しく説明しません。 異なるゲームでは、ジャック、クイーン、キングの値はそれぞれ 10、11、12 です。

一部のゲームでは、エースは 13 または 1、あるいは両方と見なすことができます。

C++ での個々のカード表現

カードの種類/スイート情報と、各カードのカード番号/値を保存する必要があります。 カードの値は整数変数に格納できる単純な数値ですが、カードの種類はタイプ 0、タイプ 1、タイプ 2、およびタイプ 3 としてエンコードできます。

タイプ 0 の場合は 0 を格納できます (ハートのタイプであるとします)。 同様に、クラブのタイプ 1 には 1 を、スペードとダイアモンドには 1 を格納できます。

この概念を実現するために、以下のコードを見てみましょう。

// Representation of jack of hearts
int type = 0;
int value = 11;
// Representation of king of club
int type = 1;
int value = 13;
// Representation of three of spade
int type = 2;
int value = 3;

この表現は、カードのゲームを処理する目的に役立ちます。 たとえば、value 変数を比較して、どちらのプレイヤーがより大きな値のカードを持っているかを確認できます。

検討中のプレイヤーが持っているカードの種類を確認したい場合は、type 変数を確認できます。

C++ でのデッキ オブ カード配列表現

単一のカードの表現について説明しました。 カードのデッキのさまざまな C++ 表現を見てみましょう。

C++ での並列配列による表現

カードのデッキを表すために、2つの並列配列を使用できます。 1つの配列はカードの種類を格納し、もう 1つの配列は対応する位置に値を格納します。

これらの並列配列の配列宣言と初期化は次のとおりです。

int type[52] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
int value[52] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

このコードでは、type[i]value[i] はデッキの i 番目のカードを表します。

C++における剰余と整数除算による表現

このスキームよりも優れた別のスマートな表現方法があります。 整数配列には 0 ~ 51 を格納できます。

整数除算と剰余演算を使用して、カードの種類と値を取得できます。

最初の 13 個のインデックスに 0 ~ 12 を割り当てます。 C++ の算術除算演算子は、デフォルトで整数除算を実行します。 したがって、0 から 12 までの数字を 13 で割ると、カードの種類として 0 が得られます (つまり、この場合はハートのスーツ)。

さらに、任意の数の余りを 13 で取ると、0 から 12 になります。この余りの値は、カードの実際の値として機能します。

したがって、13 から 25 を次の 13 のインデックスに割り当て、残りのカードについても同様に割り当てます。

繰り返しますが、これらのカードを 13 で整数で割ると、値 1 (つまり、この場合のクラブのカード タイプ) が得られます。 繰り返しますが、13 で余りを取ると、カードの値である 0 から 12 が得られます。

このアイデアをまとめたコード例を見てみましょう。

int card[52] = {0, 1,  2,  3,  4,  5,  6,  7,     8,
                9, 10, 11, 12, 13, 14, 15, 16..., 51};
int type = card[i] / 13;
int value = card[i] % 13;

この表現では、空間の複雑さははるかに優れています (つまり、2つの並列配列を使用する必要はありません)。

前の表現では、1つの配列の要素を交換したい場合、2 番目の配列の対応する要素を交換して、対応する位置で型と値の両方の一貫性を保つ必要がありました。 この 2 番目の表現では、それを気にせずにシャッフルできます。

その理由は、カードの種類と価値の両方を表す数字が 1つだけだからです。

10 や 11 などの数字を使用すると、コードの可読性が低下します。 したがって、コードを読みやすくするために、いくつかの定数を定義できます。

#define JACK 10
#define QUEEN 11
#define KING 12
#define ACE 1
#define HEART 0
#define DIAMOND 1
#define CLUB 2
#define SPADE 3

これで、次のように比較できます。

type = card[i] / 13;
value = card[i] % 13;
... if (type == CLUB)... if (value == JACK)...

最後に、カードを印刷する文字列配列を定義できます。

string type_name[] = {"Heart", "Diamond", "Club", "Spade"};
string value_name[]{"Ace", "Two", ..., "Jack", "Queen", "King"};
... cout << value_name[value] << "of" << type_name[type] << '\n';

出力は次のようになります。

Three of Spade
Jack of Diamond
Queen of Diamond
...

完全な C++ 実装

ここで、C++ 配列でのカード デッキの表現は明確でなければなりません。 ただし、表現を認識できるようにするために、上記のすべてのコード チャンクを、すぐにコンパイルできる単一のコードに結合しましょう。

#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;

void Shuffle(int* Deck) {
  srand(time(NULL));
  int shufflePosition1, shufflePosition2, temp;
  int shuffleCount = 100;
  for (int i = 0; i < shuffleCount; i++) {
    shufflePosition1 = rand() % 52;
    shufflePosition2 = rand() % 52;
    // swap cards at the shuffle positions
    temp = Deck[shufflePosition1];
    Deck[shufflePosition1] = Deck[shufflePosition2];
    Deck[shufflePosition2] = temp;
  }
}

void ShowFirstTenCards(int* Deck, string* TypeName, string* ValueName) {
  int valueNamePosition;
  int typeNamePosition;
  for (int t = 0; t <= 10; t++) {
    valueNamePosition = Deck[t] % 13;
    typeNamePosition = Deck[t] / 13;
    cout << "Position " << t << ": ";
    cout << ValueName[valueNamePosition] << " of ";
    cout << TypeName[typeNamePosition] << endl;
  }
}

int main() {
  int Deck[52];
  for (int itr = 0; itr <= 51; itr++) {
    Deck[itr] = itr;
  }
  string TypeName[] = {"Heart", "Diamond", "Club", "Spade"};
  string ValueName[]{"Ace",   "Two",  "Three", "Four", "Five",  "six", "Seven",
                     "Eight", "Nine", "Ten",   "Jack", "Queen", "King"};
  cout << "Top 10 cards before shuffle:" << endl;
  ShowFirstTenCards(Deck, TypeName, ValueName);
  Shuffle(Deck);
  cout << "\nTop 10 cards After shuffle:" << endl;
  ShowFirstTenCards(Deck, TypeName, ValueName);

  return 0;
}

上記のコード例は、カード デッキを表し、シャッフル操作を実行する簡単な実装です。 詳細について説明する前に、出力を見てみましょう。

出力:

Top 10 cards before shuffle:
Position 0: Ace of Heart
Position 1: Two of Heart
Position 2: Three of Heart
Position 3: Four of Heart
Position 4: Five of Heart
Position 5: six of Heart
Position 6: Seven of Heart
Position 7: Eight of Heart
Position 8: Nine of Heart
Position 9: Ten of Heart
Position 10: Jack of Heart

Top 10 cards After shuffle:
Position 0: Queen of Diamond
Position 1: Eight of Spade
Position 2: Five of Heart
Position 3: King of Diamond
Position 4: Nine of Heart
Position 5: Eight of Heart
Position 6: Seven of Heart
Position 7: Seven of Diamond
Position 8: six of Spade
Position 9: Ace of Club
Position 10: Two of Heart

上記のプログラムには、3つの主要なコード セグメントがあります。

  1. ドライバーコード (main())
  2. ShowFirstTenCards() 関数
  3. Shuffle() 関数

上から下までコード セグメントの説明を始めましょう。 メイン ドライバー コードは、次の 3つの配列を作成します。

  1. デッキ 配列: 前のセクションで説明した方法に従って、カードのスイート情報とその額面を格納します。
  2. TypeName: 4つの利用可能なスイートの名前を保存します。
  3. ValueName: 出力をより適切に表示するために、額面の値の英語名を保存します。

重要な宣言の後、ドライバ コードは 3つの配列を引数として ShowFirstTenCards() メソッドに渡しました。 次に、このメソッドは値とスーツの変換戦略を使用して、デッキの最初の 10 枚のカードを表示します。

その後、ドライバー関数は Deck 配列を引数として Shuffle() 関数を呼び出します。 この Shuffle() 関数は、範囲 [0-51] からランダムに 2つの位置を選択し、それらの位置でコンテンツを交換します。

良いシャッフルを確実にするために、同じプロセスを 100 回繰り返します。

さらに、shuffle() 関数は、現在の UNIX タイムスタンプを rand() 関数のシード値 (srand() 関数内) として使用して、異なる呼び出しでシャッフルが一意のままであることを保証します。

このタイムスタンプをシードとして rand() 関数に渡さない場合、rand() は常にランダムな位置に対して同じシーケンスを生成しますが、これはこれらの確率ゲームには望ましくありません。 rand() 関数とそのシードの詳細については、こちら を参照してください。

関連記事 - C++ Array