6

用一个int表示德州扑克玩家牌力

 3 years ago
source link: https://www.xiejingyang.com/2020/06/18/use-int-show-player-poker-strength/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
用一个int表示德州扑克玩家牌力 | Xieisabug

这个方法是在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; // 皇家同花顺
// 修改前
// 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;
// 搜索卡牌列表中是否存在三张,并返回三张的牌面大小,不存在返回-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));
// 判断存在三张之后,返回三张所对应的牌力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));
}

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK