202001 DDD DSL Design War Poker Engine - xiaoxianfaye/Courses GitHub Wiki

1 Problem

给定多手牌,按照规则找出获胜者。

一手牌(hand)由5张牌组成,每张牌有自己的花色(suit)和分值(rank)。根据下表的规则计算出每手牌的分值(hand rank),分值最高的那手牌即为获胜者。

一手牌的类型 中文别名 一手牌的分值
High Card 散牌 0
One Pair (two-kind) 一个对子 1
Two Pairs (Two two-kinds) 两个对子 2
Three of a Kind 三张一样 3
Straight 顺子 4
Flush 同花 5
Full House (three-kind + two-kind) 三带二 6
Four of a Kind 四张一样 7
Straight Flush 同花顺 8

请编写一个程序来模拟这个游戏。

2 Showcase & Discuss

请学员展示自己之前的设计思路和实现方式,大家可以互相点评、讨论。以学员为主,讲师为辅。

3 Analysis

分析:定义清楚问题是什么。

3.1 List of Concepts

概念清单:

  • hand:一手牌,由5张牌组成。
  • card:一张牌。例如:方块5,花色是方块,分值是5。
    • suit:花色
    • rank:分值
  • hand rank:一手牌的分值。

3.2 Understanding of Rules

规则解读,更加明确有哪些基本因素决定了一手牌的分值:

  • Kind:种类,指一手牌中具有同样分值的那些牌。例如:有2张分值都为3的牌,就是two-kind。
  • Straight:顺子,一手牌的分值是连续的,花色无所谓。例如:一张方块5,一张红桃6,一张黑桃7,一张梅花8,一张方块9。
  • Flush:同花,一手牌的花色全部是一样的,分值无所谓。例如:5张牌全部都是红桃。

4 Design & Implementation

设计:问题分析清楚以后,提出解决问题的逻辑框架。

实现:选择实现技术把逻辑框架的软件模型实现出来。

在实际设计开发过程中,设计和实现往往不会分得那么清楚。这次想展现一个设计实现交替前进的过程。

4.1 Computing

设计就是把问题变成可计算的。

系统框图:

design computing

4.2 Where Shall We Start?

我们该从哪里开始呢?

这里有个关键的问题要先解决,我们应该如何形式化地表达一手牌?换句话说,我们应该用什么样的数据结构来表示一手牌?

建议从两个方面考虑:

  • 对外:用户使用友好
  • 对内:数据处理简便

4.2.1 How to Formalize a Hand User-friendly?

Question:如何用户友好地形式化地表达一手牌:黑桃J、方块J、黑桃2、梅花2、红桃7?

"JS JD 2S 2C 7H"

说明:

  • 以“JS”为例,第一个字符J表示分值,后面的S表示花色。
  • 分值集合为:{2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K, A}。大于9的分值用字母表示,表达更友好,解析更方便。
  • 花色集合为:{S, D, C, H}。S是黑桃(Spade),D是方块(Diamond),C是梅花(Club),H是红桃(Heart)。
  • 字符串对用户来说是很友好的,也便于测试。
  • 这里暂不考虑规格化和合法性校验。

formalizing hand user friendly

4.2.2 How to Formalize a Hand Program-friendly?

Question:如何程序友好地形式化表达一手牌:黑桃J、方块J、黑桃2、梅花2、红桃7?

[(11, S), (11, D), (2, S), (2, C), (7, H)]

说明:

  • 一手牌有5张牌,需要用Collection来表示。
  • 是用List还是Set?关键在于元素是否一定要不重复。在这个问题领域里,不太需要这种严格性,而且如果多于一副牌,可能会出现一手牌有两个“JS”的情况。所以用List。
  • 列表元素为一张牌,由分值和花色组成。

formalizing hand program friendly

让我们来换一种有“品位”一点的说法,其实上面的讨论就是DDD的核心,构造出与问题领域根本需求相匹配的计算模型。计算模型包括数据结构和算法。

4.2.3 Poker Engine UI & Poker Engine

根据上面的设计思考,我们可以将原来的Poker Engine分成Poker Engine UI和PokerEngine两个层次:

  • 由Poker Engine UI负责用户友好的数据结构和程序友好的数据结构之间的转换。
  • 由Poker Engine负责从多手牌中找出分值最高的那手牌的主要业务逻辑。

poker engine ui and poker engine

4.3 Wishful Thinking

我们应该如何以Problem-solving为导向,识别出问题的核心,围绕核心问题的解决来演化以及延伸出整个解决方案呢?

这里用到的思维模式可以称之为Wishful Thinking。Wishful Thinking可以帮助我们从问题本身层面而不是从实现层面思考如何表达问题

我们可以假定所有需要的、辅助的东东,有个神一样的助手都帮我们做好了。这样,我们就可以以声明性的方式来描述问题的解决方法,比如:“做这个,做那个...”而不是“如何做这个,如何做那个...”。

在应用这种方法时,我们将需要解决的问题用声明性的、简短的话来描述,然后看看我们手边、使用的语言中是不是有称手的工具来帮助我们表述这个问题,有就拿来用,没有就自己制造。

在这个课程里,我们会看到无处不在的Wishful Thinking。

4.4 Poker Engine UI or Poker Engine, Which One First?

Question:先设计Poker Engine UI还是Poker Engine?

从风险、端到端交付等维度选择关键路径。先设计Poker Engine。

4.5 Poker Engine

4.5.1 Wishful Thinking - "run" Method

Poker Engine的入口方法命名为run(),大概长成下面这个样子:

    List<Card> run(List<List<Card>> hands)
    {
        return ...
    }

这里声明性的简短描述就是:返回多手牌中分值最高的那手牌。

用Card表示一张牌,包含分值(rank)和花色(suit)两个属性,大概长成下面这个样子:

    int rank;
    Suit suit;

Suit是个枚举类型,大概长成下面这个样子:

    enum Suit {S, H, C, D};

4.5.2 Wishful Thinking - "max" Method

Question:Java语言的JDK中,有哪个方法能够返回列表中排名最高的元素?

java.util.Collections.max。max接受一个Collection,返回该Collection中排名最高的那个元素。例如:

max(asList(1, 3, 8, 2)) => 8
max(asList("ab", "cd", "ef", "ca")) => "ef" //lexicographical order 字典序

除了Collection,max还可以接受另外一个参数Comparator,用于指定比较规则。Comparator是一个函数接口,提供了comparing系列静态方法。例如:

max(asList(-1, 7, -8), Comparator.comparing(Math::abs)) => 8

将“Math::abs”方法依次作用于Collection中的每个元素,对它们的返回值进行比较,返回Collection中“Math::abs”方法返回值最大的那个元素。

“Math::abs”其实是一个KeyExtractor,用于从列表元素中提取可比较的用于排序的Key。除了KeyExtractor,Comparator.comparing()还可以接受一个KeyComparator参数,用于指定Key的比较规则。

public class Person
{
    private String name;
    private int age;

    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public String getName()
    {
        return name;
    }

    public int getAge()
    {
        return age;
    }

    @Override
    public String toString()
    {
        return String.format("name = %s, age = %d", name, age);
    }
}

List<Person> persons = asList(new Person("Candy", 18), new Person("Alan", 19), new Person("Tom", 20));
max(persons, Comparator.comparing(Person::getAge)) => name = Tom, age = 20
max(persons, Comparator.comparing(Person::getName)) => name = Tom, age = 20
max(persons, Comparator.comparing(Person::getName, Comparator.comparing(String::length))) => name = Candy, age = 18

4.5.3 Wishful Thinking - HandRankCalculator and HandRankComparator

Question:如何用max来解决run(hands)?

我们需要计算每一手牌的分值(hand rank),再比较这些分值,并求取最大值。

关于一手牌,如果我们有一个分值计算器和一个分值比较器,run()方法就变成下面这个样子:

    List<Card> run(List<List<Card>> hands)
    {
        return Collections.max(hands, Comparator.comparing(HandRankCalculator::calculate, HandRankComparator::compare));
    }

将“HandRankCalculator::calculate”方法依次作用hands的每一个元素(一手牌),计算每一手牌的分值(hand rank),由“HandRankComparator::compare”指定分值的比较规则,最终得到分值最高的那手牌。

HandRankCalculator的calculate()方法用于计算一手牌的分值,大概长成下面这个样子:

    ??? calculate(List<Card> hand)
    {
        return ...
    }

这里返回类型待定。

HandRankComparator的compare()方法用于指定两手牌分值的比较规则,大概长成下面这个样子:

    int compare(??? handRank1, ??? handRank2)
    {
        return ...
    }

这里参数类型待定,但这里的参数类型和HandRankCalculator的calculate()方法的返回值类型是一样的。

4.5.4 What Does "???" Mean?

Question:HandRankCalculator的calculate()方法和HandRankComparator的compare()方法签名中的“???”是什么意思?

“???”的含义是一手牌的分值,即hand rank。

Question:如何比较“hand rank”?

规则表中每种类型的一手牌都对应了一个整型分值,可用于比较多手牌的分值。

Question:仅用这些整型分值来比较多手牌的分值,是否足够?

对于不同类型的两手牌来说,因为每个类型对应不同的整型分值,通过比较整型分值的数值大小可以比较出两手牌的分值大小。但是对于同一类型的两手牌来说,仅仅使用整型分值就不够了,需要更多的信息用于比较。

Question:"6C 7C 8C 9C TC"和"8C 9C TC JC QC",哪一手牌大?

都是Straight Flush,两手牌的整型分值都是8。但是,第一手牌从6开始,第二手牌从8开始。后者胜出。

Question:"TC TH TD 2C 2D"和"9D 9C 9H 3D 3S",哪一手牌大?

都是Full House,两手牌的整型分值都是6。但是,第一手牌三个一样的分值是10、对子的分值是2,第二手牌三个一样的分值是9、对子的分值是3。前者胜出。

我们希望这些用于比较的信息都能体现在每手牌的分值信息中。

Question:将一手牌的5张牌的分值表示为ranks,从方便比较的角度,可以尝试表示出所有类型的一手牌的分值信息吗?

  • Straight Flush: (8, max(ranks))
  • Four of a Kind: (7, 4-kind rank, rest rank)
  • Full House: (6, 3-kind rank, 2-kind rank)
  • Flush: (5, ranks)
  • Straight: (4, max(ranks))
  • Three of a Kind: (3, 3-kind rank, rest ranks)
  • Two Pairs: (2, two-pair ranks, rest rank)
  • One Pair: (1, 2-kind rank, rest ranks)
  • High Card: (0, ranks)

Question:从方便比较的角度,对ranks有什么要求吗?

以High Card来说,一手牌为"6C 8C 3S 2C 1D",另一手牌为"6D 8H 4S 2C 1D",整型分值都是0,还需要逐个比较每张牌的大小,怎么比较呢?

当然是希望将每手牌的分值由大到小排列,然后逐个比较。所以,ranks应该是按照分值降序排序的。

4.5.5 How to Formalize "Hand Rank"?

Question:如何形式化地表达一手牌的分值信息?

用StraightFlushHandRank表示Straight Flush对应的分值信息,大概长成下面这个样子:

    int typeRank = 8;
    int maxRank;

用FourKindHandRank表示Four of a Kind对应的分值信息,大概长成下面这个样子:

    int typeRank = 7;
    int fourKindRank;
    int restRank;

用FullHouseHandRank表示Full House对应的分值信息,大概长成下面这个样子:

    int typeRank = 6;
    int threeKindRank;
    int twoKindRank;

用FlushHandRank表示Flush对应的分值信息,大概长成下面这个样子:

    int typeRank = 5;
    List<Integer> ranks;

用StraightHandRank表示Straight对应的分值信息,大概长成下面这个样子:

    int typeRank = 4;
    int maxRank;

用ThreeKindHandRank表示Three of a Kind对应的分值信息,大概长成下面这个样子:

    int typeRank = 3;
    int threeKindRank;
    List<Integer> restRanks;

用TwoPairsHandRank表示Two Pairs对应的分值信息,大概长成下面这个样子:

    int typeRank = 2;
    List<Integer> twoPairsRanks;
    int restRank;

用OnePairHandRank表示One Pair对应的分值信息,大概长成下面这个样子:

    int typeRank = 1;
    int onePairRank;
    List<Integer> restRanks;

用HighCardHandRank表示High Card对应的分值信息,大概长成下面这个样子:

    int typeRank = 0;
    List<Integer> ranks;

4.5.6 How to Calculate "Hand Rank"?

Question:如何计算一手牌的分值信息?

由HandRankCalculator的calculate()方法负责计算一手牌的分值信息,大概长成下面这个样子:

    ?HandRank calculate(List<Card> hand)
    {
        List<Integer> ranks = extractDescentRanks(hand);
        if straightFlush(hand)
            return calcStraightFlush(ranks);
        else if fourKind(hand)
            return calcFourKind(ranks);
        else if fullHouse(hand)
            return calcFullHouse(ranks);
        ...
        else if highCard(hand)
            return calcHighCard(ranks);
        ...
    }

这里返回类型是某一种HandRank。实现逻辑是:先根据hand判断一手牌的类型,再根据类型调用对应的方法计算出对应的分值信息。需要注意的是,根据hand判断一手牌的类型需要按照“straightFlush -> fourKind -> fullHouse -> ... -> highCard”的顺序判断。这样实现起来比较简单,否则每个判断方法都需要更严格。

4.5.7 How to Compare "Hand Rank"?

Question:如何比较两手牌的分值信息?

由HandRankComparator的compare()方法负责比较两手牌的分值信息,大概长成下面这个样子:

    int compare(?HandRank handRank1, ?HandRank handRank2)
    {
        return handRank1.typeRank != handRank2.typeRank
                ? handRank1.typeRank - handRank2.typeRank
                : compareInSameType(handRank1, handRank2);
    }

这里输入参数类型是某两种HandRank。实现逻辑是:先比较两手牌的类型对应的整型分值,如果不相等,则已决出胜负;如果两手牌的类型对应的整型分值相等,则继续比较其他更详细的分值信息。

    int compareInSameType(?HandRank handRank1, ?HandRank handRank2)
    {
        if straightFlush(handRank1)
            return compareStraightFlush(handRank1, handRank2);
        if fourKind(handRank1)
            return compareFourKind(handRank1, handRank2);
        if fullHouse(handRank1)
            return compareFullHouse(handRank1, handRank2);
        ...
        if highCard(handRank1)
            return compareHighCard(handRank1, handRank2);
        ...
    }

这里输入参数类型是某两种HandRank。实现逻辑是:先根据handRank1判断一手牌的类型,再根据类型调用对应的方法得到比较结果。需要注意的是,这里根据handRank1判断一手牌的类型不需要按照“straightFlush -> fourKind -> fullHouse -> ... -> highCard”的顺序判断,避免逻辑重复。另外,用handRank1或handRank2判断一手牌的类型都可以,因为已经进入compareInSameType()方法,两手牌的类型一定是一样的。

4.5.8 How to Improve the Formalization of "?HandRank"?

