这是一道算法题:如何比较徳州扑克牌大小?(附带python实现)

2023-07-26 10:55:23 浏览数 (1)

问题描述

每个玩家有2张牌,公共牌有5张牌,共计7张牌。比牌时,每个玩家要找到自己组成最大牌型的5张,跟其他玩家的最大牌型比大小。

如果我们能给每5张牌算一个分数,分数越大,牌越大,问题就迎刃而解了。

定义手牌

德州扑克中,牌一共有52张,没有大小王。我们用0-51表示52张不同的扑克牌。其中0-51这52个数字表示牌的ID。

以下代码用python实现。

根据ID计算花色

花色有4种,刚好可用最后2位表示4种花色。

代码语言:javascript复制
def get_card_suit_id(card_id):
    return card_id & 3

根据ID计算数字

ID最后2位是花色,那么右移2位,剩下的信息就是牌的数字了。

代码语言:javascript复制
def get_card_num_id(card_id):
    return card_id >> 2

核心思路

核心问题,每个玩家要在7张牌中找到5张,用排列组合算法,遍历一遍所有组合,取最大的分数即可。

输入:7张牌的ID。 输出:该玩家最大牌型的分数。

分数 = (牌型, 牌型分)

此外,因为德州扑克的计算规则,是先看牌型,再看数字。所以,我们可以用二维表示分数:(牌型, 该牌型的分数)

比大小时,先比牌型,若牌型一样大,再比后面的分数。

由于皇家同花顺是同花顺的一种,所以牌型有9种,从小到大用0-8表示。

各牌型分

每个牌型比大小的方式不一样,牌型分的计算方式有差别。

  • 8 同花顺:比最大的牌即可。牌型分就是最大牌的数字大小
  • 7 四条:牌型分需要先比四条的数字,再比单牌的数字。所以牌型分是个二维的结构:(四条的数字, 单牌的数字)
  • 6 葫芦:牌型分是个二维的结构:(三条的数字, 一对的数字)
  • 5 同花:牌型分是个五维的结构,从大到小排列的5个牌的数字
  • 4 顺子:牌型分就是最大牌的数字大小
  • 3 三条:牌型分是个三维的结构:(三条的数字, 较大单牌的数字, 较小单牌的数字)
  • 2 两对:牌型分是个三维的结构:(较大对子的数字, 较小对子的数字, 单牌的数字)
  • 1 一对:牌型分是个三维的结构:(对子的数字, 最大单牌的数字, 第二大单牌的数字, 第三大单牌的数字)
  • 0 高牌:牌型分是个五维的结构,从大到小排列的5个牌的数字

排列组合

组合算法不用自己实现,各个编程语言都有库。

这就是根据7张牌,计算max_typemax_value的逻辑。其中get_type()get_value()是我们后面需要实现的。

代码语言:javascript复制
from itertools import combinations

def get_max_score(seven_cards):
    max_type = -1
    res = []
    for cards in combinations(seven_cards, 5):
        t, v = get_type(cards)
        if t > max_type:
            max_type = t
            res = [(t, v, cards)]
        elif t == max_type:
            res.append((t, v, cards))
    max_value = get_value(*res[0])
    for r in res[1:]:
        value = get_value(*r)
        if value > max_value:
            max_value = value
    return max_type, max_value

这里介绍一个小小的优化点:我们遍历时,并不会计算所有的5张牌组合的max_type和max_value,因为可以先把max_type算出来,得到最大的type后,只需要计算最大type的max_value,省了一些没有必要的计算。

当然由于遇到顺子时,可以很快得到max_value,所以会在get_type时就算出来。

get_type

代码语言:javascript复制
def get_type(five_cards):
    straight, straight_value = is_straight(five_cards)
    flush = is_flush(five_cards)
    if flush and straight:
        return 8, straight_value
    if flush:
        return 5, None  # sorted([get_card_num_id(c) for c in five_cards], reverse=True)
    if straight:
        return 4, straight_value
    nums = [0] * 13
    for card in five_cards:
        nums[get_card_num_id(card)]  = 1
    max_freq = max(nums)
    if max_freq == 1:
        return 0, None  # sorted([get_card_num_id(c) for c in five_cards], reverse=True)
    if max_freq == 4:
        return 7, None  # [nums.index(4), nums.index(1)]
    if max_freq == 3:
        if 2 in nums:
            return 6, None  # [nums.index(3), nums.index(2)]
        return 3, None
    # max_freq == 2
    if nums.count(2) == 1:
        return 1, None
    return 2, None

其中用到了这2个函数:

是否同花 is_flush

代码语言:javascript复制
def is_flush(five_cards):
    s = get_card_suit_id(five_cards[0])
    for c in five_cards[1:]:
        if s != get_card_suit_id(c):
            return False
    return True

是否顺子 is_straight

代码语言:javascript复制
def is_straight(five_cards):
    nums = sorted([get_card_num_id(c) for c in five_cards])
    if nums == [0, 1, 2, 3, 12]:
        return True, 3
    n4 = nums[4]
    for i, n in enumerate(nums[:4]):
        if n4 - n != 4 - i:
            return False, 0
    return True, n4

get_value

代码语言:javascript复制
def get_value(t, v, five_cards):
    if v is not None:
        return v
    if t in (0, 5):
        return sorted((get_card_num_id(c) for c in five_cards), reverse=True)
    nums = [0] * 13
    for card in five_cards:
        nums[get_card_num_id(card)]  = 1
    return sorted(list(set(get_card_num_id(c) for c in five_cards)), reverse=True, key=lambda x: (nums[x] << 5)   x)

0 人点赞