【问题标题】:Algorithm to give a value to a 5 card Poker hand为 5 张牌扑克手赋值的算法
【发布时间】:2017-07-11 20:02:21
【问题描述】:

我正在开发一个作为大学项目的扑克游戏,我们当前的任务是编写一个算法来为一手 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)

因此,对于三个 Q 和两个 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

这导致平局,显然一对 7 胜过一对 6。

我该如何改进这个公式,甚至,我可以使用什么更好的公式?

顺便说一句,在我的代码中,手牌按升序存储在每张牌的值的数组中,例如:

[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;
    }

【问题讨论】:

  • 把加法改为乘法,把秩改为前13个素数,你就发明了“Cactus Kev”技术。最早的相当快速的扑克手评估器之一。
  • @LeeDanielCrocker 我一直在玩这个,但我仍然无法让它始终如一地工作 - 例如,一对 2 可以大大击败一对 3,前提是对 2 中的其他牌大于对 3 中的牌。 22QKA = 2^2 * 31 * 37 * 41 = 188108 和 23345 = 2 * 3^2 * 5 * 7 = 630
  • 我给了你“Cactus Kev”参考。如果你不能自己谷歌并阅读它,我帮不了你。
  • 这里有一个算法,它使用完美的哈希来为一手牌打分。同样的算法也可以为 7 张扑克和奥马哈扑克牌打分。 github.com/HenryRLee/PokerHandEvaluator/blob/master/…

标签: algorithm poker


【解决方案1】:

有 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)

两对,10s 和 5s(以及一张 A)将是 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.)

结果将是每手牌的唯一值,并且您确信同花总会击败顺子,而国王高的 Full House 会击败 7 高的 Full House,等等。

规范化手

上述算法取决于被标准化的手牌,首先是最重要的牌。例如,这手牌[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,(依次为abc)等等。无论如何,这项工作主要是为你完成的,由种类。您所要做的就是将落后者移动到最后。

【讨论】:

  • 我是这个解决方案的忠实拥护者——我已经实现了它并且它运行良好。唯一的问题是,在我的整个程序中,双手通过int 值进行比较,例如if (hand1.getValue() &gt; hand2.getValue())。有什么方法可以添加到此解决方案中,以便将手的唯一二进制字符串表示为唯一整数?我尝试简单地将整个 24 位二进制字符串转换为整数,但这不起作用。
  • 编辑:我认为将二进制字符串转换为整数不起作用的原因是二进制字符串没有填充到 24 位
  • 我相信这种方法仍然存在同样的问题,例如一对 2 可以返回比一对 3 更高的分数 - AKQ22 vs 54332
  • 在此解决方案中,包含22456 的手牌将编码为0001 0000 0000 0010 0011 01000x100234。手牌33456 将是0001 0001 0001 0010 0011 01000x111234。我想你忘记了你订购卡片的部分,所以这对总是在前面。这应该不难,并且取决于你如何计算手是什么,可能已经完成了。
  • 我相信这个答案和我的答案在实现上基本一致。 ordering 要求就是区别。如果“一对”的两只手的主要区别在于组成这对的牌,那么您需要将它们排序到组合整数的高位。
【解决方案2】:

正如您所发现的,如果您按照您建议的方式将卡片的值加在一起,那么您可能会产生歧义。

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
...

然后,您可以将每张牌的特殊质数相乘,从而为每一手可能的牌生成一个唯一值。添加你的手牌类型(对子、葫芦、同花等)的价值并使用它。您可能需要增加手牌类型值的大小,以免它们影响牌面值组合。

【讨论】:

  • 优秀的解决方案。素数的确切目的是什么?如果一手牌包含更高价值的牌,是否会实现价值的指数级增长?
  • @KOB:简而言之,就是要保证一张牌的计算值不会与附近的牌的计算值冲突。
  • 我有点迷失了这种方法,因为似乎仍然存在不一致 - 例如,一对 2s 可以大大击败一对 3s,前提是对子中的其他牌2s 牌大于对 3s 中的牌。 22QKA = 2^2 * 31 * 37 * 41 = 188108 和 23345 = 2 * 3^2 * 5 * 7 = 630
  • @KOB:确实如此。您可能需要使用此处其他一些答案的观察结果来进一步开发您的解决方案,即手牌类型中的 参与 牌比非参与牌更重要。不过,我认为你进展顺利。
  • @KOB 此外,您可以将产品映射到特定的手牌等级,尽管您还必须考虑这手牌是否同花。
【解决方案3】:

一张牌的最高值是 14,假设您让非面牌保持其值 (2..10),那么 J=11,QK,A=14。

计分的目的是区分决胜局中的牌局。也就是说,“对”与“对”。如果您检测到不同的手部配置(“两对”),则会将分数分成不同的组。

您应该仔细咨询您的要求。我怀疑至少对于某些手来说,参与的牌比不参与的牌更重要。例如,一对带 7 高的 4's 能否击败一对带 Q 高的 3's? (是 4,4,7,3,2 > 3,3,Q,6,5?)这个问题的答案应该确定手中牌的顺序。

假设您有 5 张卡片,并且值 2..ABCDE。按照上面确定的顺序排列卡片。例如,4,4,7,3,2 可能会变成 4,4,7,3,2。将这些值映射到十六进制,然后映射到整数值:“0x44732”-> 0x44732。

让你的combo分数是0x100000的倍数,以确保没有任何卡配置可以将手提升到更高的级别,然后将它们相加。

【讨论】:

  • 这是一个非常实用的解决方案,但是,根据手牌类型重新计算牌将是另一项非常重要的任务
  • 原帖中提到有这些信息:ROYAL_FLUSH = 900000 等。我认为这要么是夹具的一部分,要么是作业的一部分。
  • 将皇家同花顺的默认分数设为 900,000 是我自己的想法,没有必要。最后,我按照@JimMischel 的建议做了,默认手牌得分为 0-9。我知道您的解决方案与吉姆的几乎相同,但我接受了他作为答案,因为他非常详细。另外,我很遗憾没有采纳你关于重新排序卡片的建议——我认为这会比以前困难得多。我已经根据牌的类型对牌进行了重新排序,一切都完美无缺。
猜你喜欢
  • 2011-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
  • 2016-02-24
  • 1970-01-01
  • 1970-01-01
  • 2011-10-22
相关资源
最近更新 更多