はじめに
こちらプログラミング入門者からの卒業試験は『ブラックジャック』を開発すべしを読みまして、面白そうなのでこの問題を使って大学のサークルでプログラミングコンテストをしました。
そのときにみんな苦労していたのが、「Aを1でも11でも扱えるようにするには?」という点です。
人によって
・逐次プレイヤーにAを1か11どちらで扱うか尋ねる
・11で計算して、バーストした時点で1と計算する
などの方法で解決していました。
僕は別の戦略で解いたのでその話をします。
言語はc++です。
戦略
得点は整数型で扱っている人がほとんどでしたが、そうなるとそのA問題解決のロジックを得点自身でなく外部から解決してやらなければなりません。
そうでなく、新たにScoreクラスを導入し、「Aは1か11か」という両方の可能性を内包したまま演算できるようなものを考えます。
そのために内部パラメータとして、今のスコアが取りうる可能性をすべて保持しておきます。
例えば、
「3を引く」 -> (score: 3)
↓
「Aを引く」 -> (score: 4, 14)
↓
「5を引く」 -> (score: 9, 19)
↓
「Aを引く」 -> (score: 10, 20, 30)
みたいな感じです。
コード
class Score{
vector<int> candidates;
public:
Score(int n = 0){
if(n != 1 && n <= 9){
candidates.push_back(n);
}else if(n >= 10){
candidates.push_back(10);
}else{
candidates.push_back(1);
candidates.push_back(11);
}
}
Score operator+(Score& s){
Score new_score(0);
for(auto& c1 : candidates){
for(auto& c2 : s.candidates){
new_score.candidates.push_back(c1 + c2);
}
}
return new_score;
}
Score& operator+=(Score s){
vector<int> new_candidates;
for(auto& c1 : candidates){
for(auto& c2 : s.candidates){
new_candidates.push_back(c1 + c2);
}
}
candidates = new_candidates;
return *this;
}
bool check_all(function<bool(int)> func){
for(auto& c : candidates){
if(!func(c)) return false;
}
return true;
}
bool find(function<bool(int)> func){
for(auto& c : candidates){
if(func(c)) return true;
}
return false;
}
bool all_greater_than(int n){
return check_all([&n](int a){ return a > n; });
}
bool exists_greater_than(int n){
return find([&n](int a){ return a > n; });
}
bool all_less_than(int n){
return check_all([&n](int a){ return a < n; });
}
bool exists_less_than(int n){
return find([&n](int a){ return a < n; });
}
bool exists_greater_than(Score s){
for(auto& c2 : s.candidates){
if(exists_greater_than(c2)) return true;
}
return false;
}
};
内部パラメータcandidatesが得点の候補を保持するコンテナです。
コンストラクタでAが来た時にcandidatesに1と11両方がプッシュされます。
Score型同士の足し算はオペレータオーバーロードで実装しました。慣れていないのでご指摘あればお願いします。
得点の候補の[すべて/一つでも]整数と比較したいので以下のメソッドを実装しました。
all_less_than (候補すべてが整数nより小さい)
all_greater_than (候補全てが整数nより大きい)
exists_less_than (候補のうち一つでも整数nより小さい)
exists_greater_than (候補のうち一つでも整数nより大きい)
なお、比較だからといって比較演算子(< > <= >=)をオーバーロードして実装してはいけません。オペレータオーバーロードは演算の意味に誤解を招くときは使うべきでないからです。
また勝利判定用に「自身の得点候補があるスコア型の得点候補に対して一つでも大きな数字を持っているかをチェックする」関数を用意しました。
使用例
以下は手札のカードの合計を返す関数です。
Score TotalScore(vector<Card> hand){
Score total(0);
for(auto& c : hand){
total += c.score;
}
return total;
}
このようにAが1か11か外から考慮することなく演算ができます。
# あとがき
頑張って実装してみましたが、Aを引いた時しか例外が起きないのにそれを吸収するためにここまで拡張するのはやりすぎな気がしてきました。
でも面白かったので満足です。
ご指摘ご意見ありましたらコメントどうぞ。