Question:如何改进一手牌的分值信息的形式化表达,才能达到上述目标?

有两种方法:继承和组合。

4.5.8.1 Inheritance

用Java语言实现,比较容易想到的是用继承。

父类HandRank,大概长成下面这个样子:

class HandRank
{
    int typeRank;
}

子类StraightFlushHandRank,大概长成下面这个样子:

class StraightFlushHandRank extends HandRank
{
    int maxRank;
}

子类FourKindHandRank,大概长成下面这个样子:

class FourKindHandRank extends HandRank
{
    int fourKindRank;
    int restRank;
}

子类FullHouseHandRank,大概长成下面这个样子:

class FullHouseHandRank extends HandRank
{
    int threeKindRank;
    int twoKindRank;
}

剩下的子类不再一一列举。

这样一来,我们可以确定,HandRankCalculator的calculate()方法的返回值类型为HandRank。每种一手牌的类型对应的计算方法的返回值类型为对应的HandRank的子类。

    HandRank calculate(List<Card> hand)
    {
        List<Integer> ranks = extractDescentRanks(hand);
        if straightFlush(hand)
            return calcStraightFlush(ranks);
        else if fourKind(hand)
            return calcFourKind(ranks);
        else if fullHouse(hand)
            return calcFullHouse(ranks);
        ...
        else if highCard(hand)
            return calcHighCard(ranks);
        ...
    }

我们也可以确定,HandRankComparator的compare()方法的输入参数类型为HandRank:

    int compare(HandRank handRank1, HandRank handRank2)
    {
        return handRank1.typeRank != handRank2.typeRank
                ? handRank1.typeRank - handRank2.typeRank
                : compareInSameType(handRank1, handRank2);
    }

compareInSameType()方法的输入参数类型也为HandRank:

    int compareInSameType(HandRank handRank1, HandRank handRank2)
    {
        if straightFlush(handRank1)
            return compareStraightFlush(handRank1, handRank2);
        if fourKind(handRank1)
            return compareFourKind(handRank1, handRank2);
        if fullHouse(handRank1)
            return compareFullHouse(handRank1, handRank2);
        ...
        if highCard(handRank1)
            return compareHighCard(handRank1, handRank2);
        ...
    }

这里可以根据handRank的具体类型或类型分值判断一手牌的类型,且不需要按照“straightFlush -> fourKind -> fullHouse -> ... -> highCard”的顺序判断,再根据类型调用对应的方法得到比较结果。

4.5.8.2 Composition

如果不想利用Java语言的面向对象编程特性,我们可以用组合。

HandRank,大概长成下面这个样子:

class HandRank
{
    int typeRank;
    T content;
}

这里typeRank是一手牌的类型对应的整型分值(type rank)。content是分值信息的内容,不同类型的一手牌的content类型是不一样的。

Straight Flush对应的content,大概长成下面这个样子:

class StraightFlushHandRankContent
{
    int maxRank;
}

Four of a Kind对应的content,大概长成下面这个样子:

class FourKindHandRankContent
{
    int fourKindRank;
    int restRank;
}

Full House对应的content,大概长成下面这个样子:

class FullHouseHandRankContent
{
    int threeKindRank;
    int twoKindRank;
}

剩下的HandRankContent不再一一列举。

这样一来,我们可以确定,HandRankCalculator的calculate()方法的返回值类型为HandRank。每种一手牌的类型对应的计算方法的返回值HandRank中包含一手牌的类型分值和分值内容。

    HandRank calculate(List<Card> hand)
    {
        List<Integer> ranks = extractDescentRanks(hand);
        if straightFlush(hand)
            return calcStraightFlush(ranks);
        else if fourKind(hand)
            return calcFourKind(ranks);
        else if fullHouse(hand)
            return calcFullHouse(ranks);
        ...
        else if highCard(hand)
            return calcHighCard(ranks);
        ...
    }

我们也可以确定,HandRankComparator的compare()方法的输入参数类型为HandRank:

    int compare(HandRank handRank1, HandRank handRank2)
    {
        return handRank1.typeRank != handRank2.typeRank
                ? handRank1.typeRank - handRank2.typeRank
                : compareInSameType(handRank1, handRank2);
    }

compareInSameType()方法的输入参数类型也为HandRank:

    int compareInSameType(HandRank handRank1, HandRank handRank2)
    {
        if straightFlush(handRank1)
            return compareStraightFlush(handRank1, handRank2);
        if fourKind(handRank1)
            return compareFourKind(handRank1, handRank2);
        if fullHouse(handRank1)
            return compareFullHouse(handRank1, handRank2);
        ...
        if highCard(handRank1)
            return compareHighCard(handRank1, handRank2);
        ...
    }

这里可以根据handRank的类型分值判断一手牌的类型,且不需要按照“straightFlush -> fourKind -> fullHouse -> ... -> highCard”的顺序判断,再根据类型调用对应的方法得到比较结果。

4.5.9 HandRankCalculator or HandRankComparator, Which One First?

Question:HandRankCalculator或HandRankComparator,先实现哪一个?

根据依赖关系,先实现HandRankCalculator。

4.5.10 Implementation - HandRankCalculator

4.5.10.1 Card

定义Card数据类。

poker.first.entity.Card

public class Card
{
    private int rank;
    private Suit suit;

    private Card(int rank, Suit suit)
    {
        this.rank = rank;
        this.suit = suit;
    }

    public static Card card(int rank, Suit suit)
    {
        return new Card(rank, suit);
    }

    public int getRank()
    {
        return rank;
    }

    public Suit getSuit()
    {
        return suit;
    }
}

poker.first.entity.Suit

public enum Suit
{
    S('S'), H('H'), C('C'), D('D');

    private char abbrChar;

    private Suit(char abbrChar)
    {
        this.abbrChar = abbrChar;
    }

    public char getAbbrChar()
    {
        return abbrChar;
    }

    public static Suit getEnum(char abbrChar)
    {
        return Stream.of(Suit.values())
                     .filter(suit -> suit.abbrChar == abbrChar)
                     .findFirst()
                     .orElseThrow(() -> new IllegalArgumentException(String.format("Illegal abbrChar (%c) of Suit.", abbrChar)));
    }
}

4.5.10.2 HandRank

采用Composition方式定义HandRank数据类。

poker.first.entity.handrank.HandRank

public class HandRank<T>
{
    public static final int TYPE_RANK_STRAIGHT_FLUSH = 8;
    public static final int TYPE_RANK_FOUR_KIND = 7;
    public static final int TYPE_RANK_FULL_HOUSE = 6;
    public static final int TYPE_RANK_FLUSH = 5;
    public static final int TYPE_RANK_STRAIGHT = 4;
    public static final int TYPE_RANK_THREE_KIND = 3;
    public static final int TYPE_RANK_TWO_PAIRS = 2;
    public static final int TYPE_RANK_ONE_PAIR = 1;
    public static final int TYPE_RANK_HIGH_CARD = 0;

    private int typeRank;
    private T content;

    private HandRank(int typeRank, T content)
    {
        this.typeRank = typeRank;
        this.content = content;
    }

    public static <T> HandRank<T> handRank(int typeRank, T content)
    {
        return new HandRank<>(typeRank, content);
    }

    public int getTypeRank()
    {
        return typeRank;
    }

    public T getContent()
    {
        return content;
    }
}

4.5.10.3 Calculate When Straight Flush

poker.first.HandRankCalculatorTest

public class HandRankCalculatorTest extends TestCase
{
    public void test_calculate_when_straight_flush()
    {
        HandRank<?> actual = calculate(asList(card(2, D), card(3, D), card(4, D), card(5, D), card(6, D)));

        assertEquals(TYPE_RANK_STRAIGHT_FLUSH, actual.getTypeRank());
        assertEquals(StraightFlushHandRankContent.class, actual.getContent().getClass());
        StraightFlushHandRankContent actualStraightFlushHandRankContent = (StraightFlushHandRankContent)actual.getContent();
        assertEquals(6, actualStraightFlushHandRankContent.getMaxRank());
    }
}

poker.first.entity.handrank.StraightFlushHandRankContent

public class StraightFlushHandRankContent
{
    private int maxRank;

    private StraightFlushHandRankContent(int maxRank)
    {
        this.maxRank = maxRank;
    }

    public static StraightFlushHandRankContent straightFlushHandRankContent(int maxRank)
    {
        return new StraightFlushHandRankContent(maxRank);
    }

    public int getMaxRank()
    {
        return maxRank;
    }
}

poker.first.HandRankCalculator

public class HandRankCalculator
{
    private static Map<BiPredicate<List<Integer>, List<Card>>, Function<List<Integer>, HandRank<?>>> predAndCalcFuncMap;
    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
    }

    public static HandRank<?> calculate(List<Card> hand)
    {
        List<Integer> descentRanks = extractDescentRanks(hand);

        return predAndCalcFuncMap.entrySet()
                                 .stream()
                                 .filter(entry -> entry.getKey().test(descentRanks, hand))
                                 .findFirst()
                                 .map(entry -> entry.getValue().apply(descentRanks))
                                 .orElseThrow(IllegalArgumentException::new);
    }

    private static List<Integer> extractDescentRanks(List<Card> hand)
    {
        return hand.stream()
                   .sorted(Comparator.comparing(Card::getRank, (rank1, rank2) -> rank2 - rank1))
                   .map(Card::getRank)
                   .collect(Collectors.toList());
    }

    private static boolean isStraightFlush(List<Integer> descentRanks, List<Card> hand)
    {
        return isStraight(descentRanks) && isFlush(hand);
    }

    private static boolean isFlush(List<Card> hand)
    {
        return hand.stream().map(Card::getSuit).distinct().count() == 1;
    }

    private static boolean isStraight(List<Integer> descentRanks)
    {
        return (descentRanks.stream().distinct().count() == 5) && (descentRanks.get(0) - descentRanks.get(4) == 4);
    }

    private static HandRank<StraightFlushHandRankContent> calcStraightFlush(List<Integer> descentRanks)
    {
        return handRank(TYPE_RANK_STRAIGHT_FLUSH, straightFlushHandRankContent(descentRanks.get(0)));
    }
}

4.5.10.4 Calculate When Four of a Kind

poker.first.HandRankCalculatorTest

    public void test_calculate_when_four_kind()
    {
        HandRank<?> actual = calculate(asList(card(2, D), card(2, H), card(2, C), card(2, S), card(9, C)));

        assertEquals(TYPE_RANK_FOUR_KIND, actual.getTypeRank());
        assertEquals(FourKindHandRankContent.class, actual.getContent().getClass());
        FourKindHandRankContent actualFourKindHandRankContent = (FourKindHandRankContent)actual.getContent();
        assertEquals(2, actualFourKindHandRankContent.getFourKindRank());
        assertEquals(9, actualFourKindHandRankContent.getRestRank());
    }

poker.first.entity.handrank.FourKindHandRankContent

public class FourKindHandRankContent
{
    private int fourKindRank;
    private int restRank;

    private FourKindHandRankContent(int fourKindRank, int restRank)
    {
        this.fourKindRank = fourKindRank;
        this.restRank = restRank;
    }

    public static FourKindHandRankContent fourKindHandRankContent(int fourKindRank, int restRank)
    {
        return new FourKindHandRankContent(fourKindRank, restRank);
    }

    public int getFourKindRank()
    {
        return fourKindRank;
    }

    public int getRestRank()
    {
        return restRank;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
    }

    private static boolean isFourKind(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(4, descentRanks).isPresent();
    }

    private static HandRank<FourKindHandRankContent> calcFourKind(List<Integer> descentRanks)
    {
        Optional<Integer> optFourKindRank = Lists.findByOccurrences(4, descentRanks);
        Optional<Integer> optRestRank = Lists.findByOccurrences(1, descentRanks);
        if(!optFourKindRank.isPresent() || !optRestRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match FourKind.");
        }

        return handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(optFourKindRank.get(), optRestRank.get()));
    }

Wishful Thinking,我们需要在列表中根据元素出现的次数找到元素,Java的JDK没有提供现成的工具,自己制造。

poker.first.util.ListsTest

public class ListsTest extends TestCase
{
    public void test_findByOccurrences()
    {
        assertEquals(Optional.of(4), findByOccurrences(4, asList(4, 4, 4, 4, 5)));
        assertEquals(Optional.of(4), findByOccurrences(3, asList(3, 4, 5, 4, 4)));
        assertEquals(Optional.of(4), findByOccurrences(2, asList(2, 4, 5, 3, 4)));
        assertEquals(Optional.of(4), findByOccurrences(2, asList(4, 4, 2, 5, 5)));
        assertEquals(Optional.of(1), findByOccurrences(1, asList(1, 2, 3, 4, 5)));
        assertEquals(Optional.empty(), findByOccurrences(6, asList(5, 4, 3, 2, 1)));
    }
}

poker.first.util.Lists

public class Lists
{
    public static Optional<Integer> findByOccurrences(int occurrences, List<Integer> list)
    {
        return list.stream()
                   .map(ele -> asList(ele, Collections.frequency(list, ele)))
                   .filter(eleAndFreq -> eleAndFreq.get(1) == occurrences)
                   .findFirst()
                   .map(eleAndFreq -> eleAndFreq.get(0));
    }
}

4.5.10.5 Calculate When Full House

poker.first.HandRankCalculatorTest

    public void test_calculate_when_full_house()
    {
        HandRank<?> actual = calculate(asList(card(3, D), card(3, H), card(3, C), card(4, S), card(4, D)));

        assertEquals(TYPE_RANK_FULL_HOUSE, actual.getTypeRank());
        assertEquals(FullHouseHandRankContent.class, actual.getContent().getClass());
        FullHouseHandRankContent actualFullHouseHandRankContent = (FullHouseHandRankContent)actual.getContent();
        assertEquals(3, actualFullHouseHandRankContent.getThreeKindRank());
        assertEquals(4, actualFullHouseHandRankContent.getTwoKindRank());
    }

poker.first.entity.handrank.FullHouseHandRankContent

public class FullHouseHandRankContent
{
    private int threeKindRank;
    private int twoKindRank;

    private FullHouseHandRankContent(int threeKindRank, int twoKindRank)
    {
        this.threeKindRank = threeKindRank;
        this.twoKindRank = twoKindRank;
    }

    public static FullHouseHandRankContent fullHouseHandRankContent(int threeKindRank, int twoKindRank)
    {
        return new FullHouseHandRankContent(threeKindRank, twoKindRank);
    }

    public int getThreeKindRank()
    {
        return threeKindRank;
    }

    public int getTwoKindRank()
    {
        return twoKindRank;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
    }

    private static boolean isFullHouse(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(3, descentRanks).isPresent()
                && Lists.findByOccurrences(2, descentRanks).isPresent();
    }

