Weekend Challenge - оценка рук в руке

Weekend Challenge # 2 - Оценка рук в руке

Очень рано я решил поддержать использование диких шутников, хотя я знал, что это приведет к большей работе trouble . Я также хотел поддержать большую коллекцию карт, например, если у вас есть 7 карт и выбрать лучшую комбинацию из 5 карт в покер. Разумеется, комбинаторика может быть использована для этого (7 nCr 5 = 35 комбинаций), но мне нужен лучший подход.

Итак, чтобы проверить возможные покерные руки, я действительно не хотел совершать ту же ошибку кто-то другой . И, как обычно, я также предпочитаю гибкое решение. Итак, довольно скоро в моей голове исчезла предупреждение о шаблоне стратегии .

Я потратил много времени на написание тестов, чтобы убедиться, что он работает так, как должен. Теперь я считаю, что он работает так, как должен (или, по крайней мере, так, как я этого хочу).

Обзор класса

  • AceValue - перечисление для определения того, что значение туза (не так полезно здесь, но очень используется в других проектах карты)
  • Suite - enum для сердец, лопат, бриллиантов, клубов + один для подстановочных знаков и других забавных вещей.
  • ClassicCard - класс, содержащий Suite и ранг (int). Также содержит константы для разных рангов.
  • PokerHandEval - класс, отвечающий за определение PokerHandResult с учетом массива ClassicCard
  • PokerHandAnalyze - класс, используемый PokerHandEval для анализа массива карт и структурных данных в целые массивы используемых рангов и костюмов.
  • PokerHandType - перечисление для стандартного типа доступных покерных рук.
  • PokerHandResult - сопоставимый класс, который содержит результат покера. Включает PokerHandType + параметры («полный дом чего?») + «Кикеры» (пара 6s, конечно, но каковы ваши другие старшие карты?)
  • PokerHandResultProducer - интерфейс для объявления метода, используемого шаблоном стратегии, который реализуется ниже в трех классах
    • PokerPair - стратегия поиска пар, двухпалубных, трехсоставных, четырехразовых и фулл-хаусов.
    • PokerStraight - стратегия поиска прямых
    • PokerFlush - стратегия поиска флеша, флеша и флеша - использует PokerStraight для определения прямых.

Некоторый уже существующий код

Так как я использовал карты ранее на 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);
    }
}

Suite.java

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;
    }
}

Код

Общая длина: 309 строк кода (комментарии и пробелы исключены) в 8 файлах.

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;
    }
}

PokerHandResult.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);
    }

}

Обзор кода Вопрос (ы)

Я попытался хорошо использовать шаблон стратегии и некоторые заводские методы (PokerHandAnalyze и PokerHandEval)), я хотел бы знать, являются ли они «правильными». Если вы видите другие распространенные шаблоны, которые я использовал, не зная об этом, я также хотел бы услышать это.

Но самое главное: есть ли что-нибудь, что я мог бы сделать лучше здесь? И можете ли вы найти какие-либо ошибки?

32 голоса | спросил Simon Forsberg 8 SunEurope/Moscow2013-12-08T21:45:18+04:00Europe/Moscow12bEurope/MoscowSun, 08 Dec 2013 21:45:18 +0400 2013, 21:45:18

2 ответа


24

AceValue

Этот класс меня смущает. Я не уверен, что означает ranks[] для .... и есть магические числа, и нет комментариев?

Люкс

У этого есть метод isBlack(), но это указывает, что DIAMONDS черные ... но они красные!

suiteCount(boolean) может быть намного проще:

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

Общие

Правильно, после этого становится очень сложно пересмотреть ... Честно говоря, слишком много, чтобы понять за 30 минут или меньше ... и вытащить код локально, чтобы я мог запустить его, тоже непросто ... и к тому времени, когда я решил это сделать, я слишком долго его потратил. Это само по себе является интересным наблюдением. Я попытался прочесть классы и понять, как «он висит вместе», а затем решил, что единственный способ понять код - «отладить» его и выполнить шаг.

Тогда я понял, что нет основного метода, и я предполагаю, что вы используете JUnit для его запуска?

Наконец, я просто не играл в покер ... (даже не снимаю ...), поэтому у меня нет инстинктивной идеи о том, куда должен идти код.

Нижняя строка состоит в том, что для понимания кода требуется естественное /инстинктивное понимание проблемы.

Это само по себе предполагает наличие проблемы структуры /представления.

Единственная разумная вещь, которую нужно сделать, это обзор по номиналу, а не такой глубокий, как хотелось бы ...

PokerFlush

Мне не нравятся константы, которые неявно вытягиваются из других классов. В этом случае появляется HAND_SIZE «magically», и я вижу, что это константа в интерфейсе PokerHandResultProducer, который реализует класс PokerFlush. Это плохое место для HAND_SIZE и плохой способ ссылаться на него. Это должно быть что-то вроде PokerRules.HAND_SIZE.

Мне не нравятся результаты List и комбинация PokerHandResult.returnBest. Я думаю, что есть лучший способ. Класс под названием Best<T>, который просто выбирает лучшую руку с добавлением рук ... и затем имеет метод best(). Ваш код будет выглядеть следующим образом:

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

У экземпляров PokerHandResult есть «естественный порядок» (реализация Comparable), поэтому общий класс Best<T> должен выполнять только compareTo() и сохраняйте лучшее.

PokerHandAnalyze

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

Мне потребовалось несколько минут, чтобы понять это ... Я знаю, что это может иметь смысл для вас, но даже когда я внимательно смотрю на него, я не понимаю, почему это не ClassicCard.RANK_WILDCARD. По крайней мере, эта строка нуждается в хорошем комментарии, лучше будет константа с лучшим именем .... т. Е. Wtf делает RANK_ACE_HIGH отношение к размеру массива?

