最简单的扑克手评估算法

我正在考虑Java扑克牌(5张牌)评价。 现在我正在寻求简单和清晰,而不是性能和效率。 我可能会写一个“天真”的算法,但它需要很多代码。

我还看到了一些扑克评估库,它们使用散列和按位操作,但它们看起来相当复杂。

什么是扑克牌手评估的“最干净,最简单”的算法?

这是一个非常简短但完整的基于直方图的5卡扑克评分函数(2.x)。 如果转换为Java,它将会变得更长。

 def poker(hands): scores = [(i, score(hand.split())) for i, hand in enumerate(hands)] winner = sorted(scores , key=lambda x:x[1])[-1][0] return hands[winner] def score(hand): ranks = '23456789TJQKA' rcounts = {ranks.find(r): ''.join(hand).count(r) for r, _ in hand}.items() score, ranks = zip(*sorted((cnt, rank) for rank, cnt in rcounts)[::-1]) if len(score) == 5: if ranks[0:2] == (12, 3): #adjust if 5 high straight ranks = (3, 2, 1, 0, -1) straight = ranks[0] - ranks[4] == 4 flush = len({suit for _, suit in hand}) == 1 '''no pair, straight, flush, or straight flush''' score = ([1, (3,1,1,1)], [(3,1,1,2), (5,)])[flush][straight] return score, ranks >>> poker(['8C TS KC 9H 4S', '7D 2S 5D 3S AC', '8C AD 8D AC 9C', '7C 5H 8D TD KS']) '8C AD 8D AC 9C' 

查找表是解决问题的最简单,最简单的解决方案,也是最快的解决方案。 诀窍在于管理表的大小并保持使用模式足够简单以便快速处理( 时空权衡 )。 显然,理论上你可以只编码每个可以持有的手并进行一系列评估,然后–poof–一个表查找就完成了。 不幸的是,对于大多数机器来说,这样的表会很庞大并且难以管理,并且无论如何都会让你颠倒磁盘,因为内存会被换掉。

所谓的两加二解决方案是一个大型的10M表,但字面上涉及一个表查找手中的每张卡。 您不太可能找到更快速,更简单的算法。

其他解决方案涉及更多压缩表,索引更复杂,但它们易于理解且速度非常快(尽管比2 + 2慢得多)。 在这里您可以看到有关散列的语言等等 – 将表格大小减小到更易于管理的大小的技巧。

在任何情况下,查找解决方案都比直方图排序快几个数量级 – 跳过你头上的比较 – 特殊情况并且通过这种方式进行同步冲洗解决方案,几乎没有其中值得一看。

