问题描述
每个玩家有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_type
和max_value
的逻辑。其中get_type()
和get_value()
是我们后面需要实现的。
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)