Алгоритм присвоения 5-карточной покерной руки
Я занимаюсь разработкой игры в покер в качестве проекта для колледжа, и в настоящее время наша задача состоит в том, чтобы написать алгоритм для подсчета комбинации из 5 карт, чтобы можно было сравнивать результаты двух рук, чтобы определить, какая из них лучше. Счет руки не имеет ничего общего с вероятностью того, какие руки могут быть сделаны в ходе розыгрыша случайных карт и т. Д. - Счет руки основан исключительно на 5 картах в руке, и никаких других карт в колоде.
В качестве примера решения, которое нам дали, было дать оценку по умолчанию для каждого типа покерной руки, причем оценка отражает, насколько хороша эта рука - например, так:
//HAND TYPES:
ROYAL_FLUSH = 900000
STRAIGHT_FLUSH = 800000
...
TWO_PAIR = 200000
ONE_PAR = 100000
Затем, если сравниваются две руки одного типа, значения карт в руках должны быть учтены в счете руки.
Так, например, следующая формула может быть использована для оценки руки:
HAND_TYPE + (each card value in the hand)^(the number of occurences of that value)
Таким образом, для фулл-хауса из трех Куинс и двух 7-х, счет будет:
600000 + 12^3 + 7^2
Эта формула работает по большей части, но я определил, что в некоторых случаях две одинаковые руки могут вернуть один и тот же счет, когда одна должна фактически победить другую. Пример этого:
hand1 = 4C, 6C, 6H, JS, KC
hand2 = 3H, 4H, 7C, 7D, 8H
Эти две руки имеют одну пару, поэтому их соответствующие оценки:
100000 + 4^1 + 6^2 + 11^1 + 13^1 = 100064
100000 + 3^1 + 4^1 + 7^2 + 8^1 = 100064
Это приводит к ничьей, когда ясно, что пара 7s превосходит пару 6s.
Как я могу улучшить эту формулу, или даже, какую формулу лучше использовать?
Кстати, в моем коде руки хранятся в массиве значений каждой карты в порядке возрастания, например:
[2H, 6D, 10C, KS, AS]
РЕДАКТИРОВАТЬ:
Вот мое окончательное решение благодаря ответам ниже:
/**
* Sorts cards by putting the "most important" cards first, and the rest in decreasing order.
* e.g. High Hand: KS, 9S, 8C, 4D, 2H
* One Pair: 3S, 3D, AH, 7S, 2C
* Full House: 6D, 6C, 6S, JC, JH
* Flush: KH, 9H, 7H, 6H, 3H
*/
private void sort() {
Arrays.sort(hand, Collections.reverseOrder()); // Initially sorts cards in descending order of game value
if (isFourOfAKind()) { // Then adjusts for hands where the "most important" cards
sortFourOfAKind(); // must come first
} else if (isFullHouse()) {
sortFullHouse();
} else if (isThreeOfAKind()) {
sortThreeOfAKind();
} else if (isTwoPair()) {
sortTwoPair();
} else if (isOnePair()){
sortOnePair();
}
}
private void sortFourOfAKind() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 4].getGameValue()) { // If the four of a kind are the last four cards
swapCardsByIndex(0, HAND_SIZE - 1); // swap the first and last cards
} // e.g. AS, 9D, 9H, 9S, 9C => 9C, 9D, 9H, 9S, AS
}
private void sortFullHouse() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) { // If the 3 of a kind cards are the last three
swapCardsByIndex(0, HAND_SIZE - 2); // swap cards 1 and 4, 2 and 5
swapCardsByIndex(HAND_SIZE - 4, HAND_SIZE - 1); // e.g. 10D, 10C, 6H, 6S, 6D => 6S, 6D, 6H, 10D, 10C
}
}
private void sortThreeOfAKind() { // If the 3 of a kind cards are the middle 3 cards
if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue() && hand[HAND_SIZE - 1].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) { // swap cards 1 and 4
swapCardsByIndex(0, HAND_SIZE - 2); // e.g. AH, 8D, 8S, 8C, 7D => 8C, 8D, 8S, AH, 7D
} else if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue() && hand[HAND_SIZE - 4].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) {
Arrays.sort(hand); // If the 3 of a kind cards are the last 3,
swapCardsByIndex(HAND_SIZE - 1, HAND_SIZE - 2); // reverse the order (smallest game value to largest)
} // then swap the last two cards (maintain the large to small ordering)
} // e.g. KS, 9D, 3C, 3S, 3H => 3H, 3S, 3C, 9D, KS => 3H, 3S, 3C, KS, 9D
private void sortTwoPair() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 4].getGameValue()) { // If the two pairs are the last 4 cards
for (int i = 0; i < HAND_SIZE - 1; i++) { // "bubble" the first card to the end
swapCardsByIndex(i, i + 1); // e.g. AH, 7D, 7S, 6H, 6C => 7D, 7S, 6H, 6C, AH
}
} else if (hand[0].getGameValue() == hand[HAND_SIZE - 4].getGameValue() && hand[HAND_SIZE - 2].getGameValue() == hand[HAND_SIZE - 1].getGameValue()) { // If the two pairs are the first and last two cards
swapCardsByIndex(HAND_SIZE - 3, HAND_SIZE - 1); // swap the middle and last card
} // e.g. JS, JC, 8D, 4H, 4S => JS, JC, 4S, 4H, 8D
}
private void sortOnePair() { // If the pair are cards 2 and 3, swap cards 1 and 3
if (hand[HAND_SIZE - 4].getGameValue() == hand[HAND_SIZE - 3].getGameValue()) { // e.g QD, 8H, 8C, 6S, 4J => 8C, 8H, QD, 6S, 4J
swapCardsByIndex(0, HAND_SIZE - 3);
} else if (hand[HAND_SIZE - 3].getGameValue() == hand[HAND_SIZE - 2].getGameValue()) { // If the pair are cards 3 and 4, swap 1 and 3, 2 and 4
swapCardsByIndex(0, HAND_SIZE - 3); // e.g. 10S, 8D, 4C, 4H, 2H => 4C, 4H, 10S, 8D, 2H
swapCardsByIndex(HAND_SIZE - 4, HAND_SIZE - 2);
} else if (hand[HAND_SIZE - 2].getGameValue() == hand[HAND_SIZE - 1].getGameValue()) { // If the pair are the last 2 cards, reverse the order
Arrays.sort(hand); // and then swap cards 3 and 5
swapCardsByIndex(HAND_SIZE - 3, HAND_SIZE - 1); // e.g. 9H, 7D, 6C, 3D, 3S => 3S, 3D, 6C, 7D, 9H => 3S, 3D, 9H, 7D, 6C
}
}
/**
* Swaps the two cards of the hand at the indexes taken as parameters
* @param index1
* @param index2
*/
private void swapCardsByIndex(int index1, int index2) {
PlayingCard temp = hand[index1];
hand[index1] = hand[index2];
hand[index2] = temp;
}
/**
* Gives a unique value of any hand, based firstly on the type of hand, and then on the cards it contains
* @return The Game Value of this hand
*
* Firstly, a 24 bit binary string is created where the most significant 4 bits represent the value of the type of hand
* (defined as constants private to this class), the last 20 bits represent the values of the 5 cards in the hand, where
* the "most important" cards are at greater significant places. Finally, the binary string is converter to an integer.
*/
public int getGameValue() {
String handValue = addPaddingToBinaryString(Integer.toBinaryString(getHandValue()));
for (int i = 0; i < HAND_SIZE; i++) {
handValue += addPaddingToBinaryString(Integer.toBinaryString(getCardValue(hand[i])));
}
return Integer.parseInt(handValue, 2);
}
/**
* @param binary
* @return the same binary string padded to 4 bits long
*/
private String addPaddingToBinaryString(String binary) {
switch (binary.length()) {
case 1: return "000" + binary;
case 2: return "00" + binary;
case 3: return "0" + binary;
default: return binary;
}
}
/**
* @return Default value for the type of hand
*/
private int getHandValue() {
if (isRoyalFlush()) { return ROYAL_FLUSH_VALUE; }
if (isStraightFlush()) { return STRAIGHT_FLUSH_VALUE; }
if (isFourOfAKind()) { return FOUR_OF_A_KIND_VALUE; }
if (isFullHouse()) { return FULL_HOUSE_VALUE; }
if (isFlush()) { return FLUSH_VALUE; }
if (isStraight()) { return STRAIGHT_VALUE; }
if (isThreeOfAKind()) { return THREE_OF_A_KIND_VALUE; }
if (isTwoPair()) { return TWO_PAIR_VALUE; }
if (isOnePair()) { return ONE_PAIR_VALUE; }
return 0;
}
/**
* @param card
* @return the value for a given card type, used to calculate the Hand's Game Value
* 2H = 0, 3D = 1, 4S = 2, ... , KC = 11, AH = 12
*/
private int getCardValue(PlayingCard card) {
return card.getGameValue() - 2;
}
3 ответа
Есть 10 признанных покерных комбинаций:
9 - Royal flush
8 - Straight flush (special case of royal flush, really)
7 - Four of a kind
6 - Full house
5 - Flush
4 - Straight
3 - Three of a kind
2 - Two pair
1 - Pair
0 - High card
Если вы не учитываете масти, существует только 13 возможных значений карт. Значения карты:
2 - 0
3 - 1
4 - 2
5 - 3
6 - 4
7 - 5
8 - 6
9 - 7
10 - 8
J - 9
Q - 10
K - 11
A - 12
Требуется 4 бита для кодирования руки и 4 бита для кодирования карт. Вы можете кодировать всю руку в 24 битах.
Флеш-рояль будет 1001 1100 1011 1010 1001 1000 (0x9CBA98)
Стрит с высотой 7 будет 0100 0101 0100 0011 0010 0001 (0x454321)
Две пары, 10 и 5 (и туз) будут 0010 1000 1000 0011 0011 1100 (0x28833C)
Я предполагаю, что у вас есть логика, которая выяснит, какая у вас рука. В этом вы, вероятно, написали код, чтобы расположить карты в порядке слева направо. Таким образом, флеш-рояль будет организован как [A,K,Q,J,10]. Затем вы можете построить число, представляющее руку, используя следующую логику:
int handValue = HandType; (i.e. 0 for high card, 7 for Four of a kind, etc.)
for each card
handValue = (handValue << 4) + cardValue (i.e. 0 for 2, 9 for Jack, etc.)
Результатом будет уникальное значение для каждой руки, и вы уверены, что флеш всегда побьет стрит, а фулл-хаус с королем - 7 фулл-хаусов и т. Д.
Нормализуя руку
Вышеприведенный алгоритм зависит от нормализации покерной руки, в первую очередь с наиболее важными картами. Так, например, рука [K,A,10,J,Q]
(все в одном костюме) - флеш-рояль. Это нормализовано [A,K,Q,J,10]
, Если вам дали руку [10,Q,K,A,J]
, это также будет нормализовано к [A,K,Q,J,10]
, Рука [7,4,3,2,4]
это пара из 4-х. Будет нормализовано до [4,4,7,3,2]
,
Без нормализации очень трудно создать уникальное целочисленное значение для каждой руки и гарантировать, что пара из 4 всегда будет бить пару из 3.
К счастью, сортировка руки является частью выяснения, что это за рука. Вы можете сделать это без сортировки, но сортировка пяти элементов занимает тривиальное время и значительно облегчает многие вещи. Он не только облегчает определение стритов, но и группирует общие карты, что облегчает поиск пар, троек и четверок.
Для стритов, флешей и старших карт все, что вам нужно, это сортировать. Для других вы должны сделать второй проход заказа, который заказывает, группируя. Например, полный дом будет xxxyy
пара будет xxabc
, (с a
, b
, а также c
в порядке) и т. д. Эта работа в основном делается для вас в любом случае, в роде. Все, что вам нужно сделать, это переместить отставших до конца.
Наибольшее значение для карты будет 14, при условии, что вы позволяете не-лицевым картам сохранять свое значение (2..10), тогда J=11, QK, A=14.
Целью подсчета очков было бы различие между руками в сценарии разрыва связи. То есть "пара" против "пара". Если вы обнаружите другую конфигурацию руки ("две пары"), это объединит результаты в отдельные группы.
Вы должны внимательно ознакомиться с вашими требованиями. Я подозреваю, что по крайней мере для некоторых рук участвующие карты важнее, чем не участвующие карты. Например, пара 4 с 7-хай бьет пару 3 с хай-хай? (4,4,7,3,2 > 3,3,Q,6,5?) Ответ на этот вопрос должен определить порядок карт в руке.
Если у вас есть 5 карт и значения < 16, преобразуйте каждую карту в шестнадцатеричное число: 2..10,JQKA => 2..ABCDE. Положите карты в порядке, как указано выше. Например, 4,4,7,3,2, вероятно, станет 4,4,7,3,2. Сопоставьте эти значения с шестнадцатеричным, а затем с целочисленным значением: "0x44732" -> 0x44732.
Пусть ваши комбо-очки будут кратны 0x100000, чтобы убедиться, что никакая конфигурация карты не может выдвинуть руку в более высокий класс, а затем сложить их.
Как вы обнаружили, если вы сложите значения карт так, как вы предлагаете, то вы можете получить двусмысленности.
100000 + 4^1 + 6^2 + 11^1 + 13^1 = 100064
100000 + 3^1 + 4^1 + 7^2 + 8^1 = 100064
Тем не менее, дополнение не совсем правильный инструмент здесь. Вы уже используете ^
что означает, что ты на полпути. Вместо этого используйте умножение, и вы можете избежать двусмысленности. Рассматривать:
100000 + (4^1 * 6^2 * 11^1 * 13^1)
100000 + (3^1 * 4^1 * 7^2 * 8^1)
Это почти правильно, но все еще есть неясности (например, 2^4 = 4^2
). Итак, переназначьте новые (простые!) Значения для каждой карты:
Ace => 2
3 => 3
4 => 5
5 => 7
6 => 11
...
Затем вы можете умножить специальные простые значения каждой карты, чтобы получить уникальное значение для каждой возможной руки. Добавьте свое значение для типа руки (пара, фулл-хаус, флеш и т. Д.) И используйте его. Возможно, вам придется увеличить величину значений типа вашей руки, чтобы они не мешали составлять значения карт.