    private static HandRank<FullHouseHandRankContent> calcFullHouse(List<Integer> descentRanks)
    {
        Optional<Integer> optThreeKindRank = Lists.findByOccurrences(3, descentRanks);
        Optional<Integer> optTwoKindRank = Lists.findByOccurrences(2, descentRanks);
        if(!optThreeKindRank.isPresent() || !optTwoKindRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match FullHouse.");
        }

        return handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(optThreeKindRank.get(), optTwoKindRank.get()));
    }

4.5.10.6 Calculate When Flush

poker.first.HandRankCalculatorTest

    public void test_calculate_when_flush()
    {
        HandRank<?> actual = calculate(asList(card(2, D), card(3, D), card(5, D), card(7, D), card(9, D)));

        assertEquals(TYPE_RANK_FLUSH, actual.getTypeRank());
        assertEquals(FlushHandRankContent.class, actual.getContent().getClass());
        FlushHandRankContent actualFlushHandRankContent = (FlushHandRankContent)actual.getContent();
        assertEquals(asList(9, 7, 5, 3, 2), actualFlushHandRankContent.getRanks());
    }

poker.first.entity.handrank.FlushHandRankContent

public class FlushHandRankContent
{
    private List<Integer> ranks;

    private FlushHandRankContent(List<Integer> ranks)
    {
        this.ranks = ranks;
    }

    public static FlushHandRankContent flushHandRankContent(List<Integer> ranks)
    {
        return new FlushHandRankContent(ranks);
    }

    public List<Integer> getRanks()
    {
        return ranks;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
    }

    private static boolean isFlush(List<Integer> descentRanks, List<Card> hand)
    {
        return isFlush(hand);
    }

    private static HandRank<FlushHandRankContent> calcFlush(List<Integer> descentRanks)
    {
        return handRank(TYPE_RANK_FLUSH, flushHandRankContent(descentRanks));
    }

4.5.10.7 Calculate When Straight

poker.first.HandRankCalculatorTest

    public void test_calculate_when_straight()
    {
        HandRank<?> actual = calculate(asList(card(3, S), card(4, D), card(5, C), card(6, D), card(7, C)));

        assertEquals(TYPE_RANK_STRAIGHT, actual.getTypeRank());
        assertEquals(StraightHandRankContent.class, actual.getContent().getClass());
        StraightHandRankContent actualStraightHandRankContent = (StraightHandRankContent)actual.getContent();
        assertEquals(7, actualStraightHandRankContent.getMaxRank());
    }

poker.first.entity.handrank.StraightHandRankContent

public class StraightHandRankContent
{
    private int maxRank;

    private StraightHandRankContent(int maxRank)
    {
        this.maxRank = maxRank;
    }

    public static StraightHandRankContent straightHandRankContent(int maxRank)
    {
        return new StraightHandRankContent(maxRank);
    }

    public int getMaxRank()
    {
        return maxRank;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
    }

    private static boolean isStraight(List<Integer> descentRanks, List<Card> hand)
    {
        return isStraight(descentRanks);
    }

    private static HandRank<StraightHandRankContent> calcStraight(List<Integer> descentRanks)
    {
        return handRank(TYPE_RANK_STRAIGHT, straightHandRankContent(descentRanks.get(0)));
    }

4.5.10.8 Calculate When Three of a Kind

poker.first.HandRankCalculatorTest

    public void test_calculate_when_three_kind()
    {
        HandRank<?> actual = calculate(asList(card(3, D), card(3, C), card(3, S), card(2, D), card(7, C)));

        assertEquals(TYPE_RANK_THREE_KIND, actual.getTypeRank());
        assertEquals(ThreeKindHandRankContent.class, actual.getContent().getClass());
        ThreeKindHandRankContent actualThreeKindHandRankContent = (ThreeKindHandRankContent)actual.getContent();
        assertEquals(3, actualThreeKindHandRankContent.getThreeKindRank());
        assertEquals(asList(7, 2), actualThreeKindHandRankContent.getRestRanks());
    }

poker.first.entity.handrank.ThreeKindHandRankContent

public class ThreeKindHandRankContent
{
    private int threeKindRank;
    private List<Integer> restRanks;

    private ThreeKindHandRankContent(int threeKindRank, List<Integer> restRanks)
    {
        this.threeKindRank = threeKindRank;
        this.restRanks = restRanks;
    }

    public static ThreeKindHandRankContent threeKindHandRankContent(int threeKindRank, List<Integer> restRanks)
    {
        return new ThreeKindHandRankContent(threeKindRank, restRanks);
    }

    public int getThreeKindRank()
    {
        return threeKindRank;
    }

    public List<Integer> getRestRanks()
    {
        return restRanks;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
    }

    private static boolean isThreeKind(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(3, descentRanks).isPresent();
    }

    private static HandRank<ThreeKindHandRankContent> calcThreeKind(List<Integer> descentRanks)
    {
        Optional<Integer> optThreeKindRank = Lists.findByOccurrences(3, descentRanks);
        if(!optThreeKindRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match ThreeKind.");
        }

        int threeKindRank = optThreeKindRank.get();
        List<Integer> restRanks = Lists.filterOut(threeKindRank, descentRanks);
        return handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(threeKindRank, restRanks));
    }

Wishful Thinking,我们需要过滤掉列表中的指定元素,Java的JDK没有提供现成的工具,自己制造。

poker.first.util.ListsTest

    public void test_filterOut()
    {
        assertEquals(asList(), filterOut(2, asList()));
        assertEquals(asList(3, 5), filterOut(4, asList(3, 4, 5, 4, 4)));
        assertEquals(asList(3, 4, 5, 4, 4), filterOut(2, asList(3, 4, 5, 4, 4)));
        assertEquals(asList(3, 4, 4, 4), filterOut(5, asList(3, 4, 5, 4, 4)));
    }

poker.first.util.Lists

    public static List<Integer> filterOut(int filteredOutEle, List<Integer> list)
    {
        return list.stream().filter(ele -> ele != filteredOutEle).collect(Collectors.toList());
    }

4.5.10.9 Calculate When Two Pairs

poker.first.HandRankCalculatorTest

    public void test_calculate_when_two_pairs()
    {
        HandRank<?> actual = calculate(asList(card(3, D), card(3, C), card(4, H), card(4, S), card(6, D)));

        assertEquals(TYPE_RANK_TWO_PAIRS, actual.getTypeRank());
        assertEquals(TwoPairsHandRankContent.class, actual.getContent().getClass());
        TwoPairsHandRankContent actualTwoPairsHandRankContent = (TwoPairsHandRankContent)actual.getContent();
        assertEquals(asList(4, 3), actualTwoPairsHandRankContent.getTwoPairsRanks());
        assertEquals(6, actualTwoPairsHandRankContent.getRestRank());
    }

poker.first.entity.handrank.TwoPairsHandRankContent

public class TwoPairsHandRankContent
{
    private List<Integer> twoPairsRanks;
    private int restRank;

    private TwoPairsHandRankContent(List<Integer> twoPairsRanks, int restRank)
    {
        this.twoPairsRanks = twoPairsRanks;
        this.restRank = restRank;
    }

    public static TwoPairsHandRankContent twoPairsHandRankContent(List<Integer> twoPairsRanks, int restRank)
    {
        return new TwoPairsHandRankContent(twoPairsRanks, restRank);
    }

    public List<Integer> getTwoPairsRanks()
    {
        return twoPairsRanks;
    }

    public int getRestRank()
    {
        return restRank;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
    }

    private static boolean isTwoPairs(List<Integer> descentRanks, List<Card> hand)
    {
        Optional<Integer> optFirstPairRank = Lists.findByOccurrences(2, descentRanks);
        Optional<Integer> optSecondPairRank = Lists.findByOccurrences(2, Lists.reverse(descentRanks));
        return optFirstPairRank.isPresent() && optSecondPairRank.isPresent()
                && optFirstPairRank.get() != optSecondPairRank.get();
    }

    private static HandRank<TwoPairsHandRankContent> calcTwoPairs(List<Integer> descentRanks)
    {
        Optional<Integer> optFirstPairRank = Lists.findByOccurrences(2, descentRanks);
        Optional<Integer> optSecondPairRank = Lists.findByOccurrences(2, Lists.reverse(descentRanks));
        Optional<Integer> optRestRank = Lists.findByOccurrences(1, descentRanks);
        if(!optFirstPairRank.isPresent() || !optSecondPairRank.isPresent() || optFirstPairRank.get() == optSecondPairRank.get())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match TwoPairs.");
        }

        return handRank(TYPE_RANK_TWO_PAIRS,
                twoPairsHandRankContent(asList(optFirstPairRank.get(), optSecondPairRank.get()), optRestRank.get()));
    }

在isTwoPairs()方法中,除了判断第一个对子和第二个对子都要存在以外,还要判断这两个对子的分值不相等。这是因为根据目前的实现方法,如果不要求两个对子的分值不相等,只有一个对子的一手牌也会被误判为两个对子。

Wishful Thinking,我们需要将列表倒序,且不影响原有列表,Java的JDK没有提供现成的工具,自己制造。

poker.first.util.ListsTest

    public void test_reverse()
    {
        assertEquals(asList(), reverse(asList()));
        assertEquals(asList(1, 2, 3, 4, 5), reverse(asList(5, 4, 3, 2, 1)));
        assertEquals(asList(2, 2, 3, 5, 5), reverse(asList(5, 5, 3, 2, 2)));
    }

poker.first.util.Lists

    public static List<Integer> reverse(List<Integer> list)
    {
        List<Integer> result = new ArrayList<>(list);
        Collections.reverse(result);
        return result;
    }

4.5.10.10 Calculate When One Pair

poker.first.HandRankCalculatorTest

    public void test_calculate_when_one_pair()
    {
        HandRank<?> actual = calculate(asList(card(2, D), card(3, C), card(3, H), card(9, D), card(7, C)));

        assertEquals(TYPE_RANK_ONE_PAIR, actual.getTypeRank());
        assertEquals(OnePairHandRankContent.class, actual.getContent().getClass());
        OnePairHandRankContent actualOnePairHandRankContent = (OnePairHandRankContent)actual.getContent();
        assertEquals(3, actualOnePairHandRankContent.getOnePairRank());
        assertEquals(asList(9, 7, 2), actualOnePairHandRankContent.getRestRanks());
    }

poker.first.entity.handrank.OnePairHandRankContent

public class OnePairHandRankContent
{
    private int onePairRank;
    private List<Integer> restRanks;

    private OnePairHandRankContent(int onePairRank, List<Integer> restRanks)
    {
        this.onePairRank = onePairRank;
        this.restRanks = restRanks;
    }

    public static OnePairHandRankContent onePairHandRankContent(int onePairRank, List<Integer> restRanks)
    {
        return new OnePairHandRankContent(onePairRank, restRanks);
    }

    public int getOnePairRank()
    {
        return onePairRank;
    }

    public List<Integer> getRestRanks()
    {
        return restRanks;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
        predAndCalcFuncMap.put(HandRankCalculator::isOnePair, HandRankCalculator::calcOnePair);
    }

    private static boolean isOnePair(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(2, descentRanks).isPresent();
    }

    private static HandRank<OnePairHandRankContent> calcOnePair(List<Integer> descentRanks)
    {
        Optional<Integer> optOnePairRank = Lists.findByOccurrences(2, descentRanks);
        if(!optOnePairRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match OnePair.");
        }

        int onePairRank = optOnePairRank.get();
        List<Integer> restRanks = Lists.filterOut(onePairRank, descentRanks);
        return handRank(TYPE_RANK_ONE_PAIR, onePairHandRankContent(onePairRank, restRanks));
    }

4.5.10.11 Calculate When High Card

poker.first.HandRankCalculatorTest

    public void test_calculate_when_high_card()
    {
        HandRank<?> actual = calculate(asList(card(7, D), card(2, C), card(3, H), card(4, C), card(9, S)));

        assertEquals(TYPE_RANK_HIGH_CARD, actual.getTypeRank());
        assertEquals(HighCardHandRankContent.class, actual.getContent().getClass());
        HighCardHandRankContent actualHighCardHandRankContent = (HighCardHandRankContent)actual.getContent();
        assertEquals(asList(9, 7, 4, 3, 2), actualHighCardHandRankContent.getRanks());
    }

poker.first.entity.handrank.HighCardHandRankContent

public class HighCardHandRankContent
{
    private List<Integer> ranks;

    private HighCardHandRankContent(List<Integer> ranks)
    {
        this.ranks = ranks;
    }

    public static HighCardHandRankContent highCardHandRankContent(List<Integer> ranks)
    {
        return new HighCardHandRankContent(ranks);
    }

    public List<Integer> getRanks()
    {
        return ranks;
    }
}

poker.first.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
        predAndCalcFuncMap.put(HandRankCalculator::isOnePair, HandRankCalculator::calcOnePair);
        predAndCalcFuncMap.put(HandRankCalculator::isHighCard, HandRankCalculator::calcHighCard);
    }

    private static boolean isHighCard(List<Integer> descentRanks, List<Card> hand)
    {
        return true;
    }

    private static HandRank<HighCardHandRankContent> calcHighCard(List<Integer> descentRanks)
    {
        return handRank(TYPE_RANK_HIGH_CARD, highCardHandRankContent(descentRanks));
    }

4.5.11 Implementation - HandRankComparator

4.5.11.1 Compare When Different Hand Type Ranks

poker.first.HandRankComparatorTest

public class HandRankComparatorTest extends TestCase
{
    public void test_compare_when_different_hand_type_ranks()
    {
        HandRank<StraightFlushHandRankContent> straightFlushHandRank =
                handRank(TYPE_RANK_STRAIGHT_FLUSH, straightFlushHandRankContent(6));
        HandRank<FourKindHandRankContent> fourKindHandRank =
                handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(2, 9));
        HandRank<FullHouseHandRankContent> fullHouseHandRank =
                handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(3, 4));
        HandRank<FlushHandRankContent> flushHandRank =
                handRank(TYPE_RANK_FLUSH, flushHandRankContent(asList(9, 7, 5, 3, 2)));
        HandRank<StraightHandRankContent> straightHandRank =
                handRank(TYPE_RANK_STRAIGHT, straightHandRankContent(7));
        HandRank<ThreeKindHandRankContent> threeKindHandRank =
                handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(3, asList(7, 2)));
        HandRank<TwoPairsHandRankContent> twoPairsHandRank =
                handRank(TYPE_RANK_TWO_PAIRS, twoPairsHandRankContent(asList(4, 3), 6));
        HandRank<OnePairHandRankContent> onePairHandRank =
                handRank(TYPE_RANK_ONE_PAIR, onePairHandRankContent(3, asList(9, 7, 2)));
        HandRank<HighCardHandRankContent> highCardHandRank =
                handRank(TYPE_RANK_HIGH_CARD, highCardHandRankContent(asList(9, 7, 4, 3, 2)));

