動機
プログラミング入門者からの卒業試験は『ブラックジャック』を開発すべし
C++については入門書を読んだ程度のpython人間(ただしオブジェクト指向的な機能は使ったことがない)として、学習の題材としてちょうどよさそうだったので。
ルール
ルールのおさらい(以下引用)
- 初期カードは52枚。引く際にカードの重複は無いようにする
- プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行
- 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
- その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
- プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
- プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
- プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
- JとQとKは10として扱う
- Aはとりあえず「1」としてだけ扱う。「11」にはしない
- ダブルダウンなし、スピリットなし、サレンダーなし、その他特殊そうなルールなし
コード
元記事のルール以下の部分は見ずに自分なりに書いてみたコードがこちら
# 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;
}
実行例
>./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で扱い、カードを引く動作をpop_back()で。
-
ディーラーとプレーヤーを同一のクラスで扱っている。
-
バースト時の処理がややこしくなりそうだったのでgotoを使った。
課題
オブジェクト指向っぽさが弱い……。
エースの処理を実際のBJに近くする辺りが面倒そう。
現状でもマルチプレーヤー化は簡単だが、それをどうオブジェクト指向っぽくやるか。
ディーラーが引いている時とプレーヤーが引いている場合を分けた方が変更には強そう。
今回のコード、厳密には提示されたBJのルールに従っていない。
というのも、
実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
がルールだが、今回はディーラーの2枚目以降はプレーヤーの動作の後に引かれることになっている。このおかげでカード引きの処理をディーラとプレーヤーで統一できている & カードがちゃんとランダムにシャッフルされるゲームなら問題はない、ので一概に悪いとはいえないのだけれど…