Je pense à l'évaluation de la main de poker (5 cartes) dans Java
. Maintenant, je recherche la simplicité et la clarté plutôt que la performance et l'efficacité. Je peux probablement écrire un algorithme "naïf" mais cela nécessite beaucoup de code.
J'ai aussi vu quelques bibliothèques d'évaluation de poker, qui utilisent des opérations de hachage et au niveau des bits, mais elles ont l'air plutôt complexes.
Quel est l'algorithme "le plus propre et le plus simple" pour l'évaluation des mains au poker?
Voici une fonction de notation de poker à 5 cartes basée sur un histogramme très courte mais complète en Python (2.x). Il sera considérablement plus long s'il est converti en 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'
Les tables de consultation constituent la solution la plus simple et la plus simple au problème, ainsi que la plus rapide. L'astuce consiste à gérer la taille de la table et à garder le mode d'utilisation suffisamment simple pour traiter très rapidement ( compromis espace-temps ). Evidemment, en théorie, vous pouvez simplement encoder chaque main qui peut être tenue et avoir un tableau d’évaluations, puis --poof--, une table de consultation et vous avez terminé. Malheureusement, une telle table serait énorme et ingérable pour la plupart des machines, et vous inviterait invariablement à écraser des disques de toute façon à mesure que la mémoire sera échangée.
La solution dite deux plus deux utilise une grande table de 10M, mais implique littéralement une consultation de table pour chaque carte dans la main. Il est peu probable que vous trouviez un algorithme plus rapide et plus simple à comprendre.
D'autres solutions impliquent davantage de tables compressées avec une indexation plus complexe, mais elles sont facilement compréhensibles et assez rapides (bien que beaucoup plus lentes que 2 + 2). C'est ici que vous voyez le langage concernant le hachage, etc. - astuces pour réduire la taille d'une table à des tailles plus gérables.
Dans tous les cas, les solutions de recherche sont des ordres de grandeur plus rapides que les solutions de type histogramme-danse-sur-votre-tête-comparaison-spéciale-casse-et-le-par-la-voie dont dignes d'un second regard.
En réalité, vous n’avez pas besoin de fonctions avancées, cela peut être fait au niveau du bit: (source: http://www.codeproject.com/Articles/569271/A-Poker-hand-analyzer-in-JavaScript-using- bit-math )
(Celui-ci est en fait écrit en JavaScript, mais vous pouvez évaluer JavaScript à partir de Java si nécessaire. Cela ne devrait donc pas poser de problème. En outre, c'est aussi court que possible, donc même pour illustrer l'approche. ..):
D'abord, vous divisez vos cartes en deux tableaux: les rangs (cs) et les costumes (ss). Pour représenter les costumes, vous utiliserez soit 1,2,4, soit 8 (c'est-à-dire 0b0001, 0b0010, ...):
var J=11, Q=12, K=13, A=14, C=1, D=2, H=4, S=8;
Maintenant, voici la magie:
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];
}
Usage:
evaluateHand([A,10,J,K,Q],[C,C,C,C,C]); // Royal Flush
Maintenant ce qu’il fait (très brièvement) c’est qu’il met 1 dans le 3ème bit de s quand il y a un 2, dans le 4ème quand il y a 3, etc., donc pour l’exemple ci-dessus, s ressemble à ceci:
0b111110000000000
pour [A, 2,3,4,5] cela ressemblerait à ceci
0b100 0000 0011 1100
etc.
v utilise quatre bits pour enregistrer plusieurs occurrences de la même carte. Sa longueur est donc de 52 bits. Si vous avez trois as et deux rois, ses 8 bits de poids fort ressemblent à:
0111 0011 ...
La dernière ligne recherche ensuite une couleur, une couleur ou une couleur royale (0x7c00).
Voici une version modifiée du programme de dansalmo qui fonctionne pour les mains de 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 () renvoie une liste d'indices de la ou des mains gagnantes. Dans l’exemple test (), c’est [1, 3, 6], puisque les trois mains avec les as divisent le pot, ou [8] si la main de couleur n’est pas commentée.
Voici une approche naïve de la comparaison des mains à cinq cartes que j'utilise pour aider à remplir initialement une table de recherche:
Au lieu d'être aussi concis que possible, j'ai privilégié la sécurité et un code clair et auto-documenté. Si vous ne connaissez pas les types de goyave que j'utilise, vous pouvez parcourir leur documentation .
Et je vais inclure le code ici (moins les importations statiques pour les constantes d’énumération au bas de la page), bien que ce soit vraiment trop long à afficher confortablement dans une réponse.
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<Hand> {
public final Category category;
private final LinkedList<Rank> distinctRanks = new LinkedList<>();
public Hand(Set<Card> cards) {
checkArgument(cards.size() == 5);
Set<Suit> suits = EnumSet.noneOf(Suit.class);
Multiset<Rank> ranks = EnumMultiset.create(Rank.class);
for (Card card : cards) {
suits.add(card.suit);
ranks.add(card.rank);
}
Set<Entry<Rank>> entries = ranks.entrySet();
for (Entry<Rank> 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<Entry<Rank>> byCountThenRank;
private static final Comparator<Hand> byCategoryThenRanks;
static {
Comparator<Entry<Rank>> byCount = comparingInt(Entry::getCount);
Comparator<Entry<Rank>> byRank = comparing(Entry::getElement);
byCountThenRank = from(byCount.thenComparing(byRank));
Comparator<Hand> byCategory = comparing((Hand hand) -> hand.category);
Function<Hand, Iterable<Rank>> getRanks =
(Hand hand) -> hand.distinctRanks;
Comparator<Hand> 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;
}
}
}
Si vous voulez juste comprendre comment cela fonctionne, voici un algorithme simple:
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)
}
Il est tiré de "ALGORITHMES ET ÉVALUATION IN POKER INFORMATIQUE" de Darse Billings.
Si vous représentez une main sous la forme d'un tableau d'objets, par exemple, Card
, vous disposez de méthodes permettant de parcourir ce tableau en boucle et de déterminer s'il possède un type de type 2, un flush, etc. tapez c'est; vous pourriez donc avoir la méthode 3ofaKind()
retourner 5 si une main avait trois 5. Ensuite, j’établirais une hiérarchie des possibilités (par exemple, 3 d’un type est supérieur à 2 d’un genre) et travaille à partir de là. Les méthodes elles-mêmes devraient être assez simples à écrire.
Voici une implémentation simple basée sur des règles dans Kotlin:
class PokerHand constructor(hand: String) : Comparable<PokerHand> {
companion object {
const val WIN = 1
const val TIE = 0
const val LOSS = -1
}
val cards: List<Card>
val isStraightFlush: Boolean
get() = isStraight && isFlush
val isFourOfAKind: Boolean
get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 4 }
val isFullHouse: Boolean
get() = cards.groupBy { it.weight }.map { it.value }.size == 2
val isFlush: Boolean
get() = cards.groupBy { it.suit }.map { it.value }.size == 1
val isStraight: Boolean
get() = cards.map { it.weight.ordinal } == (cards[0].weight.ordinal..cards[0].weight.ordinal + 4).toList()
val isThreeOfAKind: Boolean
get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 3 }
val isTwoPair: Boolean
get() = cards.groupBy { it.weight }.map { it.value }.filter { it.size == 2 }.count() == 2
val isPair: Boolean
get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 2 }
init {
val cards = ArrayList<Card>()
hand.split(" ").forEach {
when (it.length != 2) {
true -> throw RuntimeException("A card code must be two characters")
else -> cards += Card(Weight.forCode(it[0]), Suit.forCode(it[1]))
}
}
if (cards.size != 5) {
throw RuntimeException("There must be five cards in a hand")
}
this.cards = cards.sortedBy { it.weight.ordinal }
}
override fun compareTo(other: PokerHand): Int = when {
(this.isStraightFlush || other.isStraightFlush) ->
if (this.isStraightFlush) if (other.isStraightFlush) compareByHighCard(other) else WIN else LOSS
(this.isFourOfAKind || other.isFourOfAKind) ->
if (this.isFourOfAKind) if (other.isFourOfAKind) compareByHighCard(other) else WIN else LOSS
(this.isFullHouse || other.isFullHouse) ->
if (this.isFullHouse) if (other.isFullHouse) compareByHighCard(other) else WIN else LOSS
(this.isFlush || other.isFlush) ->
if (this.isFlush) if (other.isFlush) compareByHighCard(other) else WIN else LOSS
(this.isStraight || other.isStraight) ->
if (this.isStraight) if (other.isStraight) compareByHighCard(other) else WIN else LOSS
(this.isThreeOfAKind || other.isThreeOfAKind) ->
if (this.isThreeOfAKind) if (other.isThreeOfAKind) compareByHighCard(other) else WIN else LOSS
(this.isTwoPair || other.isTwoPair) ->
if (this.isTwoPair) if (other.isTwoPair) compareByHighCard(other) else WIN else LOSS
(this.isPair || other.isPair) ->
if (this.isPair) if (other.isPair) compareByHighCard(other) else WIN else LOSS
else -> compareByHighCard(other)
}
private fun compareByHighCard(other: PokerHand, index: Int = 4): Int = when {
(index < 0) -> TIE
cards[index].weight === other.cards[index].weight -> compareByHighCard(other, index - 1)
cards[index].weight.ordinal > other.cards[index].weight.ordinal -> WIN
else -> LOSS
}
}
Détails d'implémentation:
2H 3H 4H 5H 6H
Comparable<PokerHand>
pour évaluer par rapport à une autre main en utilisant une approche de règles simples, par exemple, une quinte flush bat quatre points du même genre, ce qui bat un full house, etc. Les sources sont ici .
Voici l'algorithme traduit enR, testé avec un jeu de 6 cartes, correspondant à 42.504 combinaisons données par le résultat de:
combinaisons de mains de poker. N'a pas été testé avec un jeu de 13 cartes en raison de limitations de traitement (cela correspondrait à 2.598.960 combinaisons).
L'algorithme représente la valeur d'une main par une chaîne , composée de 2 parties:
Ainsi, par exemple, "32000NB" sera un full house de trois As et de deux Deuce.
La chaîne de valeur de la main de poker est pratique à des fins de comparaison et de commande .
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
}
Tester la fonction avec les mêmes mains que dans l'exemple ci-dessus:
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 = " ")
Ce qui retourne le même résultat:
[1] "8C AD 8D AC 9C"
J'espère que ça aide.