        assertTrue(compare(straightFlushHandRank, fourKindHandRank) > 0);
        assertTrue(compare(fourKindHandRank, fullHouseHandRank) > 0);
        assertTrue(compare(fullHouseHandRank, flushHandRank) > 0);
        assertTrue(compare(flushHandRank, straightHandRank) > 0);
        assertTrue(compare(straightHandRank, threeKindHandRank) > 0);
        assertTrue(compare(threeKindHandRank, twoPairsHandRank) > 0);
        assertTrue(compare(twoPairsHandRank, onePairHandRank) > 0);
        assertTrue(compare(onePairHandRank, highCardHandRank) > 0);
    }
}

poker.first.HandRankComparator

public class HandRankComparator
{
    public static int compare(HandRank<?> handRank1, HandRank<?> handRank2)
    {
        return handRank1.getTypeRank() != handRank2.getTypeRank()
                ? handRank1.getTypeRank() - handRank2.getTypeRank()
                : compareHandRankContent(handRank1.getContent(), handRank2.getContent());
    }

    private static int compareHandRankContent(Object content1, Object content2)
    {
        return 0;
    }
}

4.5.11.2 Compare Straight Flush Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_straight_flush_hand_ranks()
    {
        HandRank<StraightFlushHandRankContent> straightFlushHandRank1 =
                handRank(TYPE_RANK_STRAIGHT_FLUSH, straightFlushHandRankContent(4));
        HandRank<StraightFlushHandRankContent> straightFlushHandRank2 =
                handRank(TYPE_RANK_STRAIGHT_FLUSH, straightFlushHandRankContent(5));
        HandRank<StraightFlushHandRankContent> straightFlushHandRank3 =
                handRank(TYPE_RANK_STRAIGHT_FLUSH, straightFlushHandRankContent(6));

        assertTrue(compare(straightFlushHandRank1, straightFlushHandRank2) < 0);
        assertTrue(compare(straightFlushHandRank2, straightFlushHandRank2) == 0);
        assertTrue(compare(straightFlushHandRank3, straightFlushHandRank2) > 0);
    }

poker.first.HandRankComparator

    private static Map<Class<?>, BiFunction<Object, Object, Integer>> contentClassAndCompareFuncMap;
    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
    }

    private static int compareHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != content2.getClass())
        {
            throw new IllegalArgumentException("The classes of two contents are different.");
        }

        if(!contentClassAndCompareFuncMap.containsKey(content1.getClass()))
        {
            throw new IllegalArgumentException("The classes of two contents are out of range.");
        }

        return contentClassAndCompareFuncMap.get(content1.getClass()).apply(content1, content2);
    }

    private static int compareStraightFlushHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != StraightFlushHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of StraightFlushHandRankContent.");
        }

        StraightFlushHandRankContent straightFlushHandRankContent1 = (StraightFlushHandRankContent)content1;
        StraightFlushHandRankContent straightFlushHandRankContent2 = (StraightFlushHandRankContent)content2;
        return straightFlushHandRankContent1.getMaxRank() - straightFlushHandRankContent2.getMaxRank();
    }

4.5.11.3 Compare Four of a Kind Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_four_kind_hand_ranks()
    {
        HandRank<FourKindHandRankContent> fourKindHandRank1 =
                handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(4, 3));
        HandRank<FourKindHandRankContent> fourKindHandRank2 =
                handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(5, 3));
        HandRank<FourKindHandRankContent> fourKindHandRank3 =
                handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(6, 4));
        HandRank<FourKindHandRankContent> fourKindHandRank4 =
                handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(4, 6));

        assertTrue(compare(fourKindHandRank1, fourKindHandRank2) < 0);
        assertTrue(compare(fourKindHandRank2, fourKindHandRank2) == 0);
        assertTrue(compare(fourKindHandRank3, fourKindHandRank2) > 0);

        assertTrue(compare(fourKindHandRank4, fourKindHandRank2) < 0);
        assertTrue(compare(fourKindHandRank2, fourKindHandRank4) > 0);

        assertTrue(compare(fourKindHandRank1, fourKindHandRank4) < 0);
        assertTrue(compare(fourKindHandRank4, fourKindHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
    }

    private static int compareFourKindHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != FourKindHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of FourKindHandRankContent.");
        }

        FourKindHandRankContent fourKindHandRankContent1 = (FourKindHandRankContent)content1;
        FourKindHandRankContent fourKindHandRankContent2 = (FourKindHandRankContent)content2;
        return fourKindHandRankContent1.getFourKindRank() != fourKindHandRankContent2.getFourKindRank()
                ? fourKindHandRankContent1.getFourKindRank() - fourKindHandRankContent2.getFourKindRank()
                : fourKindHandRankContent1.getRestRank() - fourKindHandRankContent2.getRestRank();
    }

4.5.11.4 Compare Full House Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_full_house_hand_ranks()
    {
        HandRank<FullHouseHandRankContent> fullHouseHandRank1 =
                handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(4, 3));
        HandRank<FullHouseHandRankContent> fullHouseHandRank2 =
                handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(5, 3));
        HandRank<FullHouseHandRankContent> fullHouseHandRank3 =
                handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(4, 6));

        assertTrue(compare(fullHouseHandRank1, fullHouseHandRank2) < 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank2) == 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank1) > 0);

        assertTrue(compare(fullHouseHandRank3, fullHouseHandRank2) < 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank3) > 0);

        assertTrue(compare(fullHouseHandRank1, fullHouseHandRank3) < 0);
        assertTrue(compare(fullHouseHandRank3, fullHouseHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
    }

    private static int compareFullHouseHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != FullHouseHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of FullHouseHandRankContent.");
        }

        FullHouseHandRankContent fullHouseHandRankContent1 = (FullHouseHandRankContent)content1;
        FullHouseHandRankContent fullHouseHandRankContent2 = (FullHouseHandRankContent)content2;
        return fullHouseHandRankContent1.getThreeKindRank() != fullHouseHandRankContent2.getThreeKindRank()
                ? fullHouseHandRankContent1.getThreeKindRank() - fullHouseHandRankContent2.getThreeKindRank()
                : fullHouseHandRankContent1.getTwoKindRank() - fullHouseHandRankContent2.getTwoKindRank();
    }

4.5.11.5 Compare Flush Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_flush_hand_ranks()
    {
        HandRank<FlushHandRankContent> flushHandRank1 =
                handRank(TYPE_RANK_FLUSH, flushHandRankContent(asList(7, 5, 4, 3, 2)));
        HandRank<FlushHandRankContent> flushHandRank2 =
                handRank(TYPE_RANK_FLUSH, flushHandRankContent(asList(8, 6, 5, 4, 3)));

        assertTrue(compare(flushHandRank1, flushHandRank2) < 0);
        assertTrue(compare(flushHandRank2, flushHandRank2) == 0);
        assertTrue(compare(flushHandRank2, flushHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
    }

    private static int compareFlushHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != FlushHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of FlushHandRankContent.");
        }

        FlushHandRankContent flushHandRankContent1 = (FlushHandRankContent)content1;
        FlushHandRankContent flushHandRankContent2 = (FlushHandRankContent)content2;
        return Lists.compare(flushHandRankContent1.getRanks(), flushHandRankContent2.getRanks());
    }

Wishful Thinking,我们需要比较列表大小,Java的JDK没有提供现成的工具,自己制造。

poker.first.util.ListsTest

    public void test_compare()
    {
        assertTrue(compare(asList(), asList()) == 0);
        assertTrue(compare(asList(6, 5, 4, 3, 2), asList(7, 6, 5, 4, 3)) < 0);
        assertTrue(compare(asList(6, 5, 4, 3, 2), asList(6, 5, 4, 3, 2)) == 0);
        assertTrue(compare(asList(7, 5, 4, 3, 2), asList(6, 6, 4, 3, 2)) > 0);
    }

poker.first.util.Lists

    public static int compare(List<Integer> list1, List<Integer> list2)
    {
        if(list1.size() != list2.size())
        {
            throw new IllegalArgumentException("The length of list1 is not equal to the length of list2.");
        }

        Iterator<Integer> it1 = list1.iterator();
        Iterator<Integer> it2 = list2.iterator();
        while(it1.hasNext())
        {
            int ele1 = it1.next();
            int ele2 = it2.next();
            if(ele1 == ele2)
            {
                continue;
            }

            return ele1 - ele2;
        }

        return 0;
    }

4.5.11.6 Compare Straight Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_straight_hand_ranks()
    {
        HandRank<StraightHandRankContent> straightHandRank1 =
                handRank(TYPE_RANK_STRAIGHT, straightHandRankContent(4));
        HandRank<StraightHandRankContent> straightHandRank2 =
                handRank(TYPE_RANK_STRAIGHT, straightHandRankContent(5));
        HandRank<StraightHandRankContent> straightHandRank3 =
                handRank(TYPE_RANK_STRAIGHT, straightHandRankContent(6));

        assertTrue(compare(straightHandRank1, straightHandRank2) < 0);
        assertTrue(compare(straightHandRank2, straightHandRank2) == 0);
        assertTrue(compare(straightHandRank3, straightHandRank2) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
        contentClassAndCompareFuncMap.put(StraightHandRankContent.class, HandRankComparator::compareStraightHandRankContent);
    }

    private static int compareStraightHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != StraightHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of StraightHandRankContent.");
        }

        StraightHandRankContent straightHandRankContent1 = (StraightHandRankContent)content1;
        StraightHandRankContent straightHandRankContent2 = (StraightHandRankContent)content2;
        return straightHandRankContent1.getMaxRank() - straightHandRankContent2.getMaxRank();
    }

4.5.11.7 Compare Three of a Kind Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_three_kind_hand_ranks()
    {
        HandRank<ThreeKindHandRankContent> threeKindHandRank1 =
                handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(4, asList(3, 2)));
        HandRank<ThreeKindHandRankContent> threeKindHandRank2 =
                handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(5, asList(3, 2)));
        HandRank<ThreeKindHandRankContent> threeKindHandRank3 =
                handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(4, asList(6, 5)));
        HandRank<ThreeKindHandRankContent> threeKindHandRank4 =
                handRank(TYPE_RANK_THREE_KIND, threeKindHandRankContent(4, asList(3, 5)));

        assertTrue(compare(threeKindHandRank1, threeKindHandRank2) < 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank2) == 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank1) > 0);

        assertTrue(compare(threeKindHandRank3, threeKindHandRank2) < 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank3) > 0);

        assertTrue(compare(threeKindHandRank1, threeKindHandRank3) < 0);
        assertTrue(compare(threeKindHandRank3, threeKindHandRank1) > 0);
        assertTrue(compare(threeKindHandRank4, threeKindHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
        contentClassAndCompareFuncMap.put(StraightHandRankContent.class, HandRankComparator::compareStraightHandRankContent);
        contentClassAndCompareFuncMap.put(ThreeKindHandRankContent.class, HandRankComparator::compareThreeKindHandRankContent);
    }

    private static int compareThreeKindHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != ThreeKindHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of ThreeKindHandRankContent.");
        }

        ThreeKindHandRankContent threeKindHandRankContent1 = (ThreeKindHandRankContent)content1;
        ThreeKindHandRankContent threeKindHandRankContent2 = (ThreeKindHandRankContent)content2;
        return threeKindHandRankContent1.getThreeKindRank() != threeKindHandRankContent2.getThreeKindRank()
                ? threeKindHandRankContent1.getThreeKindRank() - threeKindHandRankContent2.getThreeKindRank()
                : Lists.compare(threeKindHandRankContent1.getRestRanks(), threeKindHandRankContent2.getRestRanks());
    }

4.5.11.8 Compare Two Pairs Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_two_pairs_hand_ranks()
    {
        HandRank<TwoPairsHandRankContent> twoPairsHandRank1 =
                handRank(TYPE_RANK_TWO_PAIRS, twoPairsHandRankContent(asList(5, 4), 2));
        HandRank<TwoPairsHandRankContent> twoPairsHandRank2 =
                handRank(TYPE_RANK_TWO_PAIRS, twoPairsHandRankContent(asList(6, 5), 2));
        HandRank<TwoPairsHandRankContent> twoPairsHandRank3 =
                handRank(TYPE_RANK_TWO_PAIRS, twoPairsHandRankContent(asList(5, 4), 3));

        assertTrue(compare(twoPairsHandRank1, twoPairsHandRank2) < 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank2) == 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank1) > 0);

        assertTrue(compare(twoPairsHandRank3, twoPairsHandRank2) < 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank3) > 0);

        assertTrue(compare(twoPairsHandRank1, twoPairsHandRank3) < 0);
        assertTrue(compare(twoPairsHandRank3, twoPairsHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
        contentClassAndCompareFuncMap.put(StraightHandRankContent.class, HandRankComparator::compareStraightHandRankContent);
        contentClassAndCompareFuncMap.put(ThreeKindHandRankContent.class, HandRankComparator::compareThreeKindHandRankContent);
        contentClassAndCompareFuncMap.put(TwoPairsHandRankContent.class, HandRankComparator::compareTwoPairsHandRankContent);
    }

    private static int compareTwoPairsHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != TwoPairsHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of TwoPairsHandRankContent.");
        }

        TwoPairsHandRankContent twoPairsHandRankContent1 = (TwoPairsHandRankContent)content1;
        TwoPairsHandRankContent twoPairsHandRankContent2 = (TwoPairsHandRankContent)content2;
        int twoPairsComparedResult = Lists.compare(twoPairsHandRankContent1.getTwoPairsRanks(), twoPairsHandRankContent2.getTwoPairsRanks());
        return twoPairsComparedResult != 0
                ? twoPairsComparedResult
                : twoPairsHandRankContent1.getRestRank() - twoPairsHandRankContent2.getRestRank();
    }

4.5.11.9 Compare One Pair Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_one_pair_hand_ranks()
    {
        HandRank<OnePairHandRankContent> onePairHandRank1 =
                handRank(TYPE_RANK_ONE_PAIR, onePairHandRankContent(5, asList(4, 3, 2)));
        HandRank<OnePairHandRankContent> onePairHandRank2 =
                handRank(TYPE_RANK_ONE_PAIR, onePairHandRankContent(6, asList(4, 3, 2)));
        HandRank<OnePairHandRankContent> onePairHandRank3 =
                handRank(TYPE_RANK_ONE_PAIR, onePairHandRankContent(5, asList(7, 6, 4)));

        assertTrue(compare(onePairHandRank1, onePairHandRank2) < 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank2) == 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank1) > 0);

        assertTrue(compare(onePairHandRank3, onePairHandRank2) < 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank3) > 0);

        assertTrue(compare(onePairHandRank1, onePairHandRank3) < 0);
        assertTrue(compare(onePairHandRank3, onePairHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
        contentClassAndCompareFuncMap.put(StraightHandRankContent.class, HandRankComparator::compareStraightHandRankContent);
        contentClassAndCompareFuncMap.put(ThreeKindHandRankContent.class, HandRankComparator::compareThreeKindHandRankContent);
        contentClassAndCompareFuncMap.put(TwoPairsHandRankContent.class, HandRankComparator::compareTwoPairsHandRankContent);
        contentClassAndCompareFuncMap.put(OnePairHandRankContent.class, HandRankComparator::compareOnePairHandRankContent);
    }

    private static int compareOnePairHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != OnePairHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of OnePairHandRankContent.");
        }

        OnePairHandRankContent onePairHandRankContent1 = (OnePairHandRankContent)content1;
        OnePairHandRankContent onePairHandRankContent2 = (OnePairHandRankContent)content2;
        return onePairHandRankContent1.getOnePairRank() != onePairHandRankContent2.getOnePairRank()
                ? onePairHandRankContent1.getOnePairRank() - onePairHandRankContent2.getOnePairRank()
                : Lists.compare(onePairHandRankContent1.getRestRanks(), onePairHandRankContent2.getRestRanks());
    }

