Python中如何实现暴力枚举的斗地主规则检查与牌型比较器?
在玩游戏时,想着实现一个基本的网络版本,不考虑人机对战的 AI 功能,不考虑高并发的性能因素,不考虑 UI 精美的交互接口。
当前实现了一个规则检查及比较器 https://github.com/onestraw/doudizhu
- 有没有简单易用的游戏服务框架?推荐些入门资料。
- 对于扑克类游戏,有什么经典的规则检查算法?斗地主所有出牌方式只有 3w+(不考虑花色),所以枚举是个可行的办法,但是对于更为复杂的麻将,一般怎么做呢?求经验
Python中如何实现暴力枚举的斗地主规则检查与牌型比较器?
7 回复
战略性 mark 
import itertools
from collections import Counter
from enum import Enum
class CardType(Enum):
SINGLE = 1 # 单张
PAIR = 2 # 对子
TRIPLE = 3 # 三张
TRIPLE_WITH_ONE = 4 # 三带一
TRIPLE_WITH_TWO = 5 # 三带二
STRAIGHT = 6 # 顺子
PAIR_STRAIGHT = 7 # 连对
AIRPLANE = 8 # 飞机
BOMB = 9 # 炸弹
ROCKET = 10 # 火箭
class CardComparator:
CARD_RANK = '3-4-5-6-7-8-9-10-J-Q-K-A-2-BJ-RJ'.split('-')
def __init__(self):
self.rank_map = {card: i for i, card in enumerate(self.CARD_RANK)}
def parse_cards(self, cards_str):
"""将字符串牌面转换为排序后的数字列表"""
cards = cards_str.upper().split()
return sorted([self.rank_map[card] for card in cards])
def get_card_type(self, cards):
"""暴力枚举判断牌型"""
cards = sorted(cards)
length = len(cards)
counter = Counter(cards)
# 火箭
if cards == [self.rank_map['BJ'], self.rank_map['RJ']]:
return CardType.ROCKET, cards
# 炸弹
if length == 4 and len(set(cards)) == 1:
return CardType.BOMB, cards
# 单张
if length == 1:
return CardType.SINGLE, cards
# 对子
if length == 2 and cards[0] == cards[1]:
return CardType.PAIR, cards
# 三张
if length == 3 and len(set(cards)) == 1:
return CardType.TRIPLE, cards
# 三带一
if length == 4:
values = list(counter.values())
if sorted(values) == [1, 3]:
triple = [k for k, v in counter.items() if v == 3][0]
single = [k for k, v in counter.items() if v == 1][0]
return CardType.TRIPLE_WITH_ONE, [triple]*3 + [single]
# 三带二
if length == 5:
values = list(counter.values())
if sorted(values) == [2, 3]:
triple = [k for k, v in counter.items() if v == 3][0]
pair = [k for k, v in counter.items() if v == 2][0]
return CardType.TRIPLE_WITH_TWO, [triple]*3 + [pair]*2
# 顺子 (5张或以上连续单张)
if length >= 5:
unique_cards = sorted(set(cards))
if len(unique_cards) == length: # 无重复
# 检查是否连续(去掉2和大小王)
filtered = [c for c in unique_cards if c < self.rank_map['2']]
if len(filtered) == length:
if all(filtered[i] + 1 == filtered[i+1] for i in range(length-1)):
return CardType.STRAIGHT, cards
# 连对 (3对或以上连续对子)
if length >= 6 and length % 2 == 0:
pairs = [card for card, count in counter.items() if count == 2]
if len(pairs) * 2 == length:
sorted_pairs = sorted(pairs)
if all(sorted_pairs[i] + 1 == sorted_pairs[i+1] for i in range(len(pairs)-1)):
# 去掉2
if max(sorted_pairs) < self.rank_map['2']:
return CardType.PAIR_STRAIGHT, cards
# 飞机 (2个或以上连续三张)
if length >= 6 and length % 3 == 0:
triples = [card for card, count in counter.items() if count == 3]
if len(triples) * 3 == length:
sorted_triples = sorted(triples)
if all(sorted_triples[i] + 1 == sorted_triples[i+1] for i in range(len(triples)-1)):
if max(sorted_triples) < self.rank_map['2']:
return CardType.AIRPLANE, cards
return None, cards # 无效牌型
def compare(self, cards1_str, cards2_str):
"""比较两手牌的大小"""
cards1 = self.parse_cards(cards1_str)
cards2 = self.parse_cards(cards2_str)
type1, sorted1 = self.get_card_type(cards1)
type2, sorted2 = self.get_card_type(cards2)
if not type1 or not type2:
return "无效牌型"
# 火箭最大
if type1 == CardType.ROCKET:
return "第一手牌大"
if type2 == CardType.ROCKET:
return "第二手牌大"
# 炸弹比其他普通牌型大
if type1 == CardType.BOMB and type2 != CardType.BOMB:
return "第一手牌大"
if type2 == CardType.BOMB and type1 != CardType.BOMB:
return "第二手牌大"
# 同类型比较
if type1 == type2 and len(cards1) == len(cards2):
# 比较最大牌(已排序)
if type1 in [CardType.STRAIGHT, CardType.PAIR_STRAIGHT, CardType.AIRPLANE]:
# 比较顺子/连对/飞机的最大牌
max1 = max(cards1)
max2 = max(cards2)
if max1 > max2:
return "第一手牌大"
elif max1 < max2:
return "第二手牌大"
else:
# 其他牌型比较最大的单张
# 对于三带一/三带二,比较三张的部分
if type1 in [CardType.TRIPLE_WITH_ONE, CardType.TRIPLE_WITH_TWO]:
triple1 = max([k for k, v in Counter(cards1).items() if v >= 3])
triple2 = max([k for k, v in Counter(cards2).items() if v >= 3])
if triple1 > triple2:
return "第一手牌大"
elif triple1 < triple2:
return "第二手牌大"
else:
# 单张、对子、三张、炸弹直接比较最大牌
if cards1[-1] > cards2[-1]:
return "第一手牌大"
elif cards1[-1] < cards2[-1]:
return "第二手牌大"
return "不能比较(牌型不同或长度不同)"
# 使用示例
if __name__ == "__main__":
comparator = CardComparator()
# 测试用例
test_cases = [
("3 4 5 6 7", "8 9 10 J Q"), # 顺子
("K K", "A A"), # 对子
("3 3 3", "4 4 4"), # 三张
("3 3 3 4", "5 5 5 6"), # 三带一
("BJ RJ", "2 2 2 2"), # 火箭 vs 炸弹
]
for cards1, cards2 in test_cases:
result = comparator.compare(cards1, cards2)
print(f"{cards1} vs {cards2} => {result}")
核心思路就是暴力枚举所有可能的牌型组合,通过计数和规则匹配来判断牌型,然后按斗地主规则比较大小。牌型判断主要用Counter统计牌面频率,顺子类牌型额外检查连续性。比较时先处理火箭和炸弹的特殊规则,再处理同类型比较。
简单说就是暴力匹配所有牌型规则然后按规则比大小。
js 的话 pomelo 不错
准备研究,还没来及看,要不你先看看,也想搞个麻将家里人完,麻将是把 1-9,11-19,21-29,31-39 分别表示筒子,条子,万子等
https://m.gitee.com/9miao/firefly
牌型算法有没有研究过
ding

