周末挑战#2-扑克手评估


尽管我知道这样做会带来更多工作上的麻烦,但我还是很早就决定支持野蛮的小丑。我还想支持大量的纸牌,例如,如果您有7张纸牌并选择其中最好的5张纸牌。当然,可以使用组合技术(7 nCr 5 = 35种组合),但是我想要一个更好的方法。

所以要检查可能的扑克手,我真的不想做别人犯过同样的错误。和往常一样,我也倾向于使用灵活的解决方案。很快,策略模式警报就浮现在我的脑海。

我花了很多时间编写测试来确保它能正常工作。现在,我相信它可以正常工作(或者至少是我想要的方式)。

类概述




定义一张ace的值是什么(在这里不是很有用,但在其他卡片项目中非常有用)

AceValue-心脏,黑桃,钻石,棍棒的枚举+通配符和其他趣味的枚举

Suite-包含ClassicCard和等级(Suite)的类。还包含不同等级的常量

int-负责确定给定PokerHandEval数组的PokerHandResult的类。

ClassicCard-可用的标准扑克手类型的枚举

PokerHandAnalyze-持有以下类别的比较类扑克手的结果。包括PokerHandEval +参数(“满屋子是什么?”)+“踢球者”(肯定是6s对,但是您其他最高的牌是什么?)以下三个类实现的策略模式



PokerHandType-寻找配对,两对,三种,四种和全屋的策略

PokerHandResult-寻找直系的策略

/> PokerHandType-查找同花顺,同花顺和皇家同花顺的策略-使用PokerHandResultProducer确定同花顺。 >由于我以前在Java中一直使用卡片,所以我认为周末挑战赛的结果会在以后的某个时候派上用场,所以我决定再次使用Java,并使用已经拥有的一些代码。 >
ClassicCard.java

public class ClassicCard {

    private final Suite suite;
    private final int rank;

    public static final int RANK_ACE_LOW = 1;
    public static final int RANK_2 = 2;
    public static final int RANK_3 = 3;
    public static final int RANK_4 = 4;
    public static final int RANK_5 = 5;
    public static final int RANK_6 = 6;
    public static final int RANK_7 = 7;
    public static final int RANK_8 = 8;
    public static final int RANK_9 = 9;
    public static final int RANK_10 = 10;
    public static final int RANK_JACK = 11;
    public static final int RANK_QUEEN = 12;
    public static final int RANK_KING = 13;
    public static final int RANK_ACE_HIGH = 14;
    public static final int RANK_WILDCARD = 20;

    public ClassicCard(Suite suite, int rank) {
        if (suite == null)
            throw new NullPointerException("Suite cannot be null");
        if (!suite.isWildcard() && rank == RANK_WILDCARD)
            throw new IllegalArgumentException("Rank cannot be RANK_WILDCARD when suite is " + suite);
        this.suite = suite;
        this.rank = rank;
    }

    public int getRank() {
        return rank;
    }
    public int getRankWithAceValue(AceValue aceValue) {
        if (isAce())
            return aceValue.getAceValue();
        return rank;
    }
    public boolean isAce() {
        return this.rank == RANK_ACE_LOW || this.rank == RANK_ACE_HIGH;
    }
    public boolean isWildcard() {
        return suite.isWildcard();
    }
    public Suite getSuite() {
        return suite;
    }
}


AceValue.java

public enum AceValue {
    LOW(ClassicCard.RANK_ACE_LOW), HIGH(ClassicCard.RANK_ACE_HIGH);

    private final int aceValue;
    private final int minRank;
    private final int maxRank;
    private final int[] ranks;

    private AceValue(int value) {
        this.aceValue = value;
        this.minRank = Math.min(2, getAceValue());
        this.maxRank = Math.max(ClassicCard.RANK_KING, getAceValue());
        this.ranks = new int[52 / 4];
        for (int i = 0; i < ranks.length; i++)
            ranks[i] = this.minRank + i;
    }

    public int getMaxRank() {
        return maxRank;
    }
    public int getMinRank() {
        return minRank;
    }
    public int getAceValue() {
        return this.aceValue;
    }

    public int[] getRanks() {
        return Arrays.copyOf(this.ranks, this.ranks.length);
    }
}



public enum Suite {

    SPADES, HEARTS, DIAMONDS, CLUBS, EXTRA;

    public boolean isBlack() {
        return this.ordinal() % 2 == 0 && !isWildcard();
    }
    public boolean isWildcard() {
        return this == EXTRA;
    }
    public boolean isRed() {
        return !isBlack() && !isWildcard();
    }
    public static int suiteCount(boolean includingWildcards) {
        int i = 0;
        for (Suite suite : Suite.values()) {
            if (!suite.isWildcard() || includingWildcards) {
                ++i;
            }
        }
        return i;
    }
}


代码

总长度:8个文件中的309行代码(不包括注释和空格)。