4.5.11.10 Compare High Card Hand Ranks

poker.first.HandRankComparatorTest

    public void test_compare_high_card_hand_ranks()
    {
        HandRank<HighCardHandRankContent> highCardHandRank1 =
                handRank(TYPE_RANK_HIGH_CARD, highCardHandRankContent(asList(7, 5, 4, 3, 2)));
        HandRank<HighCardHandRankContent> highCardHandRank2 =
                handRank(TYPE_RANK_HIGH_CARD, highCardHandRankContent(asList(8, 7, 5, 4, 3)));

        assertTrue(compare(highCardHandRank1, highCardHandRank2) < 0);
        assertTrue(compare(highCardHandRank2, highCardHandRank2) == 0);
        assertTrue(compare(highCardHandRank2, highCardHandRank1) > 0);
    }

poker.first.HandRankComparator

    static
    {
        contentClassAndCompareFuncMap = new HashMap<>();
        contentClassAndCompareFuncMap.put(StraightFlushHandRankContent.class, HandRankComparator::compareStraightFlushHandRankContent);
        contentClassAndCompareFuncMap.put(FourKindHandRankContent.class, HandRankComparator::compareFourKindHandRankContent);
        contentClassAndCompareFuncMap.put(FullHouseHandRankContent.class, HandRankComparator::compareFullHouseHandRankContent);
        contentClassAndCompareFuncMap.put(FlushHandRankContent.class, HandRankComparator::compareFlushHandRankContent);
        contentClassAndCompareFuncMap.put(StraightHandRankContent.class, HandRankComparator::compareStraightHandRankContent);
        contentClassAndCompareFuncMap.put(ThreeKindHandRankContent.class, HandRankComparator::compareThreeKindHandRankContent);
        contentClassAndCompareFuncMap.put(TwoPairsHandRankContent.class, HandRankComparator::compareTwoPairsHandRankContent);
        contentClassAndCompareFuncMap.put(OnePairHandRankContent.class, HandRankComparator::compareOnePairHandRankContent);
        contentClassAndCompareFuncMap.put(HighCardHandRankContent.class, HandRankComparator::compareHighCardHandRankContent);
    }

    private static int compareHighCardHandRankContent(Object content1, Object content2)
    {
        if(content1.getClass() != HighCardHandRankContent.class)
        {
            throw new IllegalArgumentException("The content is not an instance of HighCardHandRankContent.");
        }

        HighCardHandRankContent highCardHandRankContent1 = (HighCardHandRankContent)content1;
        HighCardHandRankContent highCardHandRankContent2 = (HighCardHandRankContent)content2;
        return Lists.compare(highCardHandRankContent1.getRanks(), highCardHandRankContent2.getRanks());
    }

4.5.12 Implementation - PokerEngine

poker.first.PokerEngineTest

public class PokerEngineTest extends TestCase
{
    private static final List<Card> straightFlush = asList(card(2, D), card(3, D), card(4, D), card(5, D), card(6, D));
    private static final List<Card> straightFlushHigher = asList(card(3, C), card(4, C), card(5, C), card(6, C), card(7, C));

    private static final List<Card> fourKind = asList(card(2, D), card(2, H), card(2, C), card(2, S), card(9, C));
    private static final List<Card> fourKindHigher = asList(card(3, D), card(3, H), card(3, C), card(3, S), card(9, C));
    private static final List<Card> fourKindHigher2 = asList(card(3, D), card(3, H), card(3, C), card(3, S), card(10, C));

    private static final List<Card> fullHouse = asList(card(3, D), card(3, H), card(3, C), card(4, S), card(4, D));
    private static final List<Card> fullHouseHigher = asList(card(4, D), card(4, H), card(4, S), card(2, H), card(2, C));
    private static final List<Card> fullHouseHigher2 = asList(card(4, D), card(4, H), card(4, S), card(3, H), card(3, C));

    private static final List<Card> flush = asList(card(6, D), card(3, D), card(5, D), card(11, D), card(12, D));
    private static final List<Card> flushHigher = asList(card(2, C), card(3, C), card(4, C), card(11, C), card(14, C));

    private static final List<Card> straight = asList(card(2, C), card(3, S), card(4, D), card(5, C), card(6, D));
    private static final List<Card> straightHigher = asList(card(3, S), card(4, D), card(5, C), card(6, D), card(7, C));

    private static final List<Card> threeKind = asList(card(2, D), card(2, C), card(2, S), card(10, S), card(12, H));
    private static final List<Card> threeKindHigher = asList(card(3, D), card(3, C), card(3, H), card(9, S), card(13, H));
    private static final List<Card> threeKindHigher2 = asList(card(3, D), card(3, C), card(3, H), card(10, S), card(13, H));

    private static final List<Card> twoPairs = asList(card(3, D), card(3, C), card(4, H), card(4, S), card(6, D));
    private static final List<Card> twoPairsHigher = asList(card(2, D), card(2, C), card(5, H), card(5, S), card(6, D));
    private static final List<Card> twoPairsHigher2 = asList(card(2, D), card(2, C), card(5, H), card(5, S), card(7, D));

    private static final List<Card> onePair = asList(card(3, D), card(3, C), card(2, H), card(9, S), card(11, C));
    private static final List<Card> onePairHigher = asList(card(4, D), card(4, C), card(2, H), card(9, S), card(11, C));
    private static final List<Card> onePairHigher2 = asList(card(4, D), card(4, C), card(14, H), card(3, D), card(7, C));

    private static final List<Card> highCard = asList(card(7, D), card(2, C), card(3, H), card(4, C), card(9, S));
    private static final List<Card> highCardHigher = asList(card(7, D), card(2, C), card(3, H), card(4, C), card(10, S));

    public void test_run()
    {
        assertEquals(straightFlush, PokerEngine.run(asList(straightFlush, fourKind)));
        assertEquals(fourKind, PokerEngine.run(asList(fourKind, fullHouse)));
        assertEquals(fullHouse, PokerEngine.run(asList(fullHouse, flush)));
        assertEquals(flush, PokerEngine.run(asList(flush, straight)));
        assertEquals(straight, PokerEngine.run(asList(straight, threeKind)));
        assertEquals(threeKind, PokerEngine.run(asList(threeKind, twoPairs)));
        assertEquals(twoPairs, PokerEngine.run(asList(twoPairs, onePair)));
        assertEquals(onePair, PokerEngine.run(asList(onePair, highCard)));

        assertEquals(straightFlushHigher, PokerEngine.run(asList(straightFlush, straightFlushHigher)));

        assertEquals(fourKindHigher, PokerEngine.run(asList(fourKind, fourKindHigher)));
        assertEquals(fourKindHigher2, PokerEngine.run(asList(fourKindHigher, fourKindHigher2)));

        assertEquals(fullHouseHigher, PokerEngine.run(asList(fullHouse, fullHouseHigher)));
        assertEquals(fullHouseHigher2, PokerEngine.run(asList(fullHouseHigher, fullHouseHigher2)));

        assertEquals(flushHigher, PokerEngine.run(asList(flush, flushHigher)));

        assertEquals(straightHigher, PokerEngine.run(asList(straight, straightHigher)));

        assertEquals(threeKindHigher, PokerEngine.run(asList(threeKind, threeKindHigher)));
        assertEquals(threeKindHigher2, PokerEngine.run(asList(threeKindHigher, threeKindHigher2)));

        assertEquals(twoPairsHigher, PokerEngine.run(asList(twoPairs, twoPairsHigher)));
        assertEquals(twoPairsHigher2, PokerEngine.run(asList(twoPairsHigher, twoPairsHigher2)));

        assertEquals(onePairHigher, PokerEngine.run(asList(onePair, onePairHigher)));
        assertEquals(onePairHigher2, PokerEngine.run(asList(onePairHigher, onePairHigher2)));

        assertEquals(highCardHigher, PokerEngine.run(asList(highCard, highCardHigher)));

        assertEquals(straightFlush, PokerEngine.run(asList(straightFlush, fourKind, fullHouse, flush,
                straight, threeKind, twoPairs, onePair, highCard)));

        assertEquals(highCard, PokerEngine.run(asList(highCard)));
    }
}

poker.first.PokerEngine

public class PokerEngine
{
    public static List<Card> run(List<List<Card>> hands)
    {
        return Collections.max(hands, Comparator.comparing(HandRankCalculator::calculate, HandRankComparator::compare));
    }
}

到这里Poker Engine就完成了。

4.6 Poker Engine UI

4.6.1 Wishful Thinking - "play" Method

Poker Engine UI的入口方法命名为play(),大概长成下面这个样子:

    String play(List<String> handStrs)
    {
        return toStr(PokerEngine.run(toHands(handStrs)));
    }

先将多手字符串类型的牌转换成多手List<Card>类型的牌,传给PokerEngine.run()方法,PokerEngine.run()方法计算出分值最高的那手牌,再将那手牌由List<Card>类型转换为字符串类型。

4.6.2 Implementation - PokerEngineUI

4.6.2.1 First Test Case

poker.first.PokerEngineUITest

public class PokerEngineUITest extends TestCase
{
    private static final String straightFlush = "2D 3D 4D 5D 6D";

    private static final String fourKind = "2D 2H 2C 2S 9C";

    public void test_play()
    {
        assertEquals(straightFlush, PokerEngineUI.play(asList(straightFlush, fourKind)));
    }
}

poker.first.PokerEngineUI

public class PokerEngineUI
{
    private static final String RANK_CHARS = "--23456789TJQKA";

    public static String play(List<String> handStrs)
    {
        return toStr(PokerEngine.run(toHands(handStrs)));
    }

    private static List<List<Card>> toHands(List<String> handStrs)
    {
        return handStrs.stream().map(PokerEngineUI::toHand).collect(Collectors.toList());
    }

    private static List<Card> toHand(String handStr)
    {
        String[] cardStrs = handStr.trim().split(" +");
        return Stream.of(cardStrs)
                     .map(cardStr -> card(toRank(cardStr.charAt(0)), toSuit(cardStr.charAt(1))))
                     .collect(Collectors.toList());
    }

    private static int toRank(char rankChar)
    {
        return RANK_CHARS.indexOf(rankChar);
    }

    private static Suit toSuit(char suitChar)
    {
        return Suit.getEnum(suitChar);
    }

    private static String toStr(List<Card> hand)
    {
        return hand.stream().map(PokerEngineUI::toCardStr).collect(Collectors.joining(" "));
    }

    private static String toCardStr(Card card)
    {
        return String.format("%c%c", toRankChar(card.getRank()), toSuitChar(card.getSuit()));
    }

    private static char toRankChar(int rank)
    {
        return RANK_CHARS.charAt(rank);
    }

    private static char toSuitChar(Suit suit)
    {
        return suit.getAbbrChar();
    }
}

到这里Poker Engine UI主体就基本完成了。

4.6.2.2 All Ordinary Test Cases

poker.first.PokerEngineUITest

    private static final String straightFlush = "2D 3D 4D 5D 6D";
    private static final String straightFlushHigher = "3C 4C 5C 6C 7C";

    private static final String fourKind = "2D 2H 2C 2S 9C";
    private static final String fourKindHigher = "3D 3H 3C 3S 9C";
    private static final String fourKindHigher2 = "3D 3H 3C 3S TC";

    private static final String fullHouse = "3D 3H 3C 4S 4D";
    private static final String fullHouseHigher = "4D 4H 4S 2H 2C";
    private static final String fullHouseHigher2 = "4D 4H 4S 3H 3C";

    private static final String flush = "6D 3D 5D JD QD";
    private static final String flushHigher = "2C 3C 4C JC AC";

    private static final String straight = "2C 3S 4D 5C 6D";
    private static final String straightHigher = "3S 4D 5C 6D 7C";

    private static final String threeKind = "2D 2C 2S TS QH";
    private static final String threeKindHigher = "3D 3C 3H 9S KH";
    private static final String threeKindHigher2 = "3D 3C 3H TS KH";

    private static final String twoPairs = "3D 3C 4H 4S 6D";
    private static final String twoPairsHigher = "2D 2C 5H 5S 6D";
    private static final String twoPairsHigher2 = "2D 2C 5H 5S 7D";

    private static final String onePair = "3D 3C 2H 9S JC";
    private static final String onePairHigher = "4D 4C 2H 9S JC";
    private static final String onePairHigher2 = "4D 4C AH 3D 7C";

    private static final String highCard = "7D 2C 3H 4C 9S";
    private static final String highCardHigher = "7D 2C 3H 4C TS";

    public void test_play_all()
    {
        assertEquals(straightFlush, PokerEngineUI.play(asList(straightFlush, fourKind)));
        assertEquals(fourKind, PokerEngineUI.play(asList(fourKind, fullHouse)));
        assertEquals(fullHouse, PokerEngineUI.play(asList(fullHouse, flush)));
        assertEquals(flush, PokerEngineUI.play(asList(flush, straight)));
        assertEquals(straight, PokerEngineUI.play(asList(straight, threeKind)));
        assertEquals(threeKind, PokerEngineUI.play(asList(threeKind, twoPairs)));
        assertEquals(twoPairs, PokerEngineUI.play(asList(twoPairs, onePair)));
        assertEquals(onePair, PokerEngineUI.play(asList(onePair, highCard)));

        assertEquals(straightFlushHigher, PokerEngineUI.play(asList(straightFlush, straightFlushHigher)));

        assertEquals(fourKindHigher, PokerEngineUI.play(asList(fourKind, fourKindHigher)));
        assertEquals(fourKindHigher2, PokerEngineUI.play(asList(fourKindHigher, fourKindHigher2)));

        assertEquals(fullHouseHigher, PokerEngineUI.play(asList(fullHouse, fullHouseHigher)));
        assertEquals(fullHouseHigher2, PokerEngineUI.play(asList(fullHouseHigher, fullHouseHigher2)));

        assertEquals(flushHigher, PokerEngineUI.play(asList(flush, flushHigher)));

        assertEquals(straightHigher, PokerEngineUI.play(asList(straight, straightHigher)));

        assertEquals(threeKindHigher, PokerEngineUI.play(asList(threeKind, threeKindHigher)));
        assertEquals(threeKindHigher2, PokerEngineUI.play(asList(threeKindHigher, threeKindHigher2)));

        assertEquals(twoPairsHigher, PokerEngineUI.play(asList(twoPairs, twoPairsHigher)));
        assertEquals(twoPairsHigher2, PokerEngineUI.play(asList(twoPairsHigher, twoPairsHigher2)));

        assertEquals(onePairHigher, PokerEngineUI.play(asList(onePair, onePairHigher)));
        assertEquals(onePairHigher2, PokerEngineUI.play(asList(onePairHigher, onePairHigher2)));

        assertEquals(highCardHigher, PokerEngineUI.play(asList(highCard, highCardHigher)));

        assertEquals(straightFlush, PokerEngineUI.play(asList(straightFlush, fourKind, fullHouse, flush,
                straight, threeKind, twoPairs, onePair, highCard)));

        assertEquals(highCard, PokerEngineUI.play(asList(highCard)));
    }