Я знаю, что вы используете заводский метод здесь, но он используется неправильно ...

Заводский метод хорош для скрытия физической реализации интерфейса или для создания иначе сложных неизменяемых. В этом случае фабричный метод действительно просто упрощает конструктор ... путем перемещения всей логики к заводскому методу. На самом деле это не делает класс более простым, а просто переводит логику конструктора в нелогичное место.

В моей оценке, этот заводский метод должен быть удален, и логика перешла к конструктору. Затем сделайте все поля экземпляра окончательными, и у вас есть класс, отличный от него. Если вы действительно хотите сохранить фабричный метод, ваши поля должны быть окончательными, но вам нужно передать их всем конструктору.

getRanks() должен возвращать копию массива, чтобы сохранить неизменяемое состояние (вы отметили массив как окончательный, но вы не защищаетеконечное состояние данных в массиве)

Насколько я могу судить, массив suites[] - это «мертвый» код (полностью не используется).

getCards() также должен защищать данные, возвращая копию массива.

PokerHandEval

Никогда ни при каких обстоятельствах не используйте классы /методы с именем «test», если он не предназначен для проверки вашего кода (не проверяйте данные) .... ;-) Это просто приводит к путанице. Класс называется «... Eval», так почему вы не можете использовать evaluate или какой-нибудь вариант?

Из того, что я могу сказать, ваши тесты упорядочены в обратном порядке, чтобы они были ... вы сначала ищете результаты с самым низким рангом, а затем все более высокие результаты. Если вы отмените заказ, вы можете «закоротить» процесс и выйти, когда у вас есть свой первый результат (например, почему вы должны искать пару, если у вас уже есть флеш?).

Опять же, здесь вы также можете использовать класс «Лучший», чтобы сохранить только лучший результат.

PokerHandResult

Похож на разумно чистый неизменный класс ... но должен быть объявлен окончательным (public final class PokerHandResult)

Мне не нравится returnBest(List<PokerHandResult> results) для этого класса. Должен быть заменен на Best<T> или перенесен в основной статический класс PokerRules

PokerHandResultProducer

Как я уже сказал, HAND_SIZE здесь не принадлежит. В противном случае это прекрасный интерфейс (не так много ...).

PokerHandType

fine ...; -)

PokerPair

Вы разделили этот метод на две части, и это просто дешевый способ уменьшить «сложность». Но на самом деле он добавил сложность .... checkForFullHouseAndStuff ... действительно? От этого метода нет добавленной стоимости, и он просто создает два уровня «возврата» пользователю. Иногда методы просто должны быть сложными ....

Первое, что делает, - это возврат, если другой результат уже найден. Кроме этого, он не использует список results.

Как и в других случаях, класс Best<T> был бы полезен ... для results, pairs и threeOfAKind.

Заключение

Я не могу сильно помочь в вопросах о других используемых шаблонах, а также о том, как вы используете стратегию и фабричные шаблоны, я считаю, что стратегия прекрасна (кроме HAND_SIZE и порядок оценки). Образцы фабрики громоздки в местах.

Я не видел никаких ошибок, которые повлияли бы на вашу игру. Black-Diamond - единственная проблема, которую я вижу ... (и это хорошо для катания на лыжах).

ответил rolfl 9 MonEurope/Moscow2013-12-09T06:41:10+04:00Europe/Moscow12bEurope/MoscowMon, 09 Dec 2013 06:41:10 +0400 2013, 06:41:10
11

Почти четыре года спустя ... Я обнаружил ошибку. Рассмотрим следующие тесты:

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());

О, подстановочный знак! Но это все еще считается трёх, не так ли? Неправильно. Это должно быть три вида, но код считает это фулл-хаусом, потому что туз может быть как высоким (14), так и низким (1), поэтому у нас есть фулл-хаус с тремя четырьмя и двумя.

Мы даже можем избавиться от нескольких дополнительных карт и взглянем на это:

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());

Код по-прежнему считает этот дом полным и низким тузом.

Как насчет чего-то более простого?

assertEquals(PokerHandType.PAIR, eval.evaluate(
    card(Suite.CLUBS, ClassicCard.RANK_ACE_HIGH),
    card(Suite.DIAMONDS, ClassicCard.RANK_ACE_HIGH)
).getType());

Это должна быть одна пара? Нет, код говорит, что это две пары. Опять же, две пары как высоких, так и низких тузов.

Итак, что это за возможное исправление? В checkForFullHouseAndStuff в PokerPair сначала передайте результаты с помощью следующего метода:

private PokerHandResult fixDoubleAce(PokerHandResult result, PokerHandAnalyze analyze) {
    if (result.getPrimaryRank() != ClassicCard.RANK_ACE_HIGH || result.getSecondaryRank() != ClassicCard.RANK_ACE_LOW) {
        return result;
    }
    switch (result.getType()) {
        case FULL_HOUSE:
            return new PokerHandResult(PokerHandType.THREE_OF_A_KIND, result.getPrimaryRank(), 0, analyze.getCards());
        case TWO_PAIR:
            return new PokerHandResult(PokerHandType.PAIR, result.getPrimaryRank(), 0, analyze.getCards());
        default:
            throw new IllegalStateException("Unexpected scenario cleaning double ace from " + result);
    }
}

Этот метод сначала проверяет, является ли результат как для высоких, так и для низких тузов, и если это так, он возвращает деградированную руку в покере только с первичным рангом. Таким образом, полный дом становится тройкой, а две пары становятся одной парой.

ответил Simon Forsberg 5 72017vEurope/Moscow11bEurope/MoscowSun, 05 Nov 2017 18:26:56 +0300 2017, 18:26:56

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132