PokerFlush.java

/**
 * Checks for FLUSH, ROYAL_FLUSH and STRAIGHT_FLUSH. Depends on {@link PokerStraight} for the straight analyze.
 */
public class PokerFlush implements PokerHandResultProducer {

    private final PokerHandResultProducer straight = new PokerStraight();

    @Override
    public PokerHandResult resultFor(PokerHandAnalyze analyze) {
        List<PokerHandResult> results = new ArrayList<PokerHandResult>();
        for (Suite suite : Suite.values()) {
            if (suite.isWildcard())
                continue;

            PokerHandAnalyze suiteHand = analyze.filterBySuite(suite);
            if (suiteHand.size() < HAND_SIZE)
                continue; // Not enough cards to make a complete hand

            // We have a complete hand, now let's create a HandResult for it.
            PokerHandResult straightResult = straight.resultFor(suiteHand);
            if (straightResult != null) {
                PokerHandType type = straightResult.getPrimaryRank() == AceValue.HIGH.getAceValue() ? PokerHandType.ROYAL_FLUSH : PokerHandType.STRAIGHT_FLUSH;
                results.add(new PokerHandResult(type, straightResult.getPrimaryRank(), 0, null)); // We have a straight so we don't need to provide any kickers.
            }
            else results.add(new PokerHandResult(PokerHandType.FLUSH, 0, 0, suiteHand.getCards()));
        }
        if (results.isEmpty())
            return null;

        return PokerHandResult.returnBest(results);
    }

}


PokerHandAnalyze.java

/**
 * A helper class to analyze ranks and suits for an array of {@link ClassicCard}s. Create new using the static method {@link #analyze(ClassicCard...)}
 */
public class PokerHandAnalyze {

    private final int[] ranks = new int[ClassicCard.RANK_ACE_HIGH];
    private final int[] suites = new int[Suite.values().length];
    private final ClassicCard[] cards;
    private int wildcards;

    private PokerHandAnalyze(ClassicCard[] cards2) {
        this.cards = Arrays.copyOf(cards2, cards2.length);
    }
    /**
     * Create a new instance and analyze the provided cards
     * @param cards The cards to analyze
     * @return Organized analyze of the provided cards
     */
    public static PokerHandAnalyze analyze(ClassicCard... cards) {
        PokerHandAnalyze hand = new PokerHandAnalyze(cards);
        for (ClassicCard card : cards) {
            if (card.isWildcard()) {
                hand.wildcards++;
            }
            else if (card.isAce()) {
                hand.ranks[AceValue.HIGH.getAceValue() - 1]++;
                hand.ranks[AceValue.LOW.getAceValue() - 1]++;
            }
            else hand.ranks[card.getRank() - 1]++;

            hand.suites[card.getSuite().ordinal()]++;
        }
        return hand;
    }

    public int[] getRanks() {
        return ranks;
    }
    public int getWildcards() {
        return wildcards;
    }
    public ClassicCard[] getCards() {
        return cards;
    }
    public int size() {
        return cards.length;
    }
    /**
     * Create a sub-analyze which only includes wildcards and the specified suite. Useful to check for the FLUSH {@link PokerHandType}
     * @param suite The suite to filter by
     * @return A new analyze object
     */
    public PokerHandAnalyze filterBySuite(Suite suite) {
        List<ClassicCard> cards = new ArrayList<ClassicCard>();
        for (ClassicCard card : this.cards) {
            if (card.isWildcard() || card.getSuite().equals(suite)) {
                cards.add(card);
            }
        }
        return analyze(cards.toArray(new ClassicCard[cards.size()]));
    }
}


PokerHandEval.java

/**
 * Class to analyze poker hands by using a collection of {@link PokerHandResultProducer}s and return a {@link PokerHandResult}
 */
public class PokerHandEval {
    public void addTest(PokerHandResultProducer test) {
        this.tests.add(test);
    }

    private final List<PokerHandResultProducer> tests = new ArrayList<PokerHandResultProducer>();

    private PokerHandResult evaluate(PokerHandAnalyze analyze) {
        if (tests.isEmpty())
            throw new IllegalStateException("No PokerHandResultProducers added.");
        List<PokerHandResult> results = new ArrayList<PokerHandResult>();

        for (PokerHandResultProducer test : tests) {
            PokerHandResult result = test.resultFor(analyze);
            if (result != null)
                results.add(result);
        }
        return PokerHandResult.returnBest(results);
    }

    /**
     * Test a bunch of cards and return the best matching 5-card {@link PokerHandResult}
     * @param cards The cards to test
     * @return The best matching 5-card Poker Hand
     */
    public PokerHandResult test(ClassicCard... cards) {
        return evaluate(PokerHandAnalyze.analyze(cards));
    }