4.6.2.3 Some Special Cases

Question:程序是否在任何情况下都能正常工作了?是否存在一些边界情况没有想清楚?

4.6.2.3.1 About "A"

Question:假设有这样一手牌:“2S 3H 4C 5D AC”,是个顺子,5张牌中最大分值是多少?

不是14,是5。所以我们需要处理这个特殊场景。

Question:在哪里处理这个特殊场景比较合适?

在HandRankCalculator的extractDescentRanks()方法增加这段处理逻辑比较合适。

poker.first.PokerEngineUITest

    private static final String straight = "2C 3S 4D 5C 6D";
    private static final String straightHigher = "3S 4D 5C 6D 7C";
    private static final String straightIncludingA = "2C 3S 4D 5C AD";

    public void test_play_when_straight_including_A()
    {
        assertEquals(straight, PokerEngineUI.play(asList(straight, straightIncludingA)));
    }

Question:测试用例直接测试通过,这是为什么?

这是因为如果不修改产品代码,straightIncludingA这手牌被判断成了HighCard,测试用例肯定是测试通过的。

Question:增加怎样的测试用例,才能驱动出产品代码?

增加highCardIncludingA相关的测试用例。

poker.first.PokerEngineUITest

    private static final String highCard = "7D 2C 3H 4C 9S";
    private static final String highCardHigher = "7D 2C 3H 4C TS";
    private static final String highCardIncludingA = "2C 3S 4D 6C AD";

    public void test_play_when_straight_including_A()
    {
        assertEquals(straightIncludingA, PokerEngineUI.play(asList(straightIncludingA, highCardIncludingA)));
        assertEquals(straight, PokerEngineUI.play(asList(straight, straightIncludingA)));
    }

poker.first.HandRankCalculator

    private static List<Integer> extractDescentRanks(List<Card> hand)
    {
         List<Integer> descentRanks = hand.stream()
                                          .sorted(Comparator.comparing(Card::getRank, (rank1, rank2) -> rank2 - rank1))
                                          .map(Card::getRank)
                                          .collect(Collectors.toList());

         return asList(14, 5, 4, 3, 2).equals(descentRanks) ? asList(5, 4, 3, 2, 1) : descentRanks;
    }
4.6.2.3.2 About "Tie"

Question:假设有这样两手牌:“2D 3D 4D 5D 6D”和“2C 3C 4C 5C 6C”,哪手牌的分值高?

这是一种“平局(tie)”的特殊场景。从目前的设计和代码来看,哪手牌先出,哪手牌的分值高。

新增相关测试用例验证,确实如此。

poker.first.PokerEngineUITest

    private static final String straightFlush = "2D 3D 4D 5D 6D";
    private static final String straightFlushHigher = "3C 4C 5C 6C 7C";
    private static final String straightFlush2 = "2C 3C 4C 5C 6C";

    public void test_play_when_tie()
    {
        assertEquals(straightFlush, PokerEngineUI.play(asList(straightFlush, straightFlush2)));
        assertEquals(straightFlush2, PokerEngineUI.play(asList(straightFlush2, straightFlush)));
    }

当然,也可以做成“给定多手牌,按照规则找出所有获胜者”。这时,max就不够了,我们需要一个类似于allMax的工具,没有现成的,我们自己制造。留做作业。

5 Improvement of Design & Implementation

Question:重新审视目前的设计和实现,是否有可以改进的地方?

5.1 Duplication in HandRankCalculator

有个原则叫做DRY(Don't Repeat Yourself),即不要重复相同或类似的处理逻辑和计算。

仔细观察目前的代码,在HandRankCalculator中存在很多的重复运算。例如:

    private static boolean isFourKind(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(4, descentRanks).isPresent();
    }

    private static HandRank<FourKindHandRankContent> calcFourKind(List<Integer> descentRanks)
    {
        Optional<Integer> optFourKindRank = Lists.findByOccurrences(4, descentRanks);
        Optional<Integer> optRestRank = Lists.findByOccurrences(1, descentRanks);
        if(!optFourKindRank.isPresent() || !optRestRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match FourKind.");
        }

        return handRank(TYPE_RANK_FOUR_KIND, fourKindHandRankContent(optFourKindRank.get(), optRestRank.get()));
    }

“Lists.findByOccurrences(4, descentRanks)”在isFourKind()方法和calcFourKind()方法中重复出现。

    private static boolean isFullHouse(List<Integer> descentRanks, List<Card> hand)
    {
        return Lists.findByOccurrences(3, descentRanks).isPresent()
                && Lists.findByOccurrences(2, descentRanks).isPresent();
    }

    private static HandRank<FullHouseHandRankContent> calcFullHouse(List<Integer> descentRanks)
    {
        Optional<Integer> optThreeKindRank = Lists.findByOccurrences(3, descentRanks);
        Optional<Integer> optTwoKindRank = Lists.findByOccurrences(2, descentRanks);
        if(!optThreeKindRank.isPresent() || !optTwoKindRank.isPresent())
        {
            throw new IllegalArgumentException("The descentRanks doesn't match FullHouse.");
        }

        return handRank(TYPE_RANK_FULL_HOUSE, fullHouseHandRankContent(optThreeKindRank.get(), optTwoKindRank.get()));
    }

“Lists.findByOccurrences(3, descentRanks)”和“Lists.findByOccurrences(2, descentRanks)”在isFullHouse()方法和calcFullHouse()方法中重复出现。

在判断一手牌的类型和计算一手牌的分值这两个逻辑中存在重复计算。

5.2 How to Eliminate Duplication in HandRankCalculator

Question:如何消除HandRankCalculator中的重复?

5.2.1 What Does the Type of a Hand Mean?

Question:一手牌的类型是什么意思?

Straight Flush、Four of a Kind、Full House、Flush、Straight、Three of a Kind、Two Pairs、One Pair、High Card都是什么意思?

Question:或者换一种问法,什么决定了一手牌的类型?(What determines the type of a hand?)

一手牌的分值分布特征。HandRankCalculator中的重复计算就是在重复计算一手牌的分值分布特征。

“Four of a Kind”的意思是4个分值一样的、1个分值不一样的。这种类型的一手牌的分值分布特征可以表示为“(4, 1)”。 “Full House”的意思是3个分值一样的、2个分值一样的。这种类型的一手牌的分值分布特征可以表示为“(3, 2)”。

Question:大家可以尝试表示出所有类型的一手牌的分值分布特征吗?

  • Straight Flush: (1, 1, 1, 1, 1)
  • Four of a Kind: (4, 1)
  • Full House: (3, 2)
  • Flush: (1, 1, 1, 1, 1)
  • Straight: (1, 1, 1, 1, 1)
  • Three of a Kind: (3, 1, 1)
  • Two Pairs: (2, 2, 1)
  • One Pair: (2, 1, 1, 1)
  • High Card: (1, 1, 1, 1, 1)

需要注意的是,Flush、Straight和Straight Flush类型均无法仅用分值分布特征判定。

Question:仅用上述分值分布特征来比较多手牌的分值,是否足够?

分值分布特征只能决定一手牌的类型。同样类型的多手牌比较大小时,我们还需要知道每个分值分布特征对应的那些牌的分值。这两部分信息合起来称为分值分布

例如:“TC TH TD 3C 3D”和“9D 9C 9H 4D 4S”都是Full House。

“TC TH TD 3C 3D”这手牌用分值分布可以表达为:((3, 2), (10, 3))。 “9D 9C 9H 4D 4S”这手牌用分值分布可以表达为:((3, 2), (9, 4))。

例如:“2D 2C 2S TS QH”和“3D 3C 3H 9S KH”都是Three of a Kind。

“2D 2C 2S TS QH”这手牌用分值分布可以表达为:((3, 1, 1), (2, 12, 10))。 “3D 3C 3H 9S KH”这手牌用分值分布可以表达为:((3, 1, 1), (3, 13, 9))。

分值分布的表达共有两部分,第一部分是分值分布特征,第二部分是每个分值分布特征中出现次数各自对应的分值。分值分布特征中出现次数相等时,分值按降序排列。

5.2.3 How to Formalize "Hand Rank" of a Hand with Rank Distribution?

Question:如何用分值分布形式化地表达一手牌的分值信息?

如果用分值分布形式化地表达,不同类型的一手牌的分值信息都可以大概长成下面这个样子:

    int typeRank;
    List<Integer> distributedRanks;

不再需要根据不同类型定义各种HandRankContent。事实上,各种HandRankContent中也存在数据结构上的重复。

可以看到,引入分值分布后,不仅可以消除HandRankCalculator中的重复计算,连HandRankContent中的重复也被消除了。

而且,这样的两个Hand Rank比较起来也很简单。

5.3 Improvement of Implementation - HandRankCalculator

5.3.1 Card

Card、Suit内容不变,只是包路径变一下。

poker.distribution.entity.Card

public class Card
{
    private int rank;
    private Suit suit;

    private Card(int rank, Suit suit)
    {
        this.rank = rank;
        this.suit = suit;
    }

    public static Card card(int rank, Suit suit)
    {
        return new Card(rank, suit);
    }

    public int getRank()
    {
        return rank;
    }

    public Suit getSuit()
    {
        return suit;
    }
}

poker.distribution.entity.Suit

public enum Suit
{
    S('S'), H('H'), C('C'), D('D');

    private char abbrChar;

    private Suit(char abbrChar)
    {
        this.abbrChar = abbrChar;
    }

    public char getAbbrChar()
    {
        return abbrChar;
    }

    public static Suit getEnum(char abbrChar)
    {
        return Stream.of(Suit.values())
                     .filter(suit -> suit.abbrChar == abbrChar)
                     .findFirst()
                     .orElseThrow(() -> new IllegalArgumentException(String.format("Illegal abbrChar (%c) of Suit.", abbrChar)));
    }
}

5.3.2 HandRank

poker.distribution.entity.HandRank

public class HandRank
{
    public static final int TYPE_RANK_STRAIGHT_FLUSH = 8;
    public static final int TYPE_RANK_FOUR_KIND = 7;
    public static final int TYPE_RANK_FULL_HOUSE = 6;
    public static final int TYPE_RANK_FLUSH = 5;
    public static final int TYPE_RANK_STRAIGHT = 4;
    public static final int TYPE_RANK_THREE_KIND = 3;
    public static final int TYPE_RANK_TWO_PAIRS = 2;
    public static final int TYPE_RANK_ONE_PAIR = 1;
    public static final int TYPE_RANK_HIGH_CARD = 0;

    private int typeRank;
    private List<Integer> distributedRanks;

    private HandRank(int typeRank, List<Integer> distributedRanks)
    {
        this.typeRank = typeRank;
        this.distributedRanks = distributedRanks;
    }

    public static HandRank handRank(int typeRank, List<Integer> distributedRanks)
    {
        return new HandRank(typeRank, distributedRanks);
    }

    public int getTypeRank()
    {
        return typeRank;
    }

    public List<Integer> getDistributedRanks()
    {
        return distributedRanks;
    }
}

5.3.3 HandRankCalculator

5.3.3.1 Calculate When Straight Flush

poker.distribution.HandRankCalculatorTest

public class HandRankCalculatorTest extends TestCase
{
    public void test_calculate_when_straight_flush()
    {
        HandRank actual = calculate(asList(card(2, D), card(3, D), card(4, D), card(5, D), card(6, D)));

        assertEquals(TYPE_RANK_STRAIGHT_FLUSH, actual.getTypeRank());
        assertEquals(asList(6, 5, 4, 3, 2), actual.getDistributedRanks());
    }
}

可以看到,验证HandRank内容的代码简化了很多。

poker.distribution.HandRankCalculator

public class HandRankCalculator
{
    private static Map<HandTypePredication, Function<List<Integer>, HandRank>> predAndCalcFuncMap;
    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
    }

    @FunctionalInterface
    interface HandTypePredication
    {
        boolean predicate(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand);
    }

    public static HandRank calculate(List<Card> hand)
    {
        List<List<Integer>> rankDistribution = calcRankDistribution(hand);

        return predAndCalcFuncMap.entrySet()
                                 .stream()
                                 .filter(entry -> entry.getKey().predicate(rankDistribution.get(0), rankDistribution.get(1), hand))
                                 .findFirst()
                                 .map(entry -> entry.getValue().apply(rankDistribution.get(1)))
                                 .orElseThrow(IllegalArgumentException::new);
    }

    private static List<List<Integer>> calcRankDistribution(List<Card> hand)
    {
        return Lists.calcDistribution(extractRanks(hand));
    }

    private static List<Integer> extractRanks(List<Card> hand)
    {
        return hand.stream().map(Card::getRank).collect(Collectors.toList());
    }

    private static boolean isStraightFlush(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return isStraight(distributedRanks) && isFlush(hand);
    }

    private static boolean isFlush(List<Card> hand)
    {
        return hand.stream().map(Card::getSuit).distinct().count() == 1;
    }

    private static boolean isStraight(List<Integer> distributedRanks)
    {
        return distributedRanks.stream().distinct().count() == 5 && (distributedRanks.get(0) - distributedRanks.get(4) == 4);
    }

    private static HandRank calcStraightFlush(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_STRAIGHT_FLUSH, distributedRanks);
    }
}

修改了predAndCalcFuncMap的类型。判断一手牌类型的参数个数统一为3个,所以自定义了函数接口HandTypePredication。主要根据分值分布特征判断一手牌的类型。计算HandRank的逻辑也简化了很多。

Wishful Thinking,我们需要计算列表的数值分布,Java的JDK没有提供现成的工具,自己制造。

