这个方法是在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
代码有吗
xiejingyang · 2023年12月25日 14:06
可以直接看那个德州库
https://github.com/ishikota/PyPokerEngine
然后把我说的东西改改就行
文章里项目的代码是前公司的,而且我这已经找不到了
111 · 2023年12月26日 10:42
好的 谢谢提供, 不过那个库有一些corner case是不正确的 我得改下