ポーカーゲーム作成Step2 ~役判定アルゴリズムの作成~
引き続きmodelパッケージ内で作業していきます。今回作成するのは手札の役を判定するアルゴリズムです。早速役判定を行うHandEvaluatorクラスを実装していきましょう。szpp_poker/model内にHandEvaluator.javaを作成してください。
HandEvaluatorのパラメータって?
HandEvaluatorは手札の役判定を行うクラスなのですから、フィールドとして「自分の手札」と「相手の手札」を持たせたいかもしれません。が、ポーカーにおいて手札は動的に変化します。一度HandEvaluatorをインスタンス化しても、そのときにコンストラクタに投げた手札は手札交換後にはもう使えません。今回は簡単のため手札交換は一回しか行わない仕様にしていますが、もし何回も交換できるように仕様変更したらどうでしょう?手札交換のたびに再度インスタンス化をするか、インスタンスの手札を書き換えなければいけなくなってしまいます。
このような事態を防ぐために、そもそもHandEvaluatorクラスにパラメータを持たせないという手法を取ります。HandEvaluatorクラスはメソッドだけを持ち、「引数として受け取った手札の役を判定する」という役割だけを持たせます。
基本的には、フィールドを持たないクラスはインスタンス化するべきではありません。今はまだ難しいかもしれませんが、インスタンス化のメリットは「クラスのフィールドをインスタンスごとに分離させられる」ことです。したがって、そもそもフィールドを持たないクラスに対してインスタンス化は利益がありません。
では、どうすればインスタンス化をせずに外部からHandEvaluatorのメソッドを呼び出せるのでしょう?
ここで役立つのが今まで散々おまじないと言ってきたstaticです。
static
staticを付与されたメソッドはクラスに属するメソッドになります。通常メソッドを呼び出すにはインスタンスを介する必要がありますが、staticを付与することでインスタンスの生成が不要になります。このようなメソッドのことを静的メソッドと呼びます。
staticメソッドはクラス名.メソッド名()の形式で呼び出すことができます。staticメソッドについてもっと知りたい人は↓のサイトがわかりやすいです。
【Java】staticメソッドと非staticメソッドの違いとよくあるエラーの解決方法
また、static属性はメソッドだけではなくフィールドにも付与することができます。(クラスには付与することができません)このようなフィールドを静的フィールド(静的変数)と呼びます。staticフィールドについて知りたい人は、例えば↓のサイトなどを見てみるといいかもしれません。
【Java】初心者による初心者のためのstaticのしくみ解説
staticメソッドevaluate()の実装
実際にstaticなメソッドであるevaluate()メソッドを作成していきましょう。メソッドにstatic属性を付与するのはとても簡単です。メソッド宣言時にstaticと追記するだけです。 HandEvaluator.java
package model;
import java.util.List;
public class HandEvaluator {
public static String evaluate(List<Card> hand) {
}
}評価する手札は引数として受け取る形式にします。こうすることで、自分の手札でも相手の手札でも同じようにメソッドを呼び出せるようになります。(もしフィールドとして「自分の手札」と「相手の手札」を持たせた場合、自分の手札はevaluateMyHand()、相手の手札はevaluateEnemyHand()みたいな感じで別々のメソッドを用意する必要が生まれてしまいます。)
手札にある数字を管理する
手札はカードが5個集まったリストとして扱えます。それぞれのカードは柄(mark)と数字(rank)の2つのフィールドを持っています。まずは「手札に各数字(A~K)のカードが何枚あるか」を管理しましょう。
public static String evaluate(List<Card> hand) {
// 手札が5枚じゃなければそもそも役の判定ができない
if (hand == null || hand.size() != 5) {
throw new IllegalArgumentException("手札は5枚である必要があります。");
}
// 手札に各数字(1~13)が何枚あるかをカウントする配列
// インデックスと数字を合わせるため、要素数は14(0は使わない)
// 例: 数字が2のカードが3枚ある → rankCounts[2] = 3
// 数字が13のカードが1枚ある → rankCounts[13] = 1
int[] rankCounts = new int[14];
// 手札に各数字が何枚あるかを計算する
for (int i = 0; i < hand.size(); i++) {
// 手札のカードを1枚取得
Card card = hand.get(i);
// 取得したカードの数字(rank)を取得
int rank = card.getRank();
// rankCountsのカウントを増やす
rankCounts[rank]++;
}
}ペア、スリーカード、フォーカードの数をカウントする処理
では、rankCounts配列を使用して、evaluate()メソッド内に「手札にペア、スリーカード、フォーカードがいくつあるかを判定する処理」を書いてみましょう。
// ペアの数をカウントする変数
int pair = 0;
// スリーカードの数をカウントする変数
int threes = 0;
// フォーカードの数をカウントする変数
int fours = 0;
// ペア、スリーカード、フォーカードを判定し、カウントを増やす処理を頑張って書くヒント
手札にある数字 i が何枚あるかはrankCounts[i]にアクセスすることで取得できます。例えば、rankCounts[i]が2ならばそれはペアになりそうです。
ストレートとフラッシュを判定する処理
続いてストレートとフラッシュを判定していきましょう。ペアやスリーカードのときと違って少々処理が複雑になりそうなので、メソッドとして切り出してしまいましょう。
boolean isStraight = checkStraight();
boolean isFlush = checkFlush();ストレート・フラッシュならisStraight・isFlushがtrueになるようにcheckStraight()・checkFlush()メソッドを作成してください。がんば!
注意!!!上記のサンプルコードはあくまでイメージです。 実際にストレート・フラッシュを判定するためにはそれぞれ正しい引数を渡して上げる必要があります! サンプルコードをコピペしただけでは動きません! 引数をどんな型にすれば楽にかけるかなども考えて実装してみてください。
役の判定を行って返り値を返す
ここまでできたら実際に役の判定を行いましょう。
// 役の判定を行う
if (isStraight && isFlush && rankCounts[1] == 1 && rankCounts[13] == 1) {
return "ロイヤルストレートフラッシュ";
}
if (isStraight && isFlush) {
return "ストレートフラッシュ";
}
if (fours == 1) {
return "フォーカード";
}
if (pairs == 1 && threes == 1) {
return "フルハウス";
}
if (isFlush) {
return "フラッシュ";
}
if (isStraight) {
return "ストレート";
}
if (threes == 1) {
return "スリーカード";
}
if (pairs == 2) {
return "ツーペア";
}
if (pairs == 1) {
return "ワンペア";
}
return "ハイカード";テスト実行
Main.javaの内容を書き換え、再度実行してみましょう。作成したメソッドは正しく動作しているでしょうか?
import java.util.ArrayList;
import java.util.List;
import model.Card;
import model.Deck;
import model.HandEvaluator;
public class Main {
public static void main(String[] args) {
// テスト1: 意図的にストレートを作ってみる
List<Card> testHand = new ArrayList<>();
testHand.add(new Card("♠", 3));
testHand.add(new Card("♦", 4));
testHand.add(new Card("♥", 5));
testHand.add(new Card("♦", 6));
testHand.add(new Card("♣", 7));
System.out.println("testHandの手札: " + testHand);
String rank = HandEvaluator.evaluate(testHand);
System.out.println("判定結果: " + rank);
System.out.println();
// テスト2: 意図的にフラッシュを作ってみる
List<Card> testHand2 = new ArrayList<>();
testHand2.add(new Card("♣", 4));
testHand2.add(new Card("♣", 2));
testHand2.add(new Card("♣", 10));
testHand2.add(new Card("♣", 13));
testHand2.add(new Card("♣", 1));
System.out.println("testHand2の手札: " + testHand2);
String rank2 = HandEvaluator.evaluate(testHand2);
System.out.println("判定結果: " + rank2);
System.out.println();
// テスト3: ランダムに5枚引いてみる
Deck deck = new Deck();
List<Card> randomHand = new ArrayList<>();
for (int i = 0; i < 5; i++) {
randomHand.add(deck.draw());
}
System.out.println("randomHandの手札: " + randomHand);
String rank3 = HandEvaluator.evaluate(randomHand);
System.out.println("判定結果: " + rank3);
}
}実装例
HandEvaluator.java
package model;
import java.util.List;
public class HandEvaluator {
public static String evaluate(List<Card> hand) {
if (hand == null || hand.size() != 5) {
throw new IllegalArgumentException("手札は5枚である必要があります。");
}
// 手札に各数字(1~13)が何枚あるかをカウントする配列
// インデックスと数字を合わせるため、要素数は14(0は使わない)
// 例: 数字が2のカードが3枚ある → rankCounts[2] = 3
// 数字が13のカードが1枚ある → rankCounts[13] = 1
int[] rankCounts = new int[14];
// 手札に各数字が何枚あるかを計算する
for (int i = 0; i < hand.size(); i++) {
// 手札のカードを1枚取得
Card card = hand.get(i);
// 取得したカードの数字(rank)を取得
int rank = card.getRank();
// rankCountsのカウントを増やす
rankCounts[rank]++;
}
int pairs = 0;
int threes = 0;
int fours = 0;
for (int i = 1; i <= 13; i++) {
if (rankCounts[i] == 2) {
pairs++;
} else if (rankCounts[i] == 3) {
threes++;
} else if (rankCounts[i] == 4) {
fours++;
}
}
boolean isStraight = checkStraight(rankCounts);
boolean isFlush = checkFlush(hand);
// 役の判定を行う
if (isStraight && isFlush && rankCounts[1] == 1 && rankCounts[13] == 1) {
return "ロイヤルストレートフラッシュ";
}
if (isStraight && isFlush) {
return "ストレートフラッシュ";
}
if (fours == 1) {
return "フォーカード";
}
if (pairs == 1 && threes == 1) {
return "フルハウス";
}
if (isFlush) {
return "フラッシュ";
}
if (isStraight) {
return "ストレート";
}
if (threes == 1) {
return "スリーカード";
}
if (pairs == 2) {
return "ツーペア";
}
if (pairs == 1) {
return "ワンペア";
}
return "ハイカード";
}
private static boolean checkStraight(int[] rankCounts) {
int continueCount = 0;
for (int i = 1; i <= 13; i++) {
if (rankCounts[i] == 1) {
continueCount++;
} else {
continueCount = 0;
}
if (continueCount == 5) {
return true;
}
}
// 特別なストレート(10,J,Q,K,A)の判定
if (rankCounts[1] > 0 && rankCounts[10] > 0 && rankCounts[11] > 0 &&
rankCounts[12] > 0 && rankCounts[13] > 0) {
return true;
}
return false;
}
private static boolean checkFlush(List<Card> hand) {
// はじめのカードを取得
Card firstCard = hand.get(0);
String firstMark = firstCard.getMark();
for (int i = 1; i < hand.size(); i++) {
Card card = hand.get(i);
String mark = card.getMark();
if (!mark.equals(firstMark)) {
return false;
}
}
return true;
}
}