poker.distribution.util.ListsTest

    public void test_calcDistribution()
    {
        assertEquals(asList(asList(1, 1, 1, 1, 1), asList(6, 5, 4, 3, 2)), calcDistribution(asList(2, 3, 4, 5, 6)));
        assertEquals(asList(asList(4, 1), asList(2, 9)), calcDistribution(asList(2, 2, 9, 2, 2)));
        assertEquals(asList(asList(3, 2), asList(10, 3)), calcDistribution(asList(10, 3, 10, 3, 10)));
        assertEquals(asList(asList(1, 1, 1, 1, 1), asList(12, 11, 6, 5, 3)), calcDistribution(asList(6, 3, 5, 11, 12)));
        assertEquals(asList(asList(1, 1, 1, 1, 1), asList(6, 5, 4, 3, 2)), calcDistribution(asList(2, 3, 4, 5, 6)));
        assertEquals(asList(asList(3, 1, 1), asList(2, 12, 10)), calcDistribution(asList(2, 2, 2, 10, 12)));
        assertEquals(asList(asList(2, 2, 1), asList(4, 3, 6)), calcDistribution(asList(3, 3, 4, 4, 6)));
        assertEquals(asList(asList(2, 1, 1, 1), asList(3, 11, 9, 2)), calcDistribution(asList(3, 3, 2, 9, 11)));
        assertEquals(asList(asList(1, 1, 1, 1, 1), asList(9, 7, 4, 3, 2)), calcDistribution(asList(7, 2, 3, 4, 9)));
    }

poker.distribution.util.Lists

    public static List<List<Integer>> calcDistribution(List<Integer> list)
    {
        List<Integer> rankDistribution = new ArrayList<>();
        List<Integer> distributedRanks = new ArrayList<>();

        list.stream()
            .distinct()
            .sorted(Comparator.comparing(Function.identity(), (ele1, ele2) -> ele2 - ele1))
            .map(ele -> asList(Collections.frequency(list, ele), ele))
            .sorted(Comparator.comparing(freqAndEle -> freqAndEle.get(0).intValue(), (freq1, freq2) -> freq2 - freq1))
            .forEach(freqAndEle -> {
                rankDistribution.add(freqAndEle.get(0));
                distributedRanks.add(freqAndEle.get(1));
            });

        return asList(rankDistribution, distributedRanks);
    }

5.3.3.2 Calculate When Four of Kind

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_four_kind()
    {
        HandRank actual = calculate(asList(card(2, D), card(2, H), card(2, C), card(2, S), card(9, C)));

        assertEquals(TYPE_RANK_FOUR_KIND, actual.getTypeRank());
        assertEquals(asList(2, 9), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
    }

    private static boolean isFourKind(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return asList(4, 1).equals(rankDistributionPattern);
    }

    private static HandRank calcFourKind(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_FOUR_KIND, distributedRanks);
    }

5.3.3.3 Calculate When Full House

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_full_house()
    {
        HandRank actual = calculate(asList(card(3, D), card(3, H), card(3, C), card(4, S), card(4, D)));

        assertEquals(TYPE_RANK_FULL_HOUSE, actual.getTypeRank());
        assertEquals(asList(3, 4), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
    }

    private static boolean isFullHouse(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return asList(3, 2).equals(rankDistributionPattern);
    }

    private static HandRank calcFullHouse(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_FULL_HOUSE, distributedRanks);
    }

5.3.3.4 Calculate When Straight

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_straight()
    {
        HandRank actual = calculate(asList(card(3, S), card(4, D), card(5, C), card(6, D), card(7, C)));

        assertEquals(TYPE_RANK_STRAIGHT, actual.getTypeRank());
        assertEquals(asList(7, 6, 5, 4, 3), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
    }

    private static boolean isStraight(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return isStraight(distributedRanks);
    }

    private static HandRank calcStraight(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_STRAIGHT, distributedRanks);
    }

5.3.3.5 Calculate When Three of a Kind

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_three_kind()
    {
        HandRank actual = calculate(asList(card(3, D), card(3, C), card(3, S), card(2, D), card(7, C)));

        assertEquals(TYPE_RANK_THREE_KIND, actual.getTypeRank());
        assertEquals(asList(3, 7, 2), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
    }

    private static boolean isThreeKind(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return asList(3, 1, 1).equals(rankDistributionPattern);
    }

    private static HandRank calcThreeKind(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_THREE_KIND, distributedRanks);
    }

5.3.3.6 Calculate When Two Pairs

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_two_pairs()
    {
        HandRank actual = calculate(asList(card(3, D), card(3, C), card(4, H), card(4, S), card(6, D)));

        assertEquals(TYPE_RANK_TWO_PAIRS, actual.getTypeRank());
        assertEquals(asList(4, 3, 6), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
    }

    private static boolean isTwoPairs(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return asList(2, 2, 1).equals(rankDistributionPattern);
    }

    private static HandRank calcTwoPairs(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_TWO_PAIRS, distributedRanks);
    }

5.3.3.7 Calculate When One Pair

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_one_pair()
    {
        HandRank actual = calculate(asList(card(2, D), card(3, C), card(3, H), card(9, D), card(7, C)));

        assertEquals(TYPE_RANK_ONE_PAIR, actual.getTypeRank());
        assertEquals(asList(3, 9, 7, 2), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
        predAndCalcFuncMap.put(HandRankCalculator::isOnePair, HandRankCalculator::calcOnePair);
    }

    private static boolean isOnePair(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return asList(2, 1, 1, 1).equals(rankDistributionPattern);
    }

    private static HandRank calcOnePair(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_ONE_PAIR, distributedRanks);
    }

5.3.3.8 Calculate When High Card

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_high_card()
    {
        HandRank actual = calculate(asList(card(7, D), card(2, C), card(3, H), card(4, C), card(9, S)));

        assertEquals(TYPE_RANK_HIGH_CARD, actual.getTypeRank());
        assertEquals(asList(9, 7, 4, 3, 2), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    static
    {
        predAndCalcFuncMap = new LinkedHashMap<>();
        predAndCalcFuncMap.put(HandRankCalculator::isStraightFlush, HandRankCalculator::calcStraightFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isFourKind, HandRankCalculator::calcFourKind);
        predAndCalcFuncMap.put(HandRankCalculator::isFullHouse, HandRankCalculator::calcFullHouse);
        predAndCalcFuncMap.put(HandRankCalculator::isFlush, HandRankCalculator::calcFlush);
        predAndCalcFuncMap.put(HandRankCalculator::isStraight, HandRankCalculator::calcStraight);
        predAndCalcFuncMap.put(HandRankCalculator::isThreeKind, HandRankCalculator::calcThreeKind);
        predAndCalcFuncMap.put(HandRankCalculator::isTwoPairs, HandRankCalculator::calcTwoPairs);
        predAndCalcFuncMap.put(HandRankCalculator::isOnePair, HandRankCalculator::calcOnePair);
        predAndCalcFuncMap.put(HandRankCalculator::isHighCard, HandRankCalculator::calcHighCard);
    }

    private static boolean isHighCard(List<Integer> rankDistributionPattern, List<Integer> distributedRanks, List<Card> hand)
    {
        return true;
    }

    private static HandRank calcHighCard(List<Integer> distributedRanks)
    {
        return handRank(TYPE_RANK_HIGH_CARD, distributedRanks);
    }

5.3.3.9 Special Case - About "A"

poker.distribution.HandRankCalculatorTest

    public void test_calculate_when_straight_with_A()
    {
        HandRank actual = calculate(asList(card(2, C), card(3, S), card(4, D), card(5, C), card(14, D)));

        assertEquals(TYPE_RANK_STRAIGHT, actual.getTypeRank());
        assertEquals(asList(5, 4, 3, 2, 1), actual.getDistributedRanks());
    }

poker.distribution.HandRankCalculator

    private static List<List<Integer>> calcRankDistribution(List<Card> hand)
    {
        List<List<Integer>> rankDistribution = Lists.calcDistribution(extractRanks(hand));

        if(asList(14, 5, 4, 3, 2).equals(rankDistribution.get(1)))
        {
            rankDistribution.set(1, asList(5, 4, 3, 2, 1));
        }

        return rankDistribution;
    }

5.4 Improvement of Implementation - HandRankComparator

5.4.1 Compare When Different Hand Type Ranks

poker.distribution.HandRankComparatorTest

public class HandRankComparatorTest extends TestCase
{
    public void test_compare_when_different_hand_type_ranks()
    {
        HandRank straightFlushHandRank = handRank(TYPE_RANK_STRAIGHT_FLUSH, asList(6, 5, 4, 3, 2));
        HandRank fourKindHandRank = handRank(TYPE_RANK_FOUR_KIND, asList(2, 9));
        HandRank fullHouseHandRank = handRank(TYPE_RANK_FULL_HOUSE, asList(3, 4));
        HandRank flushHandRank = handRank(TYPE_RANK_FLUSH, asList(9, 7, 5, 3, 2));
        HandRank straightHandRank = handRank(TYPE_RANK_STRAIGHT, asList(7, 6, 5, 4, 3));
        HandRank threeKindHandRank = handRank(TYPE_RANK_THREE_KIND, asList(3, 7, 2));
        HandRank twoPairsHandRank = handRank(TYPE_RANK_TWO_PAIRS, asList(4, 3, 6));
        HandRank onePairHandRank = handRank(TYPE_RANK_ONE_PAIR, asList(3, 9, 7, 2));
        HandRank highCardHandRank = handRank(TYPE_RANK_HIGH_CARD, asList(9, 7, 4, 3, 2));

        assertTrue(compare(straightFlushHandRank, fourKindHandRank) > 0);
        assertTrue(compare(fourKindHandRank, fullHouseHandRank) > 0);
        assertTrue(compare(fullHouseHandRank, flushHandRank) > 0);
        assertTrue(compare(flushHandRank, straightHandRank) > 0);
        assertTrue(compare(straightHandRank, threeKindHandRank) > 0);
        assertTrue(compare(threeKindHandRank, twoPairsHandRank) > 0);
        assertTrue(compare(twoPairsHandRank, onePairHandRank) > 0);
        assertTrue(compare(onePairHandRank, highCardHandRank) > 0);
    }
}

主要修改了构造HandRank的代码。

poker.distribution.HandRankComparator

public class HandRankComparator
{
    public static int compare(HandRank handRank1, HandRank handRank2)
    {
        return handRank1.getTypeRank() - handRank2.getTypeRank();
    }
}

5.4.2 Compare Straight Flush Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_straight_flush_hand_ranks()
    {
        HandRank straightFlushHandRank1 = handRank(TYPE_RANK_STRAIGHT_FLUSH, asList(5, 4, 3, 2, 1));
        HandRank straightFlushHandRank2 = handRank(TYPE_RANK_STRAIGHT_FLUSH, asList(6, 5, 4, 3, 2));
        HandRank straightFlushHandRank3 = handRank(TYPE_RANK_STRAIGHT_FLUSH, asList(7, 6, 5, 4, 3));

        assertTrue(compare(straightFlushHandRank1, straightFlushHandRank2) < 0);
        assertTrue(compare(straightFlushHandRank2, straightFlushHandRank2) == 0);
        assertTrue(compare(straightFlushHandRank3, straightFlushHandRank2) > 0);
    }

poker.distribution.HandRankComparator

    public static int compare(HandRank handRank1, HandRank handRank2)
    {
        return handRank1.getTypeRank() != handRank2.getTypeRank()
                ? handRank1.getTypeRank() - handRank2.getTypeRank()
                : Lists.compare(handRank1.getDistributedRanks(), handRank2.getDistributedRanks());
    }

由于HandRank的简化,HandRank的比较逻辑也得到了极大的简化。

5.4.3 Compare Four of a Kind Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_four_kind_hand_ranks()
    {
        HandRank fourKindHandRank1 = handRank(TYPE_RANK_FOUR_KIND, asList(4, 4, 4, 4, 3));
        HandRank fourKindHandRank2 = handRank(TYPE_RANK_FOUR_KIND, asList(5, 5, 5, 5, 3));
        HandRank fourKindHandRank3 = handRank(TYPE_RANK_FOUR_KIND, asList(6, 6, 6, 6, 4));
        HandRank fourKindHandRank4 = handRank(TYPE_RANK_FOUR_KIND, asList(4, 4, 4, 4, 6));

        assertTrue(compare(fourKindHandRank1, fourKindHandRank2) < 0);
        assertTrue(compare(fourKindHandRank2, fourKindHandRank2) == 0);
        assertTrue(compare(fourKindHandRank3, fourKindHandRank2) > 0);

        assertTrue(compare(fourKindHandRank4, fourKindHandRank2) < 0);
        assertTrue(compare(fourKindHandRank2, fourKindHandRank4) > 0);

        assertTrue(compare(fourKindHandRank1, fourKindHandRank4) < 0);
        assertTrue(compare(fourKindHandRank4, fourKindHandRank1) > 0);
    }

5.4.4 Compare Full House Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_full_house_hand_ranks()
    {
        HandRank fullHouseHandRank1 = handRank(TYPE_RANK_FULL_HOUSE, asList(4, 4, 4, 3, 3));
        HandRank fullHouseHandRank2 = handRank(TYPE_RANK_FULL_HOUSE, asList(5, 5, 5, 3, 3));
        HandRank fullHouseHandRank3 = handRank(TYPE_RANK_FULL_HOUSE, asList(4, 4, 4, 6, 6));

        assertTrue(compare(fullHouseHandRank1, fullHouseHandRank2) < 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank2) == 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank1) > 0);

        assertTrue(compare(fullHouseHandRank3, fullHouseHandRank2) < 0);
        assertTrue(compare(fullHouseHandRank2, fullHouseHandRank3) > 0);

        assertTrue(compare(fullHouseHandRank1, fullHouseHandRank3) < 0);
        assertTrue(compare(fullHouseHandRank3, fullHouseHandRank1) > 0);
    }

5.4.5 Compare Flush Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_flush_hand_ranks()
    {
        HandRank flushHandRank1 = handRank(TYPE_RANK_FLUSH, asList(7, 5, 4, 3, 2));
        HandRank flushHandRank2 = handRank(TYPE_RANK_FLUSH, asList(8, 6, 5, 4, 3));

        assertTrue(compare(flushHandRank1, flushHandRank2) < 0);
        assertTrue(compare(flushHandRank2, flushHandRank2) == 0);
        assertTrue(compare(flushHandRank2, flushHandRank1) > 0);
    }

5.4.6 Compare Straight Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_straight_hand_ranks()
    {
        HandRank straightHandRank1 = handRank(TYPE_RANK_STRAIGHT, asList(5, 4, 3, 2, 1));
        HandRank straightHandRank2 = handRank(TYPE_RANK_STRAIGHT, asList(6, 5, 4, 3, 2));
        HandRank straightHandRank3 = handRank(TYPE_RANK_STRAIGHT, asList(7, 6, 5, 4, 3));

        assertTrue(compare(straightHandRank1, straightHandRank2) < 0);
        assertTrue(compare(straightHandRank2, straightHandRank2) == 0);
        assertTrue(compare(straightHandRank3, straightHandRank2) > 0);
    }

