C++でブラックジャックを作りたい


##動機

[プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし](https://qiita.com/hirossyi73/items/cf8648c31898216312e5)

C++については入門書を読んだ程度のpython人間(ただしオブジェクト指向的な機能は使ったことがない)として、学習の題材としてちょうどよさそうだったので。


##ルール

ルールのおさらい(以下引用)

- 初期カードは52枚。引く際にカードの重複は無いようにする
- プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
- 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
- その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
- プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
- プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
- プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
- JとQとKは10として扱う
- Aはとりあえず「1」としてだけ扱う。「11」にはしない
- ダブルダウンなし、スピリットなし、サレンダーなし、その他特殊そうなルールなし




##コード

元記事のルール以下の部分は見ずに自分なりに書いてみたコードがこちら

```C++


#include <iostream>
#include <vector>
#include <random>
using namespace std;


//
// プレーヤーとディーラーを共通して扱うクラス
//

class Card_holder{
    
    private:
        int hand;
        bool bursted = false;
    
    public:
    
    bool stayed = false;
    
    int open_hand(){
        return hand;
    };
    
    int isbursted(){
        return bursted;
    };

    
    int valueof(int card){
        int cardtonum;
        int cardtotype;
        
        cardtonum = card % 13 + 1;
        cardtotype = card/13;
        
        cout << "Card is " << cardtonum << " of " << cardtotype << endl;
        
        if(cardtonum >= 11) cardtonum = 10;
        
        return cardtonum;
        
    };
    
    void hit(vector<int>& deck){
        int new_card;
        new_card = deck.back();
        deck.pop_back();
        hand = hand + valueof(new_card);
        if(hand > 21) bursted = true;
        
    };
    
};



int main() {
    
    //
    // 1-13のカード4種類をランダムにシャッフル
    //
    
    vector<int> cardlist(13*4);
    
    for(int i = 0; i < 13*4; i++) cardlist[i] = i;

    random_device seed_gen;
    mt19937 engine(seed_gen());
    shuffle(cardlist.begin(), cardlist.end(), engine);

    Card_holder player;
    Card_holder dealer;

    
    //
    // ディーラーのカードを一枚公開
    //
    
    cout << "dealer card" << endl;
    dealer.hit(cardlist);
    
    //
    // プレーヤーのターン
    //
    
    cout << "Player turn" << endl;
    cout << "player card" << endl;
    player.hit(cardlist);
    player.hit(cardlist);
    cout << "player hand is " << player.open_hand() << endl;
    

    char stay = 'n';
    
    while(!player.isbursted()){
        cout << "if stay, type y" << endl;
        cin >> stay;
        if(stay == 'y') break;
        player.hit(cardlist);
        cout << "player hand is " << player.open_hand() << endl;
    };
    
    if(stay != 'y') {
        cout << "player bursted" << endl;
        goto PLAYER_LOSE;
    }
    
    
    
    //
    // バーストしてなければディーラーのターン
    //

    cout << "dealer turn" << endl;
    
    
    while(dealer.open_hand() < 17){
        dealer.hit(cardlist);
        cout << "dealer hand is " << dealer.open_hand() << endl;;

    };
    
    if(dealer.isbursted()) {
        cout << "dealer bursted" << endl;
        goto PLAYER_WIN;
    }

    
    //
    // 勝敗判定
    //
    
    cout << "Result" << endl;
    cout << "player card is " << player.open_hand() << endl;
    cout << "dealer card is " << dealer.open_hand() << endl;
    

    if(player.open_hand() > dealer.open_hand()) goto PLAYER_WIN ;
    if(player.open_hand() == dealer.open_hand()) goto NO_GAME;
    if(player.open_hand() < dealer.open_hand()) goto PLAYER_LOSE;

    

    PLAYER_WIN:
        cout << "player win" << endl;
        return 0;
    PLAYER_LOSE:
        cout << "player lose" << endl;
        return 0;
    NO_GAME:
        cout << "no game" << endl;
        return 0;
    

}


```

##実行例


```shell 

>./a.out

dealer card
Card is 2 of 0
Player turn
player card
Card is 13 of 2
Card is 10 of 3
player hand is 20
if stay, type y
y
dealer turn
Card is 6 of 0
dealer hand is 8
Card is 7 of 3
dealer hand is 15
Card is 13 of 0
dealer hand is 25
Dealer bursted
player win


>./a.out

dealer card
Card is 3 of 2
Player turn
player card
Card is 9 of 1
Card is 6 of 2
player hand is 15
if stay, type y
n
Card is 11 of 3
player hand is 25
player bursted
player lose
```


- 面倒だったので絵柄は数字[0, 1, 2, 3]で代用。
(スペードの1 -> 1 of 0, ハートの5 -> 5 of 2のようなイメージ)

- 山はstd::vector<int>で扱い、カードを引く動作をpop_back()で。

- ディーラーとプレーヤーを同一のクラスで扱っている。

- バースト時の処理がややこしくなりそうだったのでgotoを使った。

##課題

オブジェクト指向っぽさが弱い……。

エースの処理を実際のBJに近くする辺りが面倒そう。

現状でもマルチプレーヤー化は簡単だが、それをどうオブジェクト指向っぽくやるか。

ディーラーが引いている時とプレーヤーが引いている場合を分けた方が変更には強そう。

今回のコード、厳密には提示されたBJのルールに従っていない。
というのも、

>実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする


がルールだが、今回はディーラーの2枚目以降はプレーヤーの動作の後に引かれることになっている。このおかげでカード引きの処理をディーラとプレーヤーで統一できている & カードがちゃんとランダムにシャッフルされるゲームなら問題はない、ので一概に悪いとはいえないのだけれど…