这个方法是在python的一个德州库(PyPokerEngine)里看到的,并不是原创,但是原方法bug特别的多,直接导致牌局的胜负判断错误,所以我对原方法进行了一些改进,但是原方法的思想非常厉害,所以值得推荐一番。

原库的思想是这样的:

int有32位,扑克牌最大的牌是K,值是13,需要用4位来表示,所以利用高16位表示牌型(如一对,两对,顺子等)值,用低16位表示关键牌的值(如一对中,对子的牌大小)。

[牌型大小(16bit)] [关键牌1(4bit)] [关键牌2(4bit)] [手牌1(4bit)] [手牌1(4bit)]

比如 A K Q 5 5,这副牌,可以使用二进制 0000 0000 0000 0001 0000 0101 0001 1101表示。

其中,高16位 0000 0000 0000 0001 表示一对,如果是两对则用 0000 0000 0000 0010 表示,这样的好处是,牌型大的牌无需比较牌面的大小,两对行成的int永远大于一对。其中低16位中的前8位表示的是牌中关键牌的值,这里 0000 0101 表示的是一对5中的5,低16位中的后8位表示的是手牌的大小,我这里假设A K是手牌,所以值为 0001 1101。

乍一看原库的思想的确很妙,不管是什么牌型直接用一个int数字就能表示并且轻易的比较,但是在有的牌局中,这个方法却出现了致命的bug。

在牌型关键牌平局的时候,只能比手牌,这样的比较肯定是错误的。在德州的规则中,如果两名玩家都是一对5,那么需要比较剩下的3张最大的牌,这个时候在原库思想中比的是玩家手牌,但是当桌面上5张牌就是最大的牌的时候,双方应该是平局,手牌不应该左右牌局的胜负方。比如这样一局牌:

公共牌:A K Q J J
玩家1手牌:9 8
玩家2手牌:8 7

这局牌在正确的规则中,应该是平局。但是在原库的代码中,判定玩家1获胜。所以需要修改一下原库的部分算法。

高16位的算法依然保持不变:

因为德州的牌型只有10种,所以不需要使用到全部16位。在之前我认为低16位可以够用来进行牌面大小的比较,后来被朋友指出低16位比较算法有问题。所以改为只用最高10位,而多出来的6位需要留给后面牌面比较算法使用。

高10位的算法为:

// 修改前
// public final int HIGH_CARD = 0; // 高牌
// public final int ONE_PAIR = 1 << 8; // 一对
// public final int TWO_PAIR = 1 << 9; // 两对
// public final int THREE_CARD = 1 << 10; // 三张
// public final int STRAIGHT = 1 << 11; // 顺子
// public final int FLUSH = 1 << 12; // 同花
// public final int FULL_HOUSE = 1 << 13; // 葫芦
// public final int FOUR_CARD = 1 << 14; // 四张
// public final int STRAIGHT_FLUSH = 1 << 15; // 同花顺
// public final int ROYAL_FLUSH = 1 << 16; // 皇家同花顺

// 修改后
public final int HIGH_CARD = 1 << 7; // 高牌
public final int ONE_PAIR = 1 << 8; // 一对
public final int TWO_PAIR = 1 << 9; // 两对
public final int THREE_CARD = 1 << 10; // 三张
public final int STRAIGHT = 1 << 11; // 顺子
public final int FLUSH = 1 << 12; // 同花
public final int FULL_HOUSE = 1 << 13; // 葫芦
public final int FOUR_CARD = 1 << 14; // 四张
public final int STRAIGHT_FLUSH = 1 << 15; // 同花顺
public final int ROYAL_FLUSH = 1 << 16; // 皇家同花顺

重点就是接下来的每一种牌型的后16位写法,全部都不需要保存手牌了。

同花顺:只需要保存同花顺中最小牌在后16位就可以了,比如 10 J Q K A,那么只需要保存10,就能判断同花顺的大小。

四张:保存相同的4张牌的牌的大小和剩下最大的那张牌的大小,比如 A A A A K,那么只需要保存 A K 0 0。

葫芦:保存3张相同牌的牌大小和另外一对的牌大小,比如 Q Q Q J J,那么保存的就是Q J 0 0。

顺子:同同花顺存储方法。

同花:5张牌,最大的是A(用14表示),最多5张牌加起来最大的数字是69(并不可能在同花中出现),用8位二进制完全可以存起来,那么同花就保存最大5张牌的和。

同花:5张牌,按大小排序,最大的放在高位,最小的放在低位,4位一张牌,需要20位,利用上高位剩下来的6位,就可以将5张牌全部保存下来。

三张:依次保存的是,三张相同牌的牌大小,剩下两张最大的牌大小,如Q Q Q J 9,那么按顺序存的是Q J 9 0。

两对:依次保存的是,较大的一对的牌大小,较小的一对的牌大小,剩下最大的牌的大小,如 Q Q J J 10,那么按顺序保存的 Q J 10 0。

一对:依次保存的是,一对的牌大小,剩下3张最大的牌的大小,如 Q Q J 9 8,那么保存的是 Q J 9 8。

高牌:同同花存储方法。

三张举例,对应的代码如下:

// 搜索卡牌列表中是否存在三张,并返回三张的牌面大小,不存在返回-1
private static int searchThreeCard(List<Card> cards) {
        int bestRank = -1;
        Map<Integer, List<Card>> rankGroup = cards.stream().collect(Collectors.groupingBy(Card::getRank, Collectors.toList()));
        for (Integer rank : rankGroup.keySet()) {
            List<Card> cardList = rankGroup.get(rank);
            if (cardList.size() >= 3 && rank > bestRank) {
                bestRank = rank;
            }
        }
        return bestRank;
    }
// 判断存在三张之后,返回三张所对应的牌力int值
if (GameUtil.searchThreeCard(cards) != -1) {
    int threeCard = GameUtil.searchThreeCard(cards) << 4;
    List<Integer> topCardRank = getTopCardRank(2, cards, threeCard >> 4);
    if (topCardRank == null) {
        return (THREE_CARD | threeCard) << 8;
    }
    return ((THREE_CARD | threeCard) << 8) | ((topCardRank.get(0) << 4) | topCardRank.get(1));
}

这样就能够在使用最小内存和最快的比较速度的情况下,判断出哪一位玩家获胜了。


5 条评论

淘花 · 2020年7月24日 18:36

写的不错

    xiejingyang · 2020年9月11日 14:33

    谢谢

111 · 2023年12月25日 13:39

代码有吗

111 · 2023年12月26日 10:42

好的 谢谢提供, 不过那个库有一些corner case是不正确的 我得改下

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注