你实际上不需要任何高级function,它可以按位完成:(来源: http : //www.codeproject.com/Articles/569271/A-Poker-hand-analyzer-in-JavaScript-using-bit-数学 )

(这个实际上是用JavaScript编写的,但如果需要的话,你可以用Java来评估JavaScript,所以它不应该是一个问题。而且,这只是它的简短,所以即使是为了说明方法……) :

首先,你将你的卡分成两个arrays:rank(cs)和suit(ss)并代表套装,你将使用1,2,4或8(即0b0001,0b0010,……):

 var J=11, Q=12, K=13, A=14, C=1, D=2, H=4, S=8; 

现在这里是魔术:

 function evaluateHand(cs, ss) { var pokerHands = ["4 of a Kind", "Straight Flush","Straight","Flush","High Card","1 Pair","2 Pair","Royal Flush", "3 of a Kind","Full House"]; var v,i,o,s = 1 << cs[0] | 1 << cs[1] | 1 << cs[2] | 1 << cs[3] | 1 << cs[4]; for (i = -1, v = o = 0; i < 5; i++, o = Math.pow(2, cs[i] * 4)) {v += o * ((v / o & 15) + 1);} v = v % 15 - ((s / (s & -s) == 31) || (s == 0x403c) ? 3 : 1); v -= (ss[0] == (ss[1] | ss[2] | ss[3] | ss[4])) * ((s == 0x7c00) ? -5 : 1); return pokerHands[v]; } 

用法:

 evaluateHand([A,10,J,K,Q],[C,C,C,C,C]); // Royal Flush 

现在它的作用(非常简单)就是当它存在2时将1放入s的第3位,而当存在3时将其放入第4位等等,所以对于上面的例子, s看起来像这样:

0b111110000000000

对于[A,2,3,4,5],它看起来像这样

0b100 0000 0011 1100

等等

v使用四位来记录同一张卡的多个出现,所以它是52位长,如果你有三个A和两个国王,它的8个MSB位看起来像:

0111 0011 ...

最后一行然后检查刷新或同花顺或皇家同花顺(0x7c00)。

这是一个简单的五卡手比较方法,我用它来帮助最初填充查找表:

我没有尽可能简洁,而是优先考虑类型安全和清晰的自我记录代码。 如果您不熟悉我正在使用的Guava类型,您可以浏览他们的文档 。

我将在这里包含代码(减去底部枚举常量的静态导入),虽然它真的太长了,无法在答案中轻松查看。

 import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Ordering.from; import static com.google.common.collect.Ordering.natural; import static java.util.Comparator.comparing; import static java.util.Comparator.comparingInt; import java.util.Comparator; import java.util.EnumSet; import java.util.LinkedList; import java.util.Set; import java.util.function.Function; import com.google.common.collect.EnumMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Ordering; public class Hand implements Comparable { public final Category category; private final LinkedList distinctRanks = new LinkedList<>(); public Hand(Set cards) { checkArgument(cards.size() == 5); Set suits = EnumSet.noneOf(Suit.class); Multiset ranks = EnumMultiset.create(Rank.class); for (Card card : cards) { suits.add(card.suit); ranks.add(card.rank); } Set> entries = ranks.entrySet(); for (Entry entry : byCountThenRank.immutableSortedCopy(entries)) { distinctRanks.addFirst(entry.getElement()); } Rank first = distinctRanks.getFirst(); int distinctCount = distinctRanks.size(); if (distinctCount == 5) { boolean flush = suits.size() == 1; if (first.ordinal() - distinctRanks.getLast().ordinal() == 4) { category = flush ? STRAIGHT_FLUSH : STRAIGHT; } else if (first == ACE && distinctRanks.get(1) == FIVE) { category = flush ? STRAIGHT_FLUSH : STRAIGHT; // ace plays low, move to end distinctRanks.addLast(distinctRanks.removeFirst()); } else { category = flush ? FLUSH : HIGH_CARD; } } else if (distinctCount == 4) { category = ONE_PAIR; } else if (distinctCount == 3) { category = ranks.count(first) == 2 ? TWO_PAIR : THREE_OF_A_KIND; } else { category = ranks.count(first) == 3 ? FULL_HOUSE : FOUR_OF_A_KIND; } } @Override public final int compareTo(Hand that) { return byCategoryThenRanks.compare(this, that); } private static final Ordering> byCountThenRank; private static final Comparator byCategoryThenRanks; static { Comparator> byCount = comparingInt(Entry::getCount); Comparator> byRank = comparing(Entry::getElement); byCountThenRank = from(byCount.thenComparing(byRank)); Comparator byCategory = comparing((Hand hand) -> hand.category); Function> getRanks = (Hand hand) -> hand.distinctRanks; Comparator byRanks = comparing(getRanks, natural().lexicographical()); byCategoryThenRanks = byCategory.thenComparing(byRanks); } public enum Category { HIGH_CARD, ONE_PAIR, TWO_PAIR, THREE_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH; } public enum Rank { TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE; } public enum Suit { DIAMONDS, CLUBS, HEARTS, SPADES; } public enum Card { TWO_DIAMONDS(TWO, DIAMONDS), THREE_DIAMONDS(THREE, DIAMONDS), FOUR_DIAMONDS(FOUR, DIAMONDS), FIVE_DIAMONDS(FIVE, DIAMONDS), SIX_DIAMONDS(SIX, DIAMONDS), SEVEN_DIAMONDS(SEVEN, DIAMONDS), EIGHT_DIAMONDS(EIGHT, DIAMONDS), NINE_DIAMONDS(NINE, DIAMONDS), TEN_DIAMONDS(TEN, DIAMONDS), JACK_DIAMONDS(JACK, DIAMONDS), QUEEN_DIAMONDS(QUEEN, DIAMONDS), KING_DIAMONDS(KING, DIAMONDS), ACE_DIAMONDS(ACE, DIAMONDS), TWO_CLUBS(TWO, CLUBS), THREE_CLUBS(THREE, CLUBS), FOUR_CLUBS(FOUR, CLUBS), FIVE_CLUBS(FIVE, CLUBS), SIX_CLUBS(SIX, CLUBS), SEVEN_CLUBS(SEVEN, CLUBS), EIGHT_CLUBS(EIGHT, CLUBS), NINE_CLUBS(NINE, CLUBS), TEN_CLUBS(TEN, CLUBS), JACK_CLUBS(JACK, CLUBS), QUEEN_CLUBS(QUEEN, CLUBS), KING_CLUBS(KING, CLUBS), ACE_CLUBS(ACE, CLUBS), TWO_HEARTS(TWO, HEARTS), THREE_HEARTS(THREE, HEARTS), FOUR_HEARTS(FOUR, HEARTS), FIVE_HEARTS(FIVE, HEARTS), SIX_HEARTS(SIX, HEARTS), SEVEN_HEARTS(SEVEN, HEARTS), EIGHT_HEARTS(EIGHT, HEARTS), NINE_HEARTS(NINE, HEARTS), TEN_HEARTS(TEN, HEARTS), JACK_HEARTS(JACK, HEARTS), QUEEN_HEARTS(QUEEN, HEARTS), KING_HEARTS(KING, HEARTS), ACE_HEARTS(ACE, HEARTS), TWO_SPADES(TWO, SPADES), THREE_SPADES(THREE, SPADES), FOUR_SPADES(FOUR, SPADES), FIVE_SPADES(FIVE, SPADES), SIX_SPADES(SIX, SPADES), SEVEN_SPADES(SEVEN, SPADES), EIGHT_SPADES(EIGHT, SPADES), NINE_SPADES(NINE, SPADES), TEN_SPADES(TEN, SPADES), JACK_SPADES(JACK, SPADES), QUEEN_SPADES(QUEEN, SPADES), KING_SPADES(KING, SPADES), ACE_SPADES(ACE, SPADES); public final Rank rank; public final Suit suit; Card(Rank rank, Suit suit) { this.rank = rank; this.suit = suit; } } } 

这是dansalmo程序的修改版本,适用于holdem手:

 def holdem(board, hands): scores = [(evaluate((board + ' ' + hand).split()), i) for i, hand in enumerate(hands)] best = max(scores)[0] return [x[1] for x in filter(lambda(x): x[0] == best, scores)] def evaluate(hand): ranks = '23456789TJQKA' if len(hand) > 5: return max([evaluate(hand[:i] + hand[i+1:]) for i in range(len(hand))]) score, ranks = zip(*sorted((cnt, rank) for rank, cnt in {ranks.find(r): ''.join(hand).count(r) for r, _ in hand}.items())[::-1]) if len(score) == 5: # if there are 5 different ranks it could be a straight or a flush (or both) if ranks[0:2] == (12, 3): ranks = (3, 2, 1, 0, -1) # adjust if 5 high straight score = ([1,(3,1,2)],[(3,1,3),(5,)])[len({suit for _, suit in hand}) == 1][ranks[0] - ranks[4] == 4] # high card, straight, flush, straight flush return score, ranks def test(): print holdem('9H TC JC QS KC', [ 'JS JD', # 0 'AD 9C', # 1 A-straight 'JD 2C', # 2 'AC 8D', # 3 A-straight 'QH KH', # 4 'TS 9C', # 5 'AH 3H', # 6 A-straight '3D 2C', # 7 # '8C 2C', # 8 flush ]) test() 

holdem()返回获胜手牌的索引列表。 在test()示例中,[1,3,6],因为三只手用a分开底池,或[8]如果没有注释一手牌。

如果你将一个手表示为一个例如Card对象的数组,那么我会有循环遍历这个数组的方法,并确定它是否有2-of-kind,flush等等 – 如果是,那么输入它; 所以你可以让3ofaKind()方法返回5,如果一只手有三个5s。 然后我会建立一个可能性的层次结构(例如,3种类型高于2种)并从那里开始工作。 这些方法本身应该非常简单易懂。

如果你只是想了解它是如何工作的,这里是简单的算法:

 HandStrength(ourcards,boardcards) { ahead = tied = behind = 0 ourrank = Rank(ourcards,boardcards) /* Consider all two-card combinations of the remaining cards. */ for each case(oppcards) { opprank = Rank(oppcards,boardcards) if(ourrank>opprank) ahead += 1 else if(ourrank==opprank) tied += 1 else /* < */ behind += 1 } handstrength = (ahead+tied/2) / (ahead+tied+behind) return(handstrength) } 

这是来自Darse Billings的“计算机中的算法和评估”。

这是转换为R的算法,使用6张牌进行测试,对应于以下结果给出的42.504种组合:

C65

扑克手的组合。 由于加工限制,没有使用13卡套测试(它将对应于2.598.960组合)。

该算法用字符串表示一个手 ,由2部分组成:

  • 带有订购卡数的5个字符(例如“31100”表示三种)
  • 卡号由’B’(Deuce)到’N’(Ace)的字母组成(例如’NILH’表示Ace,Queen,Nine和Eight)。 它以字母’B’开头,因为A2345扑克牌,其中Ace位于’2’之前,其中Ace(Ace)将具有值’A’。

因此,例如,“32000NB”将是三个Aces和两个Deuce的满堂。

扑克牌值字符串便于比较和排序

 library(tidyverse) library(gtools) hand_value <- function(playerhand) { numbers <- str_split("23456789TJQKA", "")[[1]] suits <- str_split("DCHS", "")[[1]] playerhand <- data.frame(card = playerhand) %>% separate(card, c("number", "suit"), sep = 1) number_values <- data.frame(number = numbers, value = LETTERS[2:14], stringsAsFactors = FALSE) playerhand_number <- playerhand %>% group_by(number) %>% count(number) %>% inner_join(number_values, by = "number") %>% arrange(desc(n), desc(value)) playerhand_suit <- playerhand %>% group_by(suit) %>% count(suit) %>% arrange(desc(n)) if (nrow(playerhand_number) == 5) { if (playerhand_number[1,1] == 'A' & playerhand_number[2,1] == '5') playerhand_number <- data.frame(playerhand_number[,1:2], value = str_split("EDCBA", "")[[1]], stringsAsFactors = FALSE) straight <- asc(playerhand_number[1,3]) - asc(playerhand_number[5,3]) == 4 } else straight = FALSE flush <- nrow(playerhand_suit) == 1 if (flush) { if (straight) playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(5, 0, 0, 0, 0), stringsAsFactors = FALSE) else playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(3, 1, 1, 2, 0), stringsAsFactors = FALSE) } else { if (straight) playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(3, 1, 1, 1, 0), stringsAsFactors = FALSE) } playerhand_value <- append(append(c(playerhand_number$n), rep("0", 5 - nrow(playerhand_number))), c(playerhand_number$value)) playerhand_value <- paste(playerhand_value, collapse = '') playerhand_value } 

使用上面示例中的相同指针测试函数:

 l <- c("8C TS KC 9H 4S", "7D 2S 5D 3S AC", "8C AD 8D AC 9C", '7C 5H 8D TD KS') t <- as_tibble(l) t <- t %>% mutate(hand = str_split(value, " ")) %>% select(hand) t <- t %>% mutate(value = sapply(t[,1]$hand, hand_value)) %>% arrange(desc(value)) paste(t[[1]][[1]], collapse = " ") 

返回相同的结果:

 [1] "8C AD 8D AC 9C" 

希望能帮助到你。