我正在思考在Java
中对扑克牌手(5张牌)进行评估。现在我寻求的是简单和清晰度而不是性能和效率。我可能可以编写一个“幼稚”的算法,但需要大量代码。
我也看到了一些扑克牌评估库,它们使用哈希和位运算,但它们看起来相当复杂。
什么是最“简洁和简单”的扑克牌手评估算法?
我正在思考在Java
中对扑克牌手(5张牌)进行评估。现在我寻求的是简单和清晰度而不是性能和效率。我可能可以编写一个“幼稚”的算法,但需要大量代码。
我也看到了一些扑克牌评估库,它们使用哈希和位运算,但它们看起来相当复杂。
什么是最“简洁和简单”的扑克牌手评估算法?
这是一段基于直方图的 5 张扑克牌得分函数,使用 Python(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'
查找表是解决问题最直接、最简单、也是最快的方法。诀窍在于管理表的大小和保持使用模式足够简单,以便能够非常快速地处理(时空权衡)。显然,在理论上,你可以对每个可能出现的手牌进行编码,并拥有一个评估数组,然后一次表查找即可完成。不过,这样的表对大多数机器来说都是巨大且无法管理的,并且随着内存大量交换,你无论如何都会遇到磁盘读写问题。
所谓的二加二方案需要一个大表格(10M),但实际上是每张卡牌只需一次表查找。你不太可能找到一个更快、更易于理解的算法。
其他方案涉及更压缩的表格和更复杂的索引,但它们很容易理解,而且速度相当快(虽然比2+2慢得多)。这就是你看到有关哈希等技巧的语言——用于将表的大小减小为更易管理的尺寸。
总之,查找解决方案比直方图排序舞蹈-在你的头上比较特殊情况-顺便问一下,那是一张什么牌解决方案要快上数量级,几乎没有一个值得第二眼看。
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
import java.util.*; // Arrays, Set
import java.util.Map.Entry;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.*; // counting, groupingBy
public record Hand(Category category, Rank... ranks) implements Comparable<Hand> {
public static Hand evaluate(Set<Card> cards) {
if (cards.size() != 5) { throw new IllegalArgumentException(); }
var flush = cards.stream().map(Card::suit).distinct().count() == 1;
var counts = cards.stream().collect(groupingBy(Card::rank, counting()));
var ranks = counts.entrySet().stream().sorted(
comparing(Entry<Rank, Long>::getValue).thenComparing(Entry::getKey).reversed()
).map(Entry::getKey).toArray(Rank[]::new);
if (ranks.length == 4) {
return new Hand(ONE_PAIR, ranks);
} else if (ranks.length == 3) {
return new Hand(counts.get(ranks[0]) == 2 ? TWO_PAIR : THREE_OF_A_KIND, ranks);
} else if (ranks.length == 2) {
return new Hand(counts.get(ranks[0]) == 3 ? FULL_HOUSE : FOUR_OF_A_KIND, ranks);
} else if (ranks[0].ordinal() - ranks[4].ordinal() == 4) {
return new Hand(flush ? STRAIGHT_FLUSH : STRAIGHT, ranks[0]);
} else if (ranks[0].equals(ACE) && ranks[1].equals(FIVE)) { // A-2-3-4-5
return new Hand(flush ? STRAIGHT_FLUSH : STRAIGHT, FIVE);
} else {
return new Hand(flush ? FLUSH : HIGH_CARD, ranks);
}
}
@Override
public int compareTo(Hand that) { // first compare by category, then compare ranks lexicographically
return comparing(Hand::category).thenComparing(Hand::ranks, Arrays::compare).compare(this, that);
}
}
enum Category {HIGH_CARD, ONE_PAIR, TWO_PAIR, THREE_OF_A_KIND, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH}
enum Rank {TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE}
enum Suit {DIAMONDS, CLUBS, HEARTS, SPADES}
record Card(Rank rank, Suit suit) {}
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的手牌分开了奖池,或者如果删去flush hand,则为[8]。
Python 3版本
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'
为了避免int与tuple比较错误,需要将1替换为(1,)
Table Cards
{ Value: '9', Suit: 'H' }
{ Value: 'A', Suit: 'H' }
{ Value: '9', Suit: 'D' }
{ Value: '7', Suit: 'S' }
{ Value: '6', Suit: 'S' }
表示我的卡牌的3D数组:
A 2 3 4 5 6 7 8 9 10 J Q K A
Spades
[ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 ]
Diamonds
[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ]
Clubs
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
Hearts
[ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]
通过上面的数值,我可以判断我拥有一对9和一张A,7,6的踢脚牌。
你可以看到数组中包含两个A。这是因为您想要从A开始测试顺子。所以是(A,2,3,4,5)。
如果您想要测试七张牌而不是五张牌,您也可以使用此系统。您可以将用户的两张牌与桌子上的五张牌一起使用我的系统进行运算。您还可以对桌上其他玩家做同样的事情并比较结果。
希望这能有些帮助。
如果您将一手牌表示为一个数组,例如Card
对象,则我将创建循环遍历该数组并确定它是否具有2个相同的、同花等类型的方法;如果是这样,还可以确定其类型。例如,3ofaKind()
方法如果一手牌有三个5,则返回5。然后,我会建立一个可能性层级(例如,3张相同的牌比2张相同的牌更高),并从那里开始工作。这些方法本身应该很容易编写。
由于问题是关于Java的,而答案是关于Python的,所以我想发布一篇新的Java答案。我的目标是让这个函数易于理解。
The Evaluator will score:
Royal flush - 10000
Straight flush - 9000 + highest card
Four of a kind - 8000 + card
Full house - 7000 + card
----- Here we have a smal gap
Flush - 5000 + highest card
Straight - 4000 + highest card
Three of a kind - 3000 + card
Two pair - 2000 + 13*card1 + card2 (card1 > card2)
Pair - 1000 + card
高牌不计分!(如果高牌打平,您需要继续检查下一张牌等等...)如果您想在评估器中对此进行编码,您确实需要位码,而且程序会变得不太容易理解。幸运的是,在两个已排序的手牌上检查高牌很容易,因此您首先要检查得分。如果得分相等,则检查高牌以打破平局;-)
没有更多的话,这里是代码(适用于已排序的手牌):
public static int scoreHand(int[] hand) {
return scoreSeries(hand) + scoreKinds(hand);
}
public static int scoreStraight(int[] hand) {
for(int i = 1; i < hand.length; i++) {
if ((hand[i] % 100 + 1) != (hand[i - 1] % 100)) return 0;
}
return 4000;
}
public static int scoreFlush(int[] hand) {
for(int i = 1; i < hand.length; i++) {
if ((hand[i] / 100) != (hand[i - 1] / 100)) return 0;
}
return 5000;
}
public static int scoreSeries(int[] hand) {
int score = scoreFlush(hand) + scoreStraight(hand);
if (hand[0] % 100 == 12 && score == 9000)
return 10000;
if (score > 0) return score + hand[0] % 100;
return 0;
}
public static int scoreKinds(int[] hand) {
int[] counts = new int[13], highCards = new int[2];
int max = 1, twoSets = 0;
for(int i = 0; i < hand.length; i++) {
counts[hand[i] % 100]++;
if (max > 1 && counts[hand[i] % 100] == 2) {
twoSets = 1;
highCards[1] = hand[i] % 100;
}
if (max < counts[hand[i] % 100]) {
max = counts[hand[i] % 100];
highCards[0] = hand[i] % 100;
}
}
if (max == 1) return 0;
int score = (max * 2 + twoSets) * 1000;
if (score < 7000) score -= 3000;
if (max == 2 && twoSets == 1) {
if (highCards[1] > highCards[0]) swap(highCards, 0, 1);
score += 13 * highCards[0] + highCards[1];
} else {
if (counts[highCards[1]] > counts[highCards[0]]) swap(highCards, 0, 1);
score += highCards[0];
}
return score;
}
如果您只是想了解它的工作原理,这里有一个简单的算法:
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)
}