日本だんでぃ協会
Japan Dandy Association

中の人プロフィール
だんでぃ
web拍手作った人
プログラマーでミュージシャンだと言い張って聞かない37才

おこんてんつ
  トップ
プログラミング (12)
デカ文字ジェネレータ
  音楽 (7)
  ポーカー (1)
  雑記 (1)

リンク
ろじっくぱらだいす
老舗のプログラム系テキストサイト。web拍手を思い付くきっかけをいただいたサイトです。ワタナベさんラヴ!

■ポーカーの役判定 数字の重なり編

2017/04/11 22:13 作成
はい、前回の続きです。

前回は、コードを組むのが簡単そうということで「フラッシュ」の判定プログラムを書きました。

引き続き他の役の判定プログラムも書いていくわけですが、
ここで今さらながらポーカーの役のおさらいをしておきましょう。

強い順に並べていくと、

・ロイヤルフラッシュ
・ストレートフラッシュ
・フォーカード
・フルハウス
・フラッシュ
・ストレート
・スリーカード
・ツーペア
・ワンペア
・ハイカード

となるわけですが、役の形を見てみると、
まず大きく以下の二つに分類できますね。

・同じ数字が複数枚あることで成立する役
  フォーカード
  フルハウス
  スリーカード
  ツーペア
  ワンペア

・それ以外
  ロイヤルフラッシュ
  ストレートフラッシュ
  フラッシュ
  ストレート
  ハイカード

こんな感じです。

お気付きの通り、10種類ある役の中で半分が
「同じ数字が複数枚あることで成立する役」なんですね。

ハイカードを役に含めず、ロイヤルフラッシュをただのナッツストレートフラッシュと考えるなら
大半の役がこの分類に入れられることになります。

ということで、フラッシュの次はこの辺の役判定をやっつけてしまいましょう。

プログラムの考え方としては、フラッシュの時にマークを数えたのと同じで、
対象となる複数のカードの数字をカウントしていって、最後に条件に当てはまるか調べる。
という方法をとりたいと思います。

まずは、一番簡単そうな「フォーカード」の判定を書いてみましょう。

こんな感じでしょうか。
function IsFourCard( 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"]]++;
	}

	//カウントし終わった数字を検証していって、4枚以上同じ数字があったらtrueをリターン
	for( var i=1; i<=13; i++ ){

		if( numbers[i]>=4 ){
			return true;
		}
	}

	//ここまで到達で4カードは成立していない
	return false;
}

1セットのトランプの中には同じ数字は4枚しかないので、
枚数の判定のところは「4枚以上あったら」じゃなくて「4枚あったら」でもいいんですが、
こうしておいた方が良い理由は後ほど解説します。

続いて、スリーカードの判定も書いてみましょう。
こんな感じになります。
function IsThreeCard( 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"]]++;
	}

	//カウントし終わった数字を検証していって、3枚以上同じ数字があったらtrueをリターン
	for( var i=1; i<=13; i++ ){

		if( numbers[i]>=3 ){
			return true;
		}
	}

	//ここまで到達で3カードは成立していない
	return false;
}

はい。
お気付きの方もいらっしゃると思いますが、
このコードは「同じ数字が3枚以上あったらスリーカード判定」になるので、
フォーカードやフルハウスが成立しているカードを渡しても、元気良く

「はい!スリーカードが成立しているであります!他の役の事は知りません!」

と、アホの子よろしく返事をしてしまいます。

一見バグっているプログラムに見えますが、ポーカーは「強い役から判定する」ということと、
麻雀のように「役の重複」はしないので、フォーカードやフルハウスが成立している場合、
スリーカードの判定まで処理が到達しないので、このプログラムで問題ないということになります。

どうしても気になる人は、関数の冒頭に
	if( IsFourCard( targetCards ) ){ return false; }
とか書いておけばスッキリすると思います。

