解答例 - j2.lesson04.PokerHand

package j2.lesson04;

import j2.lesson04.card.Card;

/**
 * 課題1704 - 解答例 (2/2).
 * ポーカーの手札を表す.
 * @author arakawa
 * @version $Id: PokerHand_java.rps,v 1.1 2006/03/06 12:56:14 java2005 Exp $
 */
public class PokerHand {

    /** 役なし. */
    private static final int NONE = 0;

    /** ロイヤルストレートフラッシュ. */
    private static final int ROYAL_FLUSH = 1;

    /** ストレートフラッシュ. */
    private static final int STRAIGHT_FLUSH = 2;

    /** フォーカード. */
    private static final int FOUR_OF_KIND = 3;

    /** フルハウス. */
    private static final int FULL_HOUSE = 4;

    /** フラッシュ. */
    private static final int FLUSH = 5;

    /** ストレート. */
    private static final int STRAIGHT = 6;

    /** スリーカード. */
    private static final int THREE_OF_KIND = 7;

    /** ツーペア. */
    private static final int TWO_PAIR = 8;

    /** ワンペア. */
    private static final int ONE_PAIR = 9;

    /** 役の名前. */
    private static final String[] RANK_NAME = {
        "なし", "ロイヤルストレートフラッシュ", "ストレートフラッシュ", "フォーカード", "フルハウス",
        "フラッシュ", "ストレート", "スリーカード", "ツーペア", "ワンペア", };

    /**
     * 実際の手札。
     */
    private final Card[] cards;
    
    /**
     * 役を表す整数。
     * @see #ROYAL_FLUSH
     * @see #STRAIGHT_FLUSH
     * @see #FOUR_OF_KIND
     * @see #FULL_HOUSE
     * @see #FLUSH
     * @see #STRAIGHT
     * @see #THREE_OF_KIND
     * @see #TWO_PAIR
     * @see #ONE_PAIR
     * @see #NONE
     */
    private final int rankOfHand;

    /**
     * 手札を生成する.
     * @param c1 1番目のカード
     * @param c2 2番目のカード
     * @param c3 3番目のカード
     * @param c4 4番目のカード
     * @param c5 5番目のカード
     */
    public PokerHand(Card c1, Card c2, Card c3, Card c4, Card c5) {
        super();
        this.cards = new Card[] { c1, c2, c3, c4, c5 };
        this.rankOfHand = checkRank();
    }
    
    /**
     * 指定した位置のカードを指定したカードと交換する。
     * 位置は <code>0..4</code> の範囲内で指定する必要がある。
     * @param at 交換する位置
     * @param card 交換するカード
     * @return 指定した位置にあった古いカード
     */
    public Card change(int at, Card card) {
        Card old = this.cards[at];
        this.cards[at] = card;
        return old;
    }
    
    /**
     * 指定した位置にあるカードを取得する。
     * 位置は <code>0..4</code> の範囲内で指定する必要がある。
     * @param at 取得するカードの位置
     * @return カード
     */
    public Card get(int at) {
        return this.cards[at];
    }
    
    /**
     * 現在の手札の役を文字列として取得する。
     * @return 手札の役を表す文字列
     */
    public String getRank() {
        return RANK_NAME[this.rankOfHand];
    }

    /**
     * 手札の役を調べる。
     * @return 手札の役を表す数値
     * @see #ROYAL_FLUSH
     * @see #STRAIGHT_FLUSH
     * @see #FOUR_OF_KIND
     * @see #FULL_HOUSE
     * @see #FLUSH
     * @see #STRAIGHT
     * @see #THREE_OF_KIND
     * @see #TWO_PAIR
     * @see #ONE_PAIR
     * @see #NONE
     */
    private int checkRank() {
        int[] ranks = new int[13];
        // ランクごとの出現枚数を数える
        for (int i = 0; i < this.cards.length; i++) {
            ranks[this.cards[i].getRankAsInt() - 1]++;
        }

        boolean flush = hasFlush();
        boolean straight = hasStraight(ranks);
        boolean four = countSameRank(4, ranks) > 0;
        boolean three = countSameRank(3, ranks) > 0;
        int pairs = countSameRank(2, ranks);

        // 強い順に調べる
        if (hasRoyalRanks(ranks) && flush) {
            return ROYAL_FLUSH;
        }
        else if (straight && flush) {
            return STRAIGHT_FLUSH;
        }
        else if (four) {
            return FOUR_OF_KIND;
        }
        else if (three && pairs == 1) {
            return FULL_HOUSE;
        }
        else if (flush) {
            return FLUSH;
        }
        else if (straight) {
            return STRAIGHT;
        }
        else if (three) {
            return THREE_OF_KIND;
        }
        else if (pairs == 2) {
            return TWO_PAIR;
        }
        else if (pairs == 1) {
            return ONE_PAIR;
        }
        else {
            return NONE;
        }
    }

    /**
     * 現在の手札が A, 10, J, Q, K のランクからなるか調べる。
     * @param ranks ランクごとの出現枚数
     * @return 現在の手札が A, 10, J, Q, K のランクからなるならば <code>true</code>
     */
    private boolean hasRoyalRanks(int[] ranks) {
        return ranks[0] == 1 && ranks[9] == 1 && ranks[10] == 1 && ranks[11] == 1 && ranks[12] == 1;
    }

    /**
     * 指定した枚数の同じランクを持つカードが何セットあるか調べる。
     * @param n 数える枚数
     * @param ranks ランクごとの出現枚数
     * @return 上記のようなセットの個数
     */
    private int countSameRank(int n, int[] ranks) {
        int count = 0;
        for (int i = 0; i < ranks.length; i++) {
            if (ranks[i] == n) {
                count++;
            }
        }
        return count;
    }

    /**
     * カードのランクが5枚連続しているかどうか調べる。
     * @param ranks ランクごとの出現枚数
     * @return ランクが5枚連続していれば <code>true</code>
     */
    private boolean hasStraight(int[] ranks) {
        // カードが連続した数
        int count = 0;
        for (int i = 0; i < ranks.length; i++) {
            if (ranks[i] == 0) {
                // 一度でも 1 を見ていたら、ランクの連続はここで終わり
                if (count >= 1) {
                    break;
                }
            }
            else if (ranks[i] == 1) {
                // 1 を連続して見た回数を増やす
                count++;
            }
            else {
                // ペアがあればストレートにはならない
                return false;
            }
        }

        // 5枚連続していればストレート
        if (count == 5) {
            return true;
        }
        else {
            return false;
        }
    }
    
    /**
     * カードのマークが全て同じかどうか調べる。
     * @return 全て同じならば <code>true</code>
     */
    private boolean hasFlush() {
        // 最初のカードと一枚でもマークが違えば何もしない
        int firstSuite = this.cards[0].getSuiteAsInt();
        for (int i = 1; i < this.cards.length; i++) {
            if (firstSuite != this.cards[i].getSuiteAsInt()) {
                return false;
            }
        }
        // 全て同じならフラッシュ
        return true;
    }
}