Python中如何实现暴力枚举的斗地主规则检查与牌型比较器?

在玩游戏时,想着实现一个基本的网络版本,不考虑人机对战的 AI 功能,不考虑高并发的性能因素,不考虑 UI 精美的交互接口。

当前实现了一个规则检查及比较器 https://github.com/onestraw/doudizhu

  1. 有没有简单易用的游戏服务框架?推荐些入门资料。
  2. 对于扑克类游戏,有什么经典的规则检查算法?斗地主所有出牌方式只有 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

牌型算法有没有研究过

回到顶部