さて、このノリでワンペアの処理も書いてしまいましょう。
こんな感じですね。
function IsOnePair( 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"]]++;
	}

	//カウントし終わった数字を検証していって、2枚以上同じ数字があったらtrueをリターン
	for( var i=1; i<=13; i++ ){

		if( numbers[i]>=2 ){
			return true;
		}
	}

	//ここまで到達でワンペアは成立していない
	return false;
}

この関数も同様に、フォーカードやスリーカード等が成立しているハンドでもtrueを返してしまいますが、
上記の理由により問題ないということになります。

さて、気付いた方もいると思いますが、この3つの関数は
	if( numbers[i]>=【ここの数字】 )
この1箇所しか違いがありません。

フォーカードなら4が、スリーカードなら3が、ワンペアなら2が入るだけで、
その他の部分は完全一致しています。

全く同じ処理をしている部分が大半で、違う部分はちょっとだけ。という関数が
3つもあるということになってしまいますね。
こういう状態のことを「冗長(じょうちょう)」といいます。

冗長なコードは、プログラム全体が長くなってしまったり、
仕様変更に弱いなどのメンテナンス性の低下を招いてしまうので
一つの関数にまとめてしまいましょう。

こんな感じになります。
function IsSameNumber( targetCards, sameCount ){ //sameCountで何枚以上ならtrueを返すのか渡せるようにします

	//数字を数える入れ物を用意
	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"]]++;
	}

	//カウントし終わった数字を検証していって、sameCount枚以上同じ数字があったらtrueをリターン
	for( var i=1; i<=13; i++ ){

		if( numbers[i]>=sameCount ){
			return true;
		}
	}

	//ここまで到達で対象の役は成立していない
	return false;
}

はい。
引数として、何枚以上同じ数字があったらtrueを返すのかを渡せるようにしてみました。

これで、「フォーカード」「スリーカード」「ワンペア」の3つの役の判定を
1つの関数でできるようになりました。

フォーカードを判定したいなら
if( IsSameNumber( targetCards, 4 ) ){
	console.log("フォーカードです");
}
みたいに書くことができるわけですね。


こんな感じで、同じような処理をしているコードを一つにまとめることを「汎用化」と言います。

汎用化のメリットとして、メンテナンス性の向上があります。
例えば(有り得ないことですが)、

「トランプにキングの上のゴッドを追加しよう!トランプの数字は1~14までにします!」

みたいな事が起きたときに、汎用化されているコードだったら1箇所の修正だけで済むわけです。

逆に、デメリットとして汎用化しすぎると
「パッと見で何の処理をしているのか分かりづらくなる」
ということがあります。

「IsFourCard()」とか「IsThreeCard()」なら何をする関数なのか名前からすぐ分かりますが、
「IsSameNumber()」だとイマイチ直感的じゃないですよね(僕の付けた関数名のセンスのせいもありますけど)

他にも、引数が増えすぎて使いづらくなってしまったり、
汎用性が高過ぎて逆に仕様変更に弱くなってしまったりすることもあるので、
この辺はプログラマのセンスの見せ所ですね。

今回、例として汎用化してみましたが、個人的な意見としては
ポーカーの役判定くらいのシンプルな処理で仕様変更の可能性も低いなら、
わざわざこんな汎用化はしなくてもいいんじゃないかな~と思います。


さて、長くなってしまったので今回はこの辺で!

ツーペアとフルハウスの判定は今回のよりもちょっと難しいので、
また次回にしたいと思います。


余談ですが、スリーカードやフォーカードの事を
「Three of a KIND」とか「Four of a KIND」とかって言うのちょっとカッコイイですよね。

「すりーかーどぉ」じゃなくて「スゥルィー オバ カイン」みたいな感じですね!(伝わりづらい)



前の記事 ポーカーの役判定プログラムを作ってみよう!
次の記事 ポーカーの役判定 ツーペア、フルハウス編
関連記事
ポーカーの役判定 最終回
ポーカーの役判定 ストレート編
ポーカーの役判定 ツーペア、フルハウス編
ポーカーの役判定 数字の重なり編
ポーカーの役判定プログラムを作ってみよう!

2017 日本だんでぃ協会 All Rights Reserved.