    /**
     * Factory method to create an evaluator for the default poker hand types.
     * @return
     */
    public static PokerHandEval defaultEvaluator() {
        PokerHandEval eval = new PokerHandEval();
        eval.addTest(new PokerPair());
        eval.addTest(new PokerStraight());
        eval.addTest(new PokerFlush());
        return eval;
    }
}


PokerHandResul t.java

/**
 * Data for a found poker hand. Provides data for the type of poker hand, the primary rank and secondary rank, and kickers. Including methods for sorting. Also implements hashCode and equals.
 */
public class PokerHandResult implements Comparable<PokerHandResult> {
    private final PokerHandType type;
    private final int primaryRank;
    private final int secondaryRank;
    private final int[] kickers;

    public PokerHandResult(PokerHandType type, int primaryRank, int secondaryRank, ClassicCard[] cards) {
        this(type, primaryRank, secondaryRank, cards, PokerHandResultProducer.HAND_SIZE);
    }
    public PokerHandResult(PokerHandType type, int primaryRank, int secondaryRank, ClassicCard[] cards, int numKickers) {
        this.type = type;
        this.primaryRank = primaryRank;
        this.secondaryRank = secondaryRank;
        this.kickers = kickers(cards, new int[]{ primaryRank, secondaryRank }, numKickers);
        Arrays.sort(this.kickers);
    }


    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        result = prime * result + primaryRank;
        result = prime * result + secondaryRank;
        result = prime * result + Arrays.hashCode(kickers);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof PokerHandResult))
            return false;
        PokerHandResult other = (PokerHandResult) obj;
        if (type != other.type)
            return false;
        if (primaryRank != other.primaryRank)
            return false;
        if (secondaryRank != other.secondaryRank)
            return false;
        if (!Arrays.equals(kickers, other.kickers))
            return false;
        return true;
    }

    private static int compareKickers(int[] sorted1, int[] sorted2) {
        int index1 = sorted1.length - 1;
        int index2 = sorted2.length - 1;
        int compare = 0;

        while (compare == 0 && index1 >= 0 && index2 >= 0) {
            // If one of them is bigger than another we will stop comparing, so decreasing both indexes is perfectly OK.
            compare = Integer.compare(sorted1[index1--], sorted2[index2--]);
        }
        return compare;
    }

    @Override
    public int compareTo(PokerHandResult other) {
        // compare order: HandType, primary rank (int), secondary (used for two pair and full house), kickers
        int compare = this.type.compareTo(other.type);
        if (compare == 0)
            compare = Integer.compare(this.primaryRank, other.primaryRank);
        if (compare == 0)
            compare = Integer.compare(this.secondaryRank, other.secondaryRank);
        if (compare == 0)
            compare = compareKickers(this.kickers, other.kickers);
        return compare;
    }
    public PokerHandType getType() {
        return type;
    }
    /**
     * Return the best {@link PokerHandResult} of a list of results. The method first orders the list and then returns the last result.
     * @param results A list of PokerHandResults
     * @return The best result from the list
     */
    public static PokerHandResult returnBest(List<PokerHandResult> results) {
        if (results.isEmpty())
            return null;
        Collections.sort(results);
        return results.get(results.size() - 1);
    }

    /**
     * Create an integer array of "kickers", to separate FOUR_OF_A_KIND with Ace-kicker vs. King-kicker
     * @param cards The cards in your hand. If null, an empty array will be returned
     * @param skip Ranks that will be skipped (for example, if you have a pair of 4s then you can skip those 4s)
     * @param count How many kickers that should be included. This should ideally be 5 - number of cards required for the {@link PokerHandType} the kickers are provided for
     * @return An array of the ranks that will be used as kickers. Wildcards and the ranks in the skip array are excluded
     */
    private static int[] kickers(ClassicCard[] cards, int[] skip, int count) {
        if (cards == null)
            return new int[]{};
        int[] result = new int[cards.length];
        Arrays.sort(skip);
        for (int i = 0; i < cards.length; i++) {
            int rank = cards[i].getRankWithAceValue(AceValue.HIGH);
            // Check if we should skip this rank in the kicker-data.
            if (cards[i].isWildcard() || Arrays.binarySearch(skip, rank) >= 0)
                continue;
            result[i] = rank;
        }
        Arrays.sort(result);
        return Arrays.copyOfRange(result, Math.max(result.length - count, 0), result.length);
    }

    public int getPrimaryRank() {
        return primaryRank;
    }

    public int getSecondaryRank() {
        return secondaryRank;
    }

    @Override
    public String toString() {
        return String.format("PokerHand: %s. %d, %d. Kickers: %s", type, primaryRank, secondaryRank, Arrays.toString(kickers));
    }
}


PokerHandResultProducer.java

/**
 * Interface for scanning for Poker hands.
 */
