ViVi Home > 技術文書 > さくさく理解できる C/C++ コンソールアプリ入門 > デッキクラス
デッキ(deck)とは、カード(トランプ)の一組のことである。
本稿ではカード(トランプ)アプリを作るために利用可能なデッキクラス(class Deck)の説明を行う。
なお、カードゲームの種類によってはジョーカーを含む場合があるが、本稿で説明するデッキにはジョーカーは含まれないものとする。
デッキクラスの前に、1枚のカードを表す構造体を定義しておく。
// 1枚のカードを表す構造体(ジョーカーは除く) struct Card { public: enum Suit { SPADES = 0, CLUBS, HERTS, DIAMONDS, N_SUIT, // スート種類数(==4) RANK_2 = 0, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, RANK_J, RANK_Q, RANK_K, RANK_A, N_RANK, // ランク種類数(==13) N_CARD = N_SUIT * N_RANK, // 全カード枚数 }; public: Card(char suit = 0, char rank = 0) : m_suit(suit), m_rank(rank) {} public: void print() const { std::cout << toString(); } std::string toString() const; std::wstring toStringW() const; bool operator==(const Card &x) const { return m_suit == x.m_suit && m_rank == x.m_rank; } public: char m_suit; // SPADES | CLUBS | HERTS | DIAMONDS char m_rank; // 0: 2,… 7: 9, 8:10, 9:J, 10:Q, 11:K, 12:A };
最初の enum でスート(マーク)、スート種類数、ランク(数字)、ランク種類数を定義している。
ついで、コンストラクタの宣言・定義。
引数で指定されたスート、ランクでメンバ変数を初期化している。
メンバ関数としては、表示、表示用文字列変換、比較演算子を宣言している。
Card は構造体で、メンバ変数はパブリックなので、ゲッター・セッターは定義していない。
メンバ変数として、スートとランクを宣言している。ランク2は値0, ランクAは値12とする。
ポーカーの役の強さにおいては、通常 A が最も強いので、A の値を一番大きな数値に割り当てている。
std::string Card::toString() const { if( m_suit < 0 || m_suit >= N_SUIT || m_rank < 0 || m_rank >= N_RANK ) return std::string("??"); std::string str(1, "SCHD"[m_suit]); str += "23456789TJQKA"[m_rank]; return str; }
上記は、カードを表示用文字列に変換するメンバ関数の実装。
最初にメンバ変数の範囲チェックを行い、範囲外であれば "??" を返す。
範囲内であれば m_suit, m_rank を参照して表示用文字列に変換している。
"文字列"[ix] は数値から文字に変換するテクニックだ。
文字列は文字配列へのポインタなので、直後に [ix] を記述すると、文字列の ix 番目の文字を取り出すことができる。
std::string は文字から文字列を作る場合は std::string(size_t n, char c) を使用するので、第1引数の文字数に1を指定する。
ランクを文字に変換するのも同様に "文字列"[ix] のテクニックを使って文字に変換し、operator+=(char) を使ってそれを連結する。
std::wstring Card::toStringW() const { if( m_suit < 0 || m_suit >= N_SUIT || m_rank < 0 || m_rank >= N_RANK ) return std::wstring(L"??"); std::wstring str(1, L"♠♣♡♢"[m_suit]); str += L"23456789TJQKA"[m_rank]; return str; }
上記は、カードをユニコードの表示用文字列に変換するメンバ関数の実装。
シングルバイトでは、スートが「SCHD」で表示するしかなく、識別が困難だが、
ユニコードであれば「♠♣♡♢」なので、識別が楽になる。
中身はシングルバイトの場合とほとんど同一だ。
カード構造体が出来たので、本題のデッキクラスを作ろう。
// 一組(52枚)のカードを表すクラス class Deck { public: Deck() { init(); } ~Deck() {} public: void print() const; int nDealt() const { return m_nDealt; } int nRest() const { return Card::N_CARD - m_nDealt; } public: void init(); void shuffle(); Card deal(); // カードを1枚配る(配ったカードを返す) bool take(Card); // 指定カードをデックから取り出す private: int m_nDealt; // 既に配られたカード枚数(m_card の最初の m_nDealt 枚が配られている) Card m_card[Card::N_CARD]; // カード配列 };
データメンバとして、既に配られたカードの枚数(m_nDealt)と、カード型の配列(m_card[])を用意している。
既に配ったカードは配列から削除するのではなく、前方に移動するものとした。