前々回
C++でブラックジャックを作りたい
前回
[続・C++でブラックジャックを作りたい] (https://qiita.com/fj91/items/054d7c5e6ef52a8f6b0b)
前回でA(エース)の処理が実装され、無事にBJが出るBJが作れた。
今回は、チップの概念を導入し、継続して複数回のプレイが可能な形に変更する。
現実のBJ同様、使用するshooterは各ゲームで同一にしたいので、shooter内のカード枚数がゼロになったら新しい山を補充するという機能を追加。
- Card_holderのメンバ関数としてtipsを定義し、各ゲームでの勝敗に応じてそれを変更していくというスタイルを取る。
- ゲームの進行をクラスGameで管理し、main関数の可読性を向上。
- 勝敗処理に使っていたgotoを排除。
- ただし、ゲームを継続するか否かの処理はgotoが一番シンプルそうなので使用。
乱数周りについては[前回] (https://qiita.com/fj91/items/054d7c5e6ef52a8f6b0b)のコメント欄で @yumetodo さんに示して頂いた例をそのまま使用(勉強不足)。
コード
# include "random_device.hpp"
# include <iostream>
# include <vector>
# include <array>
# include <random>
# include <algorithm>
using namespace std;
using seed_v_t = std::array<cpprefjp::random_device::result_type, sizeof(std::mt19937)/sizeof(cpprefjp::random_device::result_type)>;
seed_v_t create_seed_v()
{
cpprefjp::random_device rnd;
seed_v_t sed_v;
std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
return sed_v;
}
std::mt19937 create_random_engine()
{
const auto sed_v = create_seed_v();
std::seed_seq seq(sed_v.begin(), sed_v.end());
return std::mt19937(seq);
}
std::mt19937& random_engine() {
static thread_local std::mt19937 engine = create_random_engine();
return engine;
}
class Shooter{
private:
static constexpr size_t max_size = 4*13;
vector<int> cardlist;
void shuffledeck(vector<int>& deck){
shuffle(deck.begin(), deck.end(), random_engine());
};
public:
Shooter(){
for(int i = 0; i < max_size; i++) this->cardlist.emplace_back(i);
shuffledeck(this->cardlist);
};
int hit(){
if(this->cardlist.size() == 0){
cout << "No cards, new cards filled" << endl;
for(int i = 0; i < max_size; i++) this->cardlist.emplace_back(i);
shuffledeck(this->cardlist);
}
cout << "new card " << endl;
int new_card;
new_card = this->cardlist.back();
this->cardlist.pop_back();
return new_card;
};
};
class Card_holder{
private:
vector<int> mycards;
vector<int> myhands;
bool busted;
double tips;
bool check_busted() const noexcept{
return (*min_element(this->myhands.begin(), this->myhands.end()) > 21);
};
static int valueof(int card) noexcept{
return min(card % 13 + 1, 10);
};
static vector<int> deletetrash(vector<int>& myhands) noexcept{ // バーストしている組み合わせをmyhandsから削除
vector<int> non_bustedhands;
non_bustedhands.reserve(myhands.size());
copy_if(myhands.begin(), myhands.end(), back_inserter(non_bustedhands), [](int n){ return n <= 21; });
sort(non_bustedhands.begin(), non_bustedhands.end());
non_bustedhands.erase(std::unique(non_bustedhands.begin(), non_bustedhands.end()), non_bustedhands.end());
return non_bustedhands;
};
static vector<int> calc_hands(vector<int>&& myhands, vector<int>& mycards) noexcept{ // Aを処理し、可能な手の組み合わせを生成
if(0 != mycards.back() % 13){
const auto inc = valueof(mycards.back());
for(int& x: myhands) x += inc;
return myhands;
}
else{
vector<int> calculated;
calculated.reserve(myhands.size() * 2);
for(const int x: myhands){
calculated.emplace_back(x + 1);
calculated.emplace_back(x + 11);
}
return calculated;
}
};
public:
Card_holder(): mycards(), myhands(1),busted(false),tips(100){};
void cleaning() noexcept{
this->myhands = vector<int>(0);
this->myhands.push_back(0);
busted = false;
}
double showtips() const noexcept{
return this->tips;
}
int open_hand() const noexcept{ //バーストしていない組み合わせで最強のもの
return *max_element(this->myhands.begin(), this->myhands.end());
};
bool isbusted() const noexcept{
return this->busted;
};
void tipchange(double value, double bet) noexcept {
this->tips += value * bet;
}
void hit(Shooter& shooter) noexcept{ // 分割したほうが良さそう?
const int new_card = shooter.hit();
const int cardtonum = new_card % 13 + 1;
const int cardtotype = new_card/13;
cout << "Card is " << cardtonum << " of " << cardtotype << endl;
this->mycards.emplace_back(new_card);
this->myhands = calc_hands(move(this->myhands), mycards);
this->busted = check_busted();
this->myhands = deletetrash(this->myhands);
if(this->myhands.size() >0){
cout <<"hand is " ;
for(int x: this->myhands) cout << x << ", ";
cout <<endl;
}
};
};
class Game{ //勝利判定やゲームの進行をまとめたクラス
public:
Game(Card_holder& dealer, Card_holder& player, double& bet){
player.cleaning();
dealer.cleaning();
cout << "Game Start" << endl;
cout << "Your tips amout is " << player.showtips() << endl;
cout << "Please bet" << endl;
cin >> bet;
};
int judge(Card_holder& dealer, Card_holder& player) const noexcept{
cout << "--------------------------------" << endl;
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()) return 1 ;
if(player.open_hand() == dealer.open_hand()) return 0;
if(player.open_hand() < dealer.open_hand()) return -1;
return 10;
}
int player_turn(Shooter& shooter, Card_holder& player) noexcept{
cout << "--------------------------------" << endl;
cout << "Player turn" << endl;
player.hit(shooter);
player.hit(shooter);
if(player.open_hand() == 21){
cout << "Black Jack!" << endl;
return 2;
};
char stay = 'n';
while(!player.isbusted()){
cout << "if stay, type y" << endl;
cin >> stay;
if(stay == 'y') break;
player.hit(shooter);
};
if(stay != 'y') {
cout << "player busted" << endl;
return -1;
}
cout << "player hand is " << player.open_hand() << endl;
return 0;
}
int dealer_turn(Shooter& shooter, Card_holder& dealer) noexcept{
cout << "--------------------------------" << endl;
cout << "dealer turn" << endl;
while(dealer.open_hand() < 17){
dealer.hit(shooter);
};
if(dealer.isbusted()) {
cout << "dealer busted" << endl;
return 1;
}
cout << "dealer hand is " << dealer.open_hand() << endl;
return 0;
}
};
int main() {
Shooter shooter;
Card_holder player;
Card_holder dealer;
NEXT_GAME:
double bet;
int win_lose_flag; //決着がついていない状態を0で持つ。2: player BJ, 1: player win, -1: dealer win
Game game(dealer, player, bet);
win_lose_flag = 0;
cout << "--------------------------------" << endl;
cout << "dealer card open" << endl;
dealer.hit(shooter);
win_lose_flag = game.player_turn(shooter, player);
if(win_lose_flag == 0 || win_lose_flag == 1) win_lose_flag = game.dealer_turn(shooter, dealer);
if(win_lose_flag == 0) win_lose_flag = game.judge(dealer, player);
switch (win_lose_flag)
{
case 2:
cout << "player win" << endl;
player.tipchange(1.5, bet);
break;
case 1:
cout << "player win" << endl;
player.tipchange(1.0, bet);
break;
case 0:
cout << "no game" << endl;
break;
case -1:
cout << "player lose" << endl;
player.tipchange(-1.0, bet);
break;
default:
cout << "Something wrong happened" << endl;
}
cout << "Your tips amout is " << player.showtips() << endl;
char next_game;
cout << "--------------------------------" << endl;
cout << "Next game? press y or n" << endl;;
cin >> next_game;
if(next_game == 'y') goto NEXT_GAME;
return 0;
}
実行例
>./a.out
Game Start
Your tips amout is 100
Please bet
10
--------------------------------
dealer card open
new card
Card is 5 of 0
hand is 5,
--------------------------------
Player turn
new card
Card is 6 of 0
hand is 6,
new card
Card is 4 of 0
hand is 10,
if stay, type y
n
new card
Card is 3 of 0
hand is 13,
if stay, type y
y
player hand is 13
--------------------------------
dealer turn
new card
Card is 2 of 0
hand is 7,
new card
Card is 8 of 0
hand is 15,
new card
Card is 9 of 0
dealer busted
player win
Your tips amout is 110
--------------------------------
Next game? press y or n
y
Game Start
Your tips amout is 110
Please bet
10
--------------------------------
dealer card open
new card
Card is 7 of 0
hand is 7,
--------------------------------
Player turn
new card
Card is 10 of 0
hand is 10,
new card
Card is 1 of 0
hand is 11, 21,
Black Jack!
player win
Your tips amout is 125
--------------------------------
Next game? press y or n
y
Game Start
Your tips amout is 125
Please bet
15
--------------------------------
dealer card open
No cards, new cards filled
new card
Card is 4 of 0
hand is 4,
--------------------------------
Player turn
new card
Card is 8 of 0
hand is 8,
new card
Card is 1 of 0
hand is 9, 19,
if stay, type y
y
player hand is 19
--------------------------------
dealer turn
new card
Card is 3 of 0
hand is 7,
new card
Card is 10 of 0
hand is 17,
dealer hand is 17
--------------------------------
Result
player card is 19
dealer card is 17
player win
Your tips amout is 140
3連勝......
今後
勝敗判定のフラグはGameのメンバ変数にしたほうが良かったかもしれない。
次はsurrender, double, splitといった特殊動作を実装したいところ。
いい加減標準入力の例外処理をするべきか……