public interface PokerHandResultProducer {
    /**
     * Constant for how big our hands should be.
     */
    final int HAND_SIZE = 5;
    /**
     * Method which does the job of finding a matching Poker hand for some analyze data.
     * @param analyze {@link PokerHandAnalyze} object containing data for which we should try to find a matching Poker hand.
     * @return {@link PokerHandResult} for the best poker hand we could find.
     */
    PokerHandResult resultFor(PokerHandAnalyze analyze);
}


PokerHandType.java

public enum PokerHandType {
    HIGH_CARD, PAIR, TWO_PAIR, THREE_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH, ROYAL_FLUSH;
}


PokerPair.java

/**
 * Checks for PAIR, THREE_OF_A_KIND, FOUR_OF_A_KIND, and FULL_HOUSE. Returns HIGH_CARD if nothing better was found.
 */
public class PokerPair implements PokerHandResultProducer {

    @Override
    public PokerHandResult resultFor(PokerHandAnalyze analyze) {
        List<PokerHandResult> results = new ArrayList<PokerHandResult>();
        List<PokerHandResult> pairs = new ArrayList<PokerHandResult>();
        List<PokerHandResult> threeOfAKinds = new ArrayList<PokerHandResult>();
        int[] ranks = analyze.getRanks();
        int remainingWildcards = analyze.getWildcards();

        // Find out how many we should look for, primarily
        int[] sortedCounts = Arrays.copyOf(ranks, ranks.length);
        Arrays.sort(sortedCounts);
        int countForWildcards = sortedCounts[sortedCounts.length - 1];

        for (int index = ranks.length - 1; index >= 0; index--) {
            int count = ranks[index];
            int useWildcards = (count == countForWildcards ? remainingWildcards : 0);
            if (count + useWildcards >= 4) {
                remainingWildcards += count - 4;
                results.add(new PokerHandResult(PokerHandType.FOUR_OF_A_KIND, index + 1, 0, analyze.getCards(), 1));
            }

            // If there already exists some four of a kinds, then there's no need to check three of a kinds or pairs.
            if (!results.isEmpty())
                continue;

            if (count + useWildcards == 3) {
                remainingWildcards += count - 3;
                threeOfAKinds.add(new PokerHandResult(PokerHandType.THREE_OF_A_KIND, index + 1, 0, analyze.getCards(), 2));
            }
            else if (count + useWildcards == 2) {
                remainingWildcards += count - 2;
                pairs.add(new PokerHandResult(PokerHandType.PAIR, index + 1, 0, analyze.getCards(), 3));
            }
        }

        return checkForFullHouseAndStuff(analyze, pairs, threeOfAKinds, results);
    }

    private PokerHandResult checkForFullHouseAndStuff(PokerHandAnalyze analyze, List<PokerHandResult> pairs, List<PokerHandResult> threeOfAKinds, List<PokerHandResult> results) {
        if (!results.isEmpty())
            return PokerHandResult.returnBest(results);

        PokerHandResult bestPair = PokerHandResult.returnBest(pairs);
        PokerHandResult bestThree = PokerHandResult.returnBest(threeOfAKinds);
        if (bestPair != null && bestThree != null) {
            return new PokerHandResult(PokerHandType.FULL_HOUSE, bestThree.getPrimaryRank(), bestPair.getPrimaryRank(), null, 0); // No kickers because it's a complete hand.
        }
        if (bestThree != null)
            return bestThree;

        if (pairs.size() >= 2) {
            Collections.sort(pairs);
            int a = pairs.get(pairs.size() - 1).getPrimaryRank();
            int b = pairs.get(pairs.size() - 2).getPrimaryRank();
            return new PokerHandResult(PokerHandType.TWO_PAIR, Math.max(a, b), Math.min(a, b), analyze.getCards(), 1);
        }

        if (bestPair != null)
            return bestPair;

        // If we have a wildcard, then we always have at least PAIR, which means that it's fine to ignore wildcards in the kickers here as well 
        return new PokerHandResult(PokerHandType.HIGH_CARD, 0, 0, analyze.getCards());
    }

}


PokerStraight.java-(已删除)由于问题大小限制。请参见单独的问题

测试

PokerHandTest.java(175行)

public class PokerHandTest {
    private static final ClassicCard WILDCARD = new ClassicCard(Suite.EXTRA, ClassicCard.RANK_WILDCARD);

    private int findHighestIndexForStraight(int[] ranks, int wildcards) {
        int res = PokerStraight.findHighestIndexForStraight(ranks, wildcards);
        return res == -1 ? res : res - 1;
    }

