ViVi Home > 技術文書 > さくさく理解できる C/C++ コンソールアプリ入門 > デッキクラス


 

 

デッキクラス
Copyright (C) 2014 by Nobuhide Tsuda

概要

デッキ(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[])を用意している。

既に配ったカードは配列から削除するのではなく、前方に移動するものとした。