5.4.7 Compare Three of a Kind Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_three_kind_hand_ranks()
    {
        HandRank threeKindHandRank1 = handRank(TYPE_RANK_THREE_KIND, asList(4, 4, 4, 3, 2));
        HandRank threeKindHandRank2 = handRank(TYPE_RANK_THREE_KIND, asList(5, 5, 5, 3, 2));
        HandRank threeKindHandRank3 = handRank(TYPE_RANK_THREE_KIND, asList(4, 4, 4, 6, 5));
        HandRank threeKindHandRank4 = handRank(TYPE_RANK_THREE_KIND, asList(4, 4, 4, 3, 5));

        assertTrue(compare(threeKindHandRank1, threeKindHandRank2) < 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank2) == 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank1) > 0);

        assertTrue(compare(threeKindHandRank3, threeKindHandRank2) < 0);
        assertTrue(compare(threeKindHandRank2, threeKindHandRank3) > 0);

        assertTrue(compare(threeKindHandRank1, threeKindHandRank3) < 0);
        assertTrue(compare(threeKindHandRank3, threeKindHandRank1) > 0);
        assertTrue(compare(threeKindHandRank4, threeKindHandRank1) > 0);
    }

5.4.8 Compare Two Pairs Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_two_pairs_hand_ranks()
    {
        HandRank twoPairsHandRank1 = handRank(TYPE_RANK_TWO_PAIRS, asList(5, 5, 4, 4, 2));
        HandRank twoPairsHandRank2 = handRank(TYPE_RANK_TWO_PAIRS, asList(6, 6, 5, 5, 2));
        HandRank twoPairsHandRank3 = handRank(TYPE_RANK_TWO_PAIRS, asList(5, 5, 4, 4, 3));

        assertTrue(compare(twoPairsHandRank1, twoPairsHandRank2) < 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank2) == 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank1) > 0);

        assertTrue(compare(twoPairsHandRank3, twoPairsHandRank2) < 0);
        assertTrue(compare(twoPairsHandRank2, twoPairsHandRank3) > 0);

        assertTrue(compare(twoPairsHandRank1, twoPairsHandRank3) < 0);
        assertTrue(compare(twoPairsHandRank3, twoPairsHandRank1) > 0);
    }

5.4.9 Compare One Pair Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_one_pair_hand_ranks()
    {
        HandRank onePairHandRank1 = handRank(TYPE_RANK_ONE_PAIR, asList(5, 5, 4, 3, 2));
        HandRank onePairHandRank2 = handRank(TYPE_RANK_ONE_PAIR, asList(6, 6, 4, 3, 2));
        HandRank onePairHandRank3 = handRank(TYPE_RANK_ONE_PAIR, asList(5, 5, 7, 6, 4));

        assertTrue(compare(onePairHandRank1, onePairHandRank2) < 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank2) == 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank1) > 0);

        assertTrue(compare(onePairHandRank3, onePairHandRank2) < 0);
        assertTrue(compare(onePairHandRank2, onePairHandRank3) > 0);

        assertTrue(compare(onePairHandRank1, onePairHandRank3) < 0);
        assertTrue(compare(onePairHandRank3, onePairHandRank1) > 0);
    }

5.4.10 Compare High Card Hand Ranks

poker.distribution.HandRankComparatorTest

    public void test_compare_high_card_hand_ranks()
    {
        HandRank highCardHandRank1 = handRank(TYPE_RANK_HIGH_CARD, asList(7, 5, 4, 3, 2));
        HandRank highCardHandRank2 = handRank(TYPE_RANK_HIGH_CARD, asList(8, 7, 5, 4, 3));

        assertTrue(compare(highCardHandRank1, highCardHandRank2) < 0);
        assertTrue(compare(highCardHandRank2, highCardHandRank2) == 0);
        assertTrue(compare(highCardHandRank2, highCardHandRank1) > 0);
    }

5.5 PokerEngine & PokerEngineUI

除了包路径,PokerEngine和PokerEngineUI的测试代码和产品代码不用做任何修改。

6 Summary

6.1 Wishful Thinking

在这个课程里,我们看到了无处不在的Wishful Thinking。

我们试着用这样的句式:我有一个想法,如果能有X,我就能搞定了。

我们将需要解决的问题用声明性的、简短的话来描述,然后看看我们手边、使用的语言中是不是有称手的工具来帮助我们表述这个问题,有就拿来用,没有就自己制造。

Wishful Thinking可以帮助我们从问题本身层面而不是从实现层面思考如何表达问题。

Problem-solving为导向,识别出问题的核心,围绕核心问题的解决来演化以及延伸出整个解决方案。

6.2 What is Design?

设计就是把问题变成可计算的。

design computable

6.3 What is Good Design?

设计出的计算模型所提供的语义和问题领域的根本需求是否匹配,匹配就是好的设计,不匹配就是不好的设计。

good design

将Poker Engine这个问题领域映射到了数学的数值分布领域。数据结构也是计算模型很重要的一部分。

6.4 How to do Design?

how to do design

  • 对问题领域进行深入分析,发现问题领域的核心需求;
  • 通过核心需求驱动出计算模型和语义;
  • 再围绕这个计算模型提供一套语言,给外面的人使用这个计算模型提供一个接口,这个接口可以是API、可以是数据表达、也可以是语言;
  • 最终要实现这个计算模型,实现的方法有解释器和编译器两种。

这就是DDD!这才是DDD! 这就是DSL!这才是DSL!

6.5 What is Programming?

“编程”不过是在某个计算模型上用某种语言去表达计算。 用DSL编程不过是在问题领域的计算模型上用DSL来表达计算。
计算模型是相应领域中的“通用机器”。 问题领域的计算模型是问题领域的通用机器。
编程语言不过是描述计算机器的一种方法。 DSL描述的是DSL语言的计算机器。
程序是对特定机器的描述,这个特定机器可以被通用机器仿真。 用DSL程序实现了问题领域中的某个功能,这个DSL程序就是对这个功能(特定机器)的描述。

6.6 Design & Programming

design and programming

  • 从问题领域导出核心需求,得到领域的计算模型(通用计算机器),在上面可以包装一个DSL语言或者数据或者API,基于这些开发程序和应用。
  • 领域的计算模型和通用语言的计算模型之间存在鸿沟,可以用解释器或者编译器来填补。
  • 解释器和编译器听起来很复杂,其实思想很简单,而且我们没有必要实现一个工业级别的、非常全面的解释器和编译器,只要借鉴这个思想实现我们的计算模型就够了。

Common Languages(Java/C) UML对应Java/C/Python通用语言。 Common Languages(Java/C) UM对应Java/C/Python通用语言提供的计算模型(通用计算机器)。

6.7 How to improve Design Capability?

提升设计能力的根本在于提升计算模型构造和语义定义能力。

Thinking, thinking, thinking … Practice, practice and practice … No shortcuts.

7 Homework

改进Poker Engine以支持平局。

8 My Homework

所谓支持平局,意思就是:给定多手牌,按照规则找出所有分值最高的获胜者。

思路:通过Collection.max()找到多手牌中分值最高的那手牌,得到最高的分值,再遍历多手牌,找出所有分值等于最高分值的几手牌。

PokerEngineUI的play()方法需要改为返回多手牌中分值最高的那些牌。PokerEngine的run()方法需要改为返回多手牌中分值最高的那些牌。

方法签名变了,先保证已有的测试用例依然测试通过,再增加新的测试和逻辑。

poker.tie.PokerEngineUITest

    public void test_play()
    {
        assertEquals(asList(straightFlush), PokerEngineUI.play(asList(straightFlush, fourKind)));
    }

    public void test_play_all()
    {
        assertEquals(asList(straightFlush), PokerEngineUI.play(asList(straightFlush, fourKind)));
        assertEquals(asList(fourKind), PokerEngineUI.play(asList(fourKind, fullHouse)));
        assertEquals(asList(fullHouse), PokerEngineUI.play(asList(fullHouse, flush)));
        assertEquals(asList(flush), PokerEngineUI.play(asList(flush, straight)));
        assertEquals(asList(straight), PokerEngineUI.play(asList(straight, threeKind)));
        assertEquals(asList(threeKind), PokerEngineUI.play(asList(threeKind, twoPairs)));
        assertEquals(asList(twoPairs), PokerEngineUI.play(asList(twoPairs, onePair)));
        assertEquals(asList(onePair), PokerEngineUI.play(asList(onePair, highCard)));

        assertEquals(asList(straightFlushHigher), PokerEngineUI.play(asList(straightFlush, straightFlushHigher)));

        assertEquals(asList(fourKindHigher), PokerEngineUI.play(asList(fourKind, fourKindHigher)));
        assertEquals(asList(fourKindHigher2), PokerEngineUI.play(asList(fourKindHigher, fourKindHigher2)));

        assertEquals(asList(fullHouseHigher), PokerEngineUI.play(asList(fullHouse, fullHouseHigher)));
        assertEquals(asList(fullHouseHigher2), PokerEngineUI.play(asList(fullHouseHigher, fullHouseHigher2)));

        assertEquals(asList(flushHigher), PokerEngineUI.play(asList(flush, flushHigher)));

        assertEquals(asList(straightHigher), PokerEngineUI.play(asList(straight, straightHigher)));

        assertEquals(asList(threeKindHigher), PokerEngineUI.play(asList(threeKind, threeKindHigher)));
        assertEquals(asList(threeKindHigher2), PokerEngineUI.play(asList(threeKindHigher, threeKindHigher2)));

        assertEquals(asList(twoPairsHigher), PokerEngineUI.play(asList(twoPairs, twoPairsHigher)));
        assertEquals(asList(twoPairsHigher2), PokerEngineUI.play(asList(twoPairsHigher, twoPairsHigher2)));

        assertEquals(asList(onePairHigher), PokerEngineUI.play(asList(onePair, onePairHigher)));
        assertEquals(asList(onePairHigher2), PokerEngineUI.play(asList(onePairHigher, onePairHigher2)));

        assertEquals(asList(highCardHigher), PokerEngineUI.play(asList(highCard, highCardHigher)));

        assertEquals(asList(straightFlush), PokerEngineUI.play(asList(straightFlush, fourKind, fullHouse, flush,
                straight, threeKind, twoPairs, onePair, highCard)));

        assertEquals(asList(highCard), PokerEngineUI.play(asList(highCard)));
    }

    public void test_play_when_straight_including_A()
    {
        assertEquals(asList(straightIncludingA), PokerEngineUI.play(asList(straightIncludingA, highCardIncludingA)));
        assertEquals(asList(straight), PokerEngineUI.play(asList(straight, straightIncludingA)));
    }

    public void test_play_when_tie()
    {
        assertEquals(asList(straightFlush), PokerEngineUI.play(asList(straightFlush, straightFlush2)));
        assertEquals(asList(straightFlush2), PokerEngineUI.play(asList(straightFlush2, straightFlush)));
    }

主要是期望值改成List。

poker.tie.PokerEngineUI

    public static List<String> play(List<String> handStrs)
    {
        return PokerEngine.run(toHands(handStrs))
                          .stream()
                          .map(PokerEngineUI::toStr)
                          .collect(Collectors.toList());
    }

poker.tie.PokerEngineTest

    public void test_run()
    {
        assertEquals(asList(straightFlush), PokerEngine.run(asList(straightFlush, fourKind)));
        assertEquals(asList(fourKind), PokerEngine.run(asList(fourKind, fullHouse)));
        assertEquals(asList(fullHouse), PokerEngine.run(asList(fullHouse, flush)));
        assertEquals(asList(flush), PokerEngine.run(asList(flush, straight)));
        assertEquals(asList(straight), PokerEngine.run(asList(straight, threeKind)));
        assertEquals(asList(threeKind), PokerEngine.run(asList(threeKind, twoPairs)));
        assertEquals(asList(twoPairs), PokerEngine.run(asList(twoPairs, onePair)));
        assertEquals(asList(onePair), PokerEngine.run(asList(onePair, highCard)));

        assertEquals(asList(straightFlushHigher), PokerEngine.run(asList(straightFlush, straightFlushHigher)));

        assertEquals(asList(fourKindHigher), PokerEngine.run(asList(fourKind, fourKindHigher)));
        assertEquals(asList(fourKindHigher2), PokerEngine.run(asList(fourKindHigher, fourKindHigher2)));

        assertEquals(asList(fullHouseHigher), PokerEngine.run(asList(fullHouse, fullHouseHigher)));
        assertEquals(asList(fullHouseHigher2), PokerEngine.run(asList(fullHouseHigher, fullHouseHigher2)));

        assertEquals(asList(flushHigher), PokerEngine.run(asList(flush, flushHigher)));

        assertEquals(asList(straightHigher), PokerEngine.run(asList(straight, straightHigher)));

        assertEquals(asList(threeKindHigher), PokerEngine.run(asList(threeKind, threeKindHigher)));
        assertEquals(asList(threeKindHigher2), PokerEngine.run(asList(threeKindHigher, threeKindHigher2)));

        assertEquals(asList(twoPairsHigher), PokerEngine.run(asList(twoPairs, twoPairsHigher)));
        assertEquals(asList(twoPairsHigher2), PokerEngine.run(asList(twoPairsHigher, twoPairsHigher2)));

        assertEquals(asList(onePairHigher), PokerEngine.run(asList(onePair, onePairHigher)));
        assertEquals(asList(onePairHigher2), PokerEngine.run(asList(onePairHigher, onePairHigher2)));

        assertEquals(asList(highCardHigher), PokerEngine.run(asList(highCard, highCardHigher)));

        assertEquals(asList(straightFlush), PokerEngine.run(asList(straightFlush, fourKind, fullHouse, flush,
                straight, threeKind, twoPairs, onePair, highCard)));

        assertEquals(asList(highCard), PokerEngine.run(asList(highCard)));
    }

主要是期望值改成List。

poker.tie.PokerEngine

    public static List<List<Card>> run(List<List<Card>> hands)
    {
        return asList(Collections.max(hands, Comparator.comparing(HandRankCalculator::calculate, HandRankComparator::compare)));
    }

已有的测试用例全部测试通过。

新增测试用例。 poker.tie.PokerEngineTest

    private static final List<Card> straightFlush = asList(card(2, D), card(3, D), card(4, D), card(5, D), card(6, D));
    private static final List<Card> straightFlushHigher = asList(card(3, C), card(4, C), card(5, C), card(6, C), card(7, C));
    private static final List<Card> straightFlushEqual = asList(card(2, H), card(3, H), card(4, H), card(5, H), card(6, H));

    public void test_run_when_tie()
    {
        assertEquals(asList(straightFlush, straightFlushEqual),
                PokerEngine.run(asList(straightFlush, straightFlushEqual, fourKind, fullHouse)));
    }

poker.tie.PokerEngine

    public static List<List<Card>> run(List<List<Card>> hands)
    {
        Comparator<List<Card>> comparator = Comparator.comparing(HandRankCalculator::calculate, HandRankComparator::compare);
        List<Card> maxHand = Collections.max(hands, comparator);
        return hands.stream()
                    .filter(hand -> comparator.compare(maxHand, hand) == 0)
                    .collect(Collectors.toList());
    }

改一下PokerEngineUITest测试用例,不用修改产品代码,可以直接测试通过。

poker.tie.PokerEngineUITest

    public void test_play_when_tie()
    {
        assertEquals(asList(straightFlush, straightFlush2),
                PokerEngineUI.play(asList(straightFlush, straightFlush2, fourKind, fullHouse)));
    }
⚠️ **GitHub.com Fallback** ⚠️