    @Test
    public void moreCards() {
        PokerHandEval eval = PokerHandEval.defaultEvaluator();
        assertPoker(PokerHandType.THREE_OF_A_KIND, 2, eval.test(card(DIAMONDS, RANK_JACK), card(HEARTS, RANK_ACE_HIGH), card(SPADES, RANK_7),
                card(DIAMONDS, RANK_2), card(HEARTS, RANK_2), card(DIAMONDS, RANK_4), WILDCARD));

        assertPoker(PokerHandType.TWO_PAIR, 12, 10, eval.test(card(DIAMONDS, RANK_3), card(SPADES, RANK_2), card(DIAMONDS, RANK_10),
                card(CLUBS, RANK_10), card(CLUBS, RANK_7), card(SPADES, RANK_5), 
                card(SPADES, RANK_QUEEN), card(HEARTS, RANK_QUEEN), 
                card(HEARTS, RANK_ACE_HIGH), card(DIAMONDS, RANK_7)));

        PokerHandResult eq1;
        PokerHandResult eq2;
        eq1 = eval.test(card(CLUBS, RANK_10), card(CLUBS, RANK_7), card(SPADES, RANK_KING), 
                card(SPADES, RANK_QUEEN), card(HEARTS, RANK_QUEEN), card(HEARTS, RANK_ACE_HIGH), card(DIAMONDS, RANK_ACE_HIGH));
        eq2 = eval.test(card(CLUBS, RANK_JACK), card(CLUBS, RANK_7), card(SPADES, RANK_KING), 
                card(SPADES, RANK_QUEEN), card(HEARTS, RANK_QUEEN), card(HEARTS, RANK_ACE_HIGH), card(DIAMONDS, RANK_ACE_HIGH));
        assertEquals(eq1, eq2);

        eq1 = eval.test(WILDCARD, card(SPADES, RANK_QUEEN), card(HEARTS, RANK_QUEEN),   card(HEARTS, RANK_7), card(DIAMONDS, RANK_4));
        eq2 = eval.test(card(DIAMONDS, RANK_QUEEN), card(CLUBS, RANK_QUEEN), card(SPADES, RANK_QUEEN),  card(HEARTS, RANK_7), card(DIAMONDS, RANK_4));
        assertPoker(PokerHandType.THREE_OF_A_KIND, RANK_QUEEN, eq1);
        assertEquals(eq2, eq1);

        PokerHandResult result;
        result = eval.test(WILDCARD, WILDCARD, WILDCARD, WILDCARD, card(DIAMONDS, RANK_6));
        assertPoker(PokerHandType.STRAIGHT_FLUSH, result);

        result = eval.test(WILDCARD, WILDCARD, WILDCARD, card(HEARTS, RANK_10), card(DIAMONDS, RANK_6));
        assertPoker(PokerHandType.FOUR_OF_A_KIND, result);

        result = eval.test(WILDCARD, WILDCARD, WILDCARD, WILDCARD, WILDCARD);
        assertPoker(PokerHandType.ROYAL_FLUSH, result);

        result = eval.test(WILDCARD, WILDCARD, WILDCARD, WILDCARD, card(SPADES, RANK_10));
        assertPoker(PokerHandType.ROYAL_FLUSH, result);
    }
    public void assertPoker(PokerHandType type, int primary, PokerHandResult test) {
        assertPoker(type, test);
        assertEquals(primary, test.getPrimaryRank());
    }
    public void assertPoker(PokerHandType type, int primary, int secondary, PokerHandResult test) {
        assertPoker(type, primary, test);
        assertEquals(secondary, test.getSecondaryRank());
    }

    @Test
    public void royalAndFlushStraights() {
        PokerHandEval eval = PokerHandEval.defaultEvaluator();
        PokerHandResult result = eval.test(card(HEARTS, RANK_10), card(HEARTS, RANK_JACK), card(HEARTS, RANK_QUEEN), card(HEARTS, RANK_KING), card(HEARTS, RANK_ACE_LOW));
        assertPoker(PokerHandType.ROYAL_FLUSH, result);

        result = eval.test(card(HEARTS, RANK_2), card(HEARTS, RANK_3), card(HEARTS, RANK_4), card(HEARTS, RANK_5), card(HEARTS, RANK_6));
        assertPoker(PokerHandType.STRAIGHT_FLUSH, result);
    }

