はい、ではストレートの判定プログラムを書いてみましょう。
前もって言っておきますと、ストレートの判定は
全ポーカーの役の中で2番目に難しい判定になります。
1番難しいのはストレートフラッシュなので、
今回解説するストレートは今まで解説した中で一番難しいということになりますね。
さて、テキサスホールデムでは7枚のカードから判定することになるので、
7枚のカードのデータを渡したらストレートかどうかを
trueかfalseで返してくれる関数を作るわけなんですけど、
まず考えなければいけないのは、
どういうアルゴリズムでストレートの判定をするのかになります。
僕の私見ですが、これを自力で考えられない人はプログラマに向いていないと思います。
もしもプログラマの方やプログラマを目指している方がこの記事を読んでいたら、
自分だったらどういうコードを書くかな~というのを想像してみると良いかも知れません。
んでは、実際に考えていってみましょう。
ストレートという役は
「1、2、3、4、5のように連番で5枚のカードを持っていたら成立する役」なのですが、
これをコードに落とし込むのは言葉で表現するよりもずっとややこしい事になります。
僕もアレコレと考えてみたのですが、ストレートの判定には
「全手探索」が一番良いと思います。
「全手探索」とは、「可能性のある全てのパターンを検証する」手法で、
オセロや将棋、チェスなどのボードゲームのAIを作るときに用いられたりする考え方なのですが、
「難しい考え方でコードを組むより、全ての可能性を検証しちゃった方が早くね」
という、ある意味ではアルゴリズムを考えることを放棄しているとも言えるやりかたです。
実際問題、将棋やチェスなどでは「全手」のパターンが多すぎて、
現代のコンピュータでも現実的ではないそうです。
将棋やチェスに比べるとシンプルそうなオセロでも全手探索はまだ実現できてないとか。
んで、ストレートの判定に戻りますけど、ポーカーの役としてのストレートは
1~5のストレート
2~6のストレート
3~7のストレート
4~8のストレート
5~9のストレート
6~10のストレート
7~11のストレート
8~12のストレート
9~13のストレート
10~13、1のストレート
の10種類しかありません。
たったの10種類なら全手探索できそうですね。
全手探索を組む第一歩として、全手のうちのどれか1つを実際に組んでみましょう。
こんな感じになります。
function IsStraight( targetCards ){
//数字を数える入れ物を用意
var numbers = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0 };
//引数として渡されたカードの数字をカウントしていく
for( var i=0; i<targetCards.length; i++ ){
numbers[targetCards[i]["Number"]]++;
}
//1~5のそれぞれが1枚以上あったらストレート成立
if( numbers[1]>=1 && numbers[2]>=1 && numbers[3]>=1 && numbers[4]>=1 && numbers[5]>=1 ){
return true;
}
//ここまで到達でストレートは成立していない
return false;
}
これで、1~5のストレートを判定することができるようになりました。
あとは、全パターンの判定を羅列してしまいましょう。
こうなりますね。
function IsStraight( targetCards ){
//数字を数える入れ物を用意
var numbers = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0 };
//引数として渡されたカードの数字をカウントしていく
for( var i=0; i<targetCards.length; i++ ){
numbers[targetCards[i]["Number"]]++;
}
//1~5のそれぞれが1枚以上あったらストレート成立
if( numbers[1]>=1 && numbers[2]>=1 && numbers[3]>=1 && numbers[4]>=1 && numbers[5]>=1 ){
return true;
}
//2~6のそれぞれが1枚以上あったらストレート成立
if( numbers[2]>=1 && numbers[3]>=1 && numbers[4]>=1 && numbers[5]>=1 && numbers[6]>=1 ){
return true;
}
//3~7のそれぞれが1枚以上あったらストレート成立
if( numbers[3]>=1 && numbers[4]>=1 && numbers[5]>=1 && numbers[6]>=1 && numbers[7]>=1 ){
return true;
}
//4~8のそれぞれが1枚以上あったらストレート成立
if( numbers[4]>=1 && numbers[5]>=1 && numbers[6]>=1 && numbers[7]>=1 && numbers[8]>=1 ){
return true;
}
//5~9のそれぞれが1枚以上あったらストレート成立
if( numbers[5]>=1 && numbers[6]>=1 && numbers[7]>=1 && numbers[8]>=1 && numbers[9]>=1 ){
return true;
}
//6~10のそれぞれが1枚以上あったらストレート成立
if( numbers[6]>=1 && numbers[7]>=1 && numbers[8]>=1 && numbers[9]>=1 && numbers[10]>=1 ){
return true;
}
//7~11のそれぞれが1枚以上あったらストレート成立
if( numbers[7]>=1 && numbers[8]>=1 && numbers[9]>=1 && numbers[10]>=1 && numbers[11]>=1 ){
return true;
}
//8~12のそれぞれが1枚以上あったらストレート成立
if( numbers[8]>=1 && numbers[9]>=1 && numbers[10]>=1 && numbers[11]>=1 && numbers[12]>=1 ){
return true;
}
//9~13のそれぞれが1枚以上あったらストレート成立
if( numbers[9]>=1 && numbers[10]>=1 && numbers[11]>=1 && numbers[12]>=1 && numbers[13]>=1 ){
return true;
}
//10~13と1のそれぞれが1枚以上あったらストレート成立
if( numbers[10]>=1 && numbers[11]>=1 && numbers[12]>=1 && numbers[13]>=1 && numbers[1]>=1 ){
return true;
}
//ここまで到達でストレートは成立していない
return false;
}
はい、これで10パターン全てのストレートが判定できるようになりました。
いや~、めでたしめでたし。
いや、これでも要件は満たしているんですけど、ちょっとコードが汚いですね。
似たような処理が連続して、すごく冗長です。
せっかくなので、もっとコードをスマートにしましょう。
こんな感じになります。
function IsStraight( targetCards ){
//数字を数える入れ物を用意
var numbers = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0 };
//引数として渡されたカードの数字をカウントしていく
for( var i=0; i<targetCards.length; i++ ){
numbers[targetCards[i]["Number"]]++;
}
//1から始まるストレート~9から始まるストレートを判定する
for( var i=1; i<=9; i++ ){
if( numbers[i]>=1 && numbers[i+1]>=1 && numbers[i+2]>=1 && numbers[i+3]>=1 && numbers[i+4]>=1 ){
return true;
}
}
//10から始まるストレートのみ1(エース)が含まれるため別で判定
if( numbers[10]>=1 && numbers[11]>=1 && numbers[12]>=1 && numbers[13]>=1 && numbers[1]>=1 ){
return true;
}
//ここまで到達でストレートは成立していない
return false;
}
こんな感じで、forループを使用することによって
「1から始まるストレート」から「9から始まるストレート」の処理をまとめてみました。
「10から始まるストレート」のみ「10、11、12、13、1の5枚」と、扱いがちょっと特殊なので
別判定にしています。
これでもだいぶコードが短くなったのですが、
せっかくですから「10から始まるストレート」も、もうちょっと鮮やかに判定してしまいましょう。
こんな感じになりますね。
function IsStraight( targetCards ){
//数字を数える入れ物を用意
var numbers = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0, 14:0 };
//引数として渡されたカードの数字をカウントしていく
for( var i=0; i<targetCards.length; i++ ){
numbers[targetCards[i]["Number"]]++;
//1(エース)のみ、14番目の数字としてもカウント
if( targetCards[i]["Number"]==1 ){ numbers[14]++; }
}
//1から始まるストレート~10から始まるストレートを判定する
for( var i=1; i<=10; i++ ){
if( numbers[i]>=1 && numbers[i+1]>=1 && numbers[i+2]>=1 && numbers[i+3]>=1 && numbers[i+4]>=1 ){
return true;
}
}
//ここまで到達でストレートは成立していない
return false;
}
はい。
1(エース)を「数字の1」と「数字の14」として扱うことで、
10から始まるストレートを「10、11、12、13、14のストレート」として判定しています。
ひとまず、僕のプログラミング力では
これが最も鮮やかにストレート判定ができるコードになります。
これまた私見ですが、実際の開発の現場では
2番目の例で挙げたいわゆる
if文無双のコードでも良いと思います。
実際に僕の部下のプログラマがこれで提出してきても、
「ちょっとコードが汚いね~」とか言いつつも、これで良しとすると思います。
僕は、プログラマとして重要なのは
・要件を満たしたプログラムを
・バグ無く
・速く納品すること
の3点のみと考えているので、
「if文無双なら30分でできるけど、コードを整理していたら丸1日掛かってしまった」
というような事態を引き起こしてしまうなら、コードの整理なんてやる必要ありません。
ただ、上に挙げた3点の次に
・プログラムのメンテナンス性を高く保つ
ということもそれなりに重要視しないといけないので、
あまり時間をかけずにコードを綺麗にできるならやっておいた方が良いでしょう。
コードを整理するのにどれくらい時間を掛けて良いかとかは
現場のノリにもよってくると思いますので、
その辺は上司と相談しながらやると良いでしょう。
それと、僕が最後に挙げた例では1(エース)を14番目のカードとして扱っていますが、
「トランプに存在しない14番目のカードを、さも存在するかのように扱うコード」
ということに対して拒絶反応を示す人も居ると思うので、
その辺も「そういうことをして良いのか」の相談は大事だと思います。
長くなってしまいましたが、今回はストレートの役判定を通して、
全手探索の考え方や、コードの整理のしかた、
ちょっとトリッキーなコードの書き方を解説してみました。
次回は、一番難しいストレートフラッシュの判定を解説したいと思います。
ロイヤルフラッシュもストレートフラッシュの一部なので、
もしかしたら次回で「ポーカーの役判定講座」は完結するかもです。
お楽しみに~♪
2025 日本だんでぃ協会 All Rights Reserved.