    @Test
    public void rankHands() {
        PokerHandEval eval = PokerHandEval.defaultEvaluator();
        PokerHandResult highCard       = eval.test(card(HEARTS, RANK_7), card(CLUBS, RANK_JACK), card(HEARTS, RANK_6), card(HEARTS, RANK_4), card(DIAMONDS, RANK_2));

        PokerHandResult pairLowKicker  = eval.test(card(HEARTS, RANK_7), card(CLUBS, RANK_7), card(HEARTS, RANK_6), card(HEARTS, RANK_4), card(HEARTS, RANK_2));
        PokerHandResult pairHighKicker = eval.test(card(HEARTS, RANK_7), card(CLUBS, RANK_7), card(HEARTS, RANK_KING), card(HEARTS, RANK_4), card(HEARTS, RANK_2));
        PokerHandResult pairHigher     = eval.test(card(HEARTS, RANK_KING), card(CLUBS, RANK_KING), card(HEARTS, RANK_6), card(HEARTS, RANK_4), card(HEARTS, RANK_2));

        PokerHandResult twoPair        = eval.test(card(HEARTS, RANK_KING), card(CLUBS, RANK_KING), card(HEARTS, RANK_6), card(DIAMONDS, RANK_6), card(HEARTS, RANK_2));
        PokerHandResult threeOfAKind   = eval.test(card(HEARTS, RANK_KING), card(CLUBS, RANK_KING), card(SPADES, RANK_KING), card(HEARTS, RANK_4), card(HEARTS, RANK_2));

        PokerHandResult flush         = eval.test(card(HEARTS, RANK_7), card(HEARTS, RANK_2), card(HEARTS, RANK_6), card(HEARTS, RANK_9), card(HEARTS, RANK_QUEEN));
        PokerHandResult fourOfAKind    = eval.test(card(HEARTS, RANK_7), card(SPADES, RANK_7), card(DIAMONDS, RANK_7), card(CLUBS, RANK_7), card(HEARTS, RANK_QUEEN));

        PokerHandResult straight       = eval.test(card(HEARTS, RANK_2), card(CLUBS, RANK_3), card(HEARTS, RANK_4), card(HEARTS, RANK_5), card(DIAMONDS, RANK_6));
        PokerHandResult straightWild   = eval.test(card(HEARTS, RANK_2), card(CLUBS, RANK_3), WILDCARD, card(HEARTS, RANK_5), card(DIAMONDS, RANK_6));
        assertEquals(straight, straightWild);
        PokerHandResult straightLow       = eval.test(card(HEARTS, RANK_ACE_HIGH), card(CLUBS, RANK_2), card(HEARTS, RANK_3), card(HEARTS, RANK_4), card(DIAMONDS, RANK_5));

        PokerHandResult straightFlush  = eval.test(card(HEARTS, RANK_8), card(HEARTS, RANK_9), card(HEARTS, RANK_10), card(HEARTS, RANK_JACK), card(HEARTS, RANK_QUEEN));
        PokerHandResult royalFlush     = eval.test(card(HEARTS, RANK_10), card(HEARTS, RANK_JACK), card(HEARTS, RANK_QUEEN), card(HEARTS, RANK_KING), WILDCARD);

        PokerHandResult fullHouse      = eval.test(card(HEARTS, RANK_10), card(CLUBS, RANK_10), WILDCARD, card(HEARTS, RANK_KING), card(HEARTS, RANK_KING));
        assertPoker(PokerHandType.FULL_HOUSE, fullHouse);
        assertEquals(RANK_KING, fullHouse.getPrimaryRank());
        assertEquals(RANK_10, fullHouse.getSecondaryRank());

        // Add hands to list
        List<PokerHandResult> results = new ArrayList<PokerHandResult>();
        assertAdd(results, PokerHandType.HIGH_CARD, highCard);
        assertAdd(results, PokerHandType.PAIR, pairLowKicker);
        assertAdd(results, PokerHandType.PAIR, pairHighKicker);
        assertAdd(results, PokerHandType.PAIR, pairHigher);
        assertAdd(results, PokerHandType.TWO_PAIR, twoPair);
        assertAdd(results, PokerHandType.THREE_OF_A_KIND, threeOfAKind);
        assertAdd(results, PokerHandType.FLUSH, flush);
        assertAdd(results, PokerHandType.FOUR_OF_A_KIND, fourOfAKind);
        assertAdd(results, PokerHandType.STRAIGHT, straightLow);
        assertAdd(results, PokerHandType.STRAIGHT, straight);
        assertAdd(results, PokerHandType.STRAIGHT, straightWild);
        assertAdd(results, PokerHandType.STRAIGHT_FLUSH, straightFlush);
        assertAdd(results, PokerHandType.ROYAL_FLUSH, royalFlush);

        // Shuffle just for the fun of it
        Collections.shuffle(results);

        // Sort the list according to the HandResult comparable interface
        Collections.sort(results);

        // Assert the list
        Iterator<PokerHandResult> it = results.iterator();
        assertEquals(highCard, it.next());
        assertEquals(pairLowKicker, it.next());
        assertEquals(pairHighKicker, it.next());
        assertEquals(pairHigher, it.next());

        assertEquals(twoPair, it.next());
        assertEquals(threeOfAKind, it.next());

        assertEquals(straightLow, it.next());

        assertEquals(straight, it.next());
        assertEquals(straightWild, it.next());

        assertEquals(flush, it.next());
        assertEquals(fourOfAKind, it.next());
        assertEquals(straightFlush, it.next());
        assertEquals(royalFlush, it.next());


        // Make sure that we have processed the entire list
        assertFalse("List is not completely processed", it.hasNext()); 
    }

    private static void assertAdd(List<PokerHandResult> results, PokerHandType type, PokerHandResult result) {
        assertPoker(type, result);
        results.add(result);
    }

    private static void assertPoker(PokerHandType type, PokerHandResult result) {
        if (type == null) {
            assertNull(result);
            return;
        }
        assertNotNull("Expected " + type, result);
        assertEquals(result.toString(), type, result.getType());
    }


    private static ClassicCard card(Suite suite, int rank) {
        return new ClassicCard(suite, rank);
    }

}


代码审查问题)

我试图充分利用策略模式和某些工厂方法(PokerPairPokerStraight),我想知道它们是否“正确”。如果您在不知不觉中看到了其他常用的模式,我也想听听。

但最重要的是:在这里我能做得更好吗?而且您能找到任何错误吗?

评论

假设您正在玩5张牌(剩下的代码不是...),是我还是没有“踢牌手”?您无法告诉7张牌交易的赢家,即使两位球员的第6张牌是红桃王牌,而另一位球员的第6张牌是2家具乐部,两位选手的直线相同。对还是错? :p

@retailcoder标记:按设计状态。我只将完整的“扑克手”视为五张牌。如果您有七张牌,那么其中只有五张将包含在扑克手中。因此,如果您有直牌(五张牌),则没有踢脚。 (我在想德克萨斯扑克,可能还会有其他变化)

@retailcoder灵活的代码,正确的代码(咳嗽),短代码-无法全部使用。

不要使用前缀代替软件包名称。那就是将类PokerXyz打包到扑克中,并且仅使用Xyz。

细心:术语是西装,而不是西装。

#1 楼

AceValue

这个课让我感到困惑。我不确定ranks[]的用途是什么。...有魔术数字,没有评论?

Suite

这具有isBlack()方法,但这表明DIAMONDS是黑色的...但是它们是红色的!更简单:

return Suite.values - (includingWildcards ? 0 : 1);


常规

对了,此后,复习变得非常复杂...坦率地说,在30分钟内就太难理解或更少...然后将代码拉到本地以便我可以运行它也不容易...并且当我决定这样做时,我已经花了太长时间了。这本身就是一个有趣的观察。我试图通读这些类,了解它们如何“挂在一起”,然后决定理解代码的唯一方法是对其进行调试和逐步调试。

然后我意识到没有主要方法,我猜您使用JUnit来运行它吗?

最后,我从未玩过扑克...(甚至没有剥离...),所以我没有本能的想法

最重要的是,代码需要对问题有一种本能/本能的理解,以便理解代码。本身表明存在结构/表达问题。

唯一合理的事情是对面值进行评论,而不是我想要的更深入...

PokerFlush

我不喜欢从其他类“隐式”提取的常量。在这种情况下,“神奇地”出现了suiteCount(boolean),我看到它在HAND_SIZE类实现的PokerHandResultProducer接口中是一个常数。对于PokerFlush来说,这是一个较差的位置,并且是引用它的较差的方法。它应该类似于HAND_SIZE

我不喜欢结果PokerRules.HAND_SIZEList的组合。我认为有更好的方法。一个叫做PokerHandResult.returnBest的类,它在添加手时简单地选择最佳手牌...然后具有Best<T>方法。您的代码变为:

Best<PokerHandResult> results = new Best<PokerHandResult>();
....
return results.best();


best()实例具有“自然顺序”(实现可比较),因此通用类PokerHandResult仅需运行Best<T>方法并保持最好。

PokerHandAnalyze

private final int[] ranks = new int[ClassicCard.RANK_ACE_HIGH]


我花了一些时间才明白...我知道这对您来说很有意义,即使我仔细看,也无法理解为什么不是compareTo()。至少这行需要一个好的注释,更好的是一个具有更好名称的常量..即,wtf是否与数组大小有关?在这里,但是它被错误地使用...

工厂方法对于掩盖接口的物理实现或创建复杂的不可变对象很有用。在这种情况下,工厂方法实际上只是通过将所有逻辑移到工厂方法来简化构造函数。实际上,这并不能简化类,而只是将构造函数逻辑移至非逻辑位置。

在我的评估中,应删除该工厂方法,并将逻辑移至构造函数。然后将所有实例字段定为最终字段,您将拥有一个不错的不可变类。如果您确实想保留工厂方法,那么您的字段应该仍然是最终字段,但是您需要将它们全部传递给构造函数。

ClassicCard.RANK_WILDCARD应该返回数组的副本,以使其不可变状态被保留(您已将数组标记为最终状态,但未保护数组中数据的最终状态)

据我所知,RANK_ACE_HIGH数组为“死”代码(完全未使用)。

getRanks()还应该通过返回数组的副本来保护数据。

PokerHandEval

在任何情况下都不要使用名称为“ test”的类/方法,除非它旨在测试您的代码(而不是测试数据).... ;-)这只会导致混乱。该类称为“ ... Eval”,所以为什么不能使用suites[]或某些变体? 。您首先寻找排名最低的结果,然后寻找越来越高的结果。如果您取消订单,则可以“缩短”流程并在获得第一个结果时退出(即,如果已经有同花顺,为什么还要寻找一对?)。

再次,在这里您还可以使用'Best'类来保持最佳结果。 (getCards()

我不喜欢此类的evaluate。应该替换为public final class PokerHandResult或将其移至中央returnBest(List<PokerHandResult> results)静态类中。否则,它是一个很好的接口(对此不多...)。

PokerHandType


PokerPair

您已将此方法分为两部分,这只是降低“复杂性”的一种廉价方法。但是,实际上,它增加了复杂性。...Best<T> ...真的吗?该方法没有附加值,它只会为用户创建两个级别的“返回”。有时方法只是很复杂。...

做的第一件事是如果已经找到另一个结果,则返回。除此之外,它不使用PokerRules列表。

在其他情况下,对于HAND_SIZEcheckForFullHouseAndStuffresults来说,Best<T>类将很有用。

结论

对于您使用的其他模式的问题,我真的帮不上太大忙,至于您如何使用策略和工厂模式,我觉得策略很好(除了results和评估顺序)。工厂模式在某些地方很麻烦。

我没有看到任何会影响游戏的错误。黑钻石是我看到的唯一问题...(这对滑雪很有帮助)。

评论


\ $ \ begingroup \ $
我们很幸运有您@rolfl。很棒的评论!
\ $ \ endgroup \ $
– Mathieu Guindon♦
2013年12月9日,3:30

\ $ \ begingroup \ $
钻石是黑色的,而紫罗兰是蓝色的,我写错了方法,谢谢。我再也不会将this.ordinal()用于这种方法了!总会有某人(就是我自己)迟早会交换元素以使其不正确。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月9日15:30

\ $ \ begingroup \ $
AceValue用于处理ace最高和最低的两个纸牌游戏。 getRanks返回一个可以循环通过的数组,该数组提供所有可用的等级(2到ACE_HIGH或ACE_LOW到KING)。由于面临的挑战是要成为一名评估员,因此我只写了代码,并不在乎添加任何与用户进行交互的UI。但是是的,这可能会使代码更易于理解,因为会有一个更清晰的“入口点”(是的,我目前仅使用单元测试)
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月9日15:51



\ $ \ begingroup \ $
@SimonAndréForsberg。好的,这些注释消除了我提到的问题。另一方面,您确实必须添加注释以使其清晰可见,因此代码本身可能应该以;-)开始添加注释。
\ $ \ endgroup \ $
–rolfl
2013年12月9日在16:40

\ $ \ begingroup \ $
@rolfl如果不问正确的问题,很难写出正确的评论。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2013年12月9日17:50

#2 楼

差不多四年后...我发现了一个错误。请考虑以下测试:

assertEquals(PokerHandType.THREE_OF_A_KIND, eval.evaluate(
    card(Suite.CLUBS, ClassicCard.RANK_ACE_HIGH),
    card(Suite.HEARTS, ClassicCard.RANK_5),
    card(Suite.SPADES, ClassicCard.RANK_KING),
    card(Suite.DIAMONDS, ClassicCard.RANK_ACE_HIGH),
    card(Suite.SPADES, ClassicCard.RANK_ACE_HIGH)
).getType());


这是三种,应该通过,对吧?确实可以。

下一个:

assertEquals(PokerHandType.THREE_OF_A_KIND, eval.evaluate(
    card(Suite.CLUBS, ClassicCard.RANK_ACE_HIGH),
    card(Suite.SPADES, ClassicCard.RANK_ACE_HIGH),
    card(Suite.SPADES, ClassicCard.RANK_KING),
    card(Suite.HEARTS, ClassicCard.RANK_8),
    card(Suite.HEARTS, ClassicCard.RANK_5),
    card(Suite.SPADES, ClassicCard.RANK_2),
    card(Suite.EXTRA, ClassicCard.RANK_WILDCARD)
).getType());


哦,通配符!但这仍然被认为是三种,对吗?错误。它应该是三种,但是代码认为这是一个满屋,因为ace可以是高(14)和低(1),因此我们有一个满屋,其中有三个14和两个1。 br />我们甚至可以摆脱一些额外的卡,看看其中的一张:低一点。

那么简单吗?不,代码说是两对。同样,有两对高和低的ace。在checkForFullHouseAndStuff中的PokerPair中,首先通过以下方法传递结果:

assertEquals(PokerHandType.THREE_OF_A_KIND, eval.evaluate(
    card(Suite.CLUBS, ClassicCard.RANK_ACE_HIGH),
    card(Suite.SPADES, ClassicCard.RANK_ACE_HIGH),
    card(Suite.EXTRA, ClassicCard.RANK_WILDCARD)
).getType());


此方法首先检查结果是否同时包含高位和低位Ace,如果是,它会返回只有主要等级的退化扑克手。所以满屋变成了三种,而两对变成了一对。