代码实现对麻将的听牌分析(判断什么牌能胡)


前言

emmmmm,好久没发文章了上次发还是在上次
hxdm动动你们发财的小手指点点赞鸭,快没动力了,呜呜呜~
前几天面试,要求做一个类似麻将的游戏的听牌分析。简单说就是找到自己还差什么牌能够胡牌。


一、游戏规则

一副牌一共有 big(A-H) + small(a-h) + joker(X) 三种类型的牌。
每人手上一共10张牌,X可以变成任意牌。
胡牌方式是:抽取一张牌,达到 n组刻子+ m组顺子 + 1组对子 ; n + m = 3

刻子(triple):
不论大小写的3张相同字母,比如 AAA, AAa, aaA
顺子(straight):
大小写必须相同的3张连续牌, 比如 ABC, abc
对子(pair):
两张大小写相同的牌

胡牌例子
AGHHXaddgh 听牌:AGag
BGGbcdeefg 听牌:Bb

二、分析流程

主要利用的是Python中Counter类;可以理解成升级版的字典,统计所有牌的数量

1、检查手牌是否符合规范:一共10张牌,普通牌每种类型不大于5,joker不大于3,且手牌的范围是A-H,a-z,X

不含X:
2、得到所有提取出一个pair的可能手牌 + 一个都不提的原始手牌

3、将所有的triple 和 straight 提出

两种方式,优先triple,以及优先straight,两种结果取交集

4、根据最后的到的牌的数量以及牌的类型判断差什么牌胡牌

剩一张:差一张组成pair胡牌
剩两张:
两张相同,两张字母相同大小写不同:差一张组成triple
两张相邻,两张间隔为1:差一张组成straight
其余:未听牌

含X的:

在1 2步骤之间加一个步骤:将X转化成为它可能的牌并返回所有结果


三、写代码

话不所说,直接上代码,有详细注释。

import re
import copy
from collections import Counter
# 使用numpy库进行列表扁平化
# import numpy as np


class Hooler:
    def __init__(self,
                 common_nums: int = 5,
                 joker_nums: int = 3) -> None:
        """
        初始化牌堆中的牌
        @param common_nums: 每种普通牌的数量,默认为5
        @param joker_nums: joker牌的数量,默认为3
        """
        self.common_nums = common_nums
        self.joker_nums = joker_nums
        self.__all_cards = Counter('abcdefghABCDEFGH'*self.common_nums + 'X'*self.joker_nums)

    def __check_cards(self, cards: str) -> Counter | bool:
        """
        私有方法
        检查手牌是否符合规则(一共10张,包含a-h,A-H,X)
        @param cards: str类型的手牌
        @return: 符合规则返回Counter类型手牌,否则None
        """

        if len(cards) != 10:
            return False
        target = r'[i-zI-WYZ]'
        res = re.findall(target, cards)
        if res:
            return False
        counter = Counter(cards)
        self.__all_cards -= counter  # 总牌堆去掉当前的手牌
        if sum(self.__all_cards.values()) != 73:
            return False
        return counter

    def __pair(self, counter: Counter) -> list[Counter]:
        """
        私有方法
        去掉手牌中的1个pair
        @param counter: Counter类型手牌
        @return: 所有去掉1个pair后的手牌 + 未去掉pair的手牌
        """

        temp = []
        res = [counter]
        for i in counter:
            if counter[i] > 1:  # 手牌中是否有数量大于1的种类
                temp.append(i)
        for i in temp:
            counter_copy = copy.deepcopy(counter)  # 深拷贝,保证每次只去掉1个
            count = {
    
    i: 2}
            counter_copy -= count  # 去掉手牌中一个pair
            res.append(counter_copy)
        return res

    def __pairs(self, counters: [Counter]) -> list[Counter]:
        """
        私有方法
        主要用于含有X的手牌,去掉手牌中的1个pair
        @param counters: 列表类型的Counter手牌
        @return: 所有去掉1个pair后的手牌 + 未去掉pair的手牌
        """
        res = []
        for i in counters:
            res.append(self.__pair(i))
        res = [i for arr in res for i in arr]  # 列表扁平化
        return res

    def __triple(self, counter: Counter) -> Counter:
        """
        私有方法
        递归的方式去掉手牌中所有triple
        @param counter: Counter类型手牌
        @return: 去掉所有triple后的手牌
        """

        keys = list(counter.keys())
        # 对键按照字母顺序不区分大小写的方式排序
        keys.sort(key=lambda x: ord(x) - 32 if ord(x) > 96 else ord(x))
        i = 0
        while i < len(keys):
            # 在边界内比较两张牌是否为一个大写一个小写
            if i < len(keys) - 1 and keys[i].upper() == keys[i + 1].upper():
                if counter[keys[i]] + counter[keys[i + 1]] >= 3:  # 两张牌的和是否大于等于3
                    if counter[keys[i]] == 1:
                        count = {
    
    
                            keys[i]: 1,
                            keys[i + 1]: 2,
                        }
                        counter -= count
                        return self.__triple(counter)
                    else:
                        count = {
    
    
                            keys[i]: 2,
                            keys[i + 1]: 1,
                        }
                        counter -= count
                        return self.__triple(counter)
                i += 1
            elif counter[keys[i]] >= 3:  # 单张牌数大于等于3
                count = {
    
    keys[i]: 3}
                counter -= count
                return self.__triple(counter)
            i += 1
        return counter

    def __straight(self, counter: Counter) -> list[Counter]:
        """
        私有方法
        递归的方式去掉手牌中所有straight
        @param counter: Counter类型手牌
        @return: 去掉所有straight后的手牌
        """

        def help(cards_list: list[list[str]]) -> list[list[str]]:
            """
            得到去掉的straight的所有结果
            @param cards_list: 手牌
            @return: 按不同方法去掉的straight的所有结果
            """
            res = []
            for cards in cards_list:
                remove_card = []
                cards_set = list(set(cards))
                cards_set.sort()
                for i in range(1, len(cards_set) - 1):
                    # 判断三个字符串是否是相邻的
                    # counter_copy = copy.deepcopy(counter)
                    if ord(cards_set[i]) + 1 == ord(cards_set[i + 1]) and\
                            ord(cards_set[i]) - 1 == ord(cards_set[i - 1]):
                        remove_card.append(cards_set[i - 1:i + 2])

                for i in remove_card:
                    cards_copy = copy.deepcopy(cards)
                    for j in i:
                        cards_copy.remove(j)
                    res.append(cards_copy)
            if not res:
                return cards_list
            return help(res)

        cards = []
        for i in counter.keys():
            for j in range(counter[i]):
                cards.append(i)
        cards.sort()
        cards_list = help([cards])
        res = []
        for i in cards_list:
            count = Counter(i)
            if i not in res:
                res.append(count)
        return res

        # for counter in counters:
        #     keys = list()
        #
        #     keys.sort()
        #     counters_copy = [counter for i in range(len(keys) - 2)]
        #     for i in range(1, len(keys) - 1):
        #         # 判断三个字符串是否是相邻的
        #         # counter_copy = copy.deepcopy(counter)
        #         if ord(keys[i]) + 1 == ord(keys[i + 1]) and ord(keys[i]) - 1 == ord(keys[i - 1]):
        #             count = {k: 1 for k in keys[i - 1:i + 2]}
        #             counters_copy[i - 1] -= count
        #
        # if len(new_counters) == len(counters):
        #     return new_counters
        # return self.__straight(new_counters)

    def __check_surplus_cards(self, cards: list[str]) -> list[str]:
        """
        检查牌堆中是否还有某种剩余的牌
        @param cards: 需要检查的牌
        @return: 牌堆中剩余的牌
        """

        if len(cards) == 0:
            return []
        res = []
        for i in cards:
            if self.__all_cards[i] != 0:
                res.append(i)
        return res

    def __hooler_card(self, counter: Counter) -> list[str]:
        """
        判断当前手牌差什么牌能够胡牌
        @param counter: Counter类型手牌
        @return: 胡的牌
        """
        # pair
        if sum(counter.values()) == 1:
            return self.__check_surplus_cards(list(counter))
        # 没有hooler牌
        if sum(counter.values()) > 2:
            return []
        # 剩余两张牌
        keys = list(counter.keys())
        # triple:两张一样的牌
        if len(keys) == 1:
            cards = [keys[0].upper(), keys[0]] if ord(keys[0]) >= 97 else [keys[0], keys[0].lower()]
            return self.__check_surplus_cards(cards)
        # triple:两张大小写不一样的牌
        if keys[0].upper() == keys[1].upper():
            return self.__check_surplus_cards(keys)
        # straight:差首位
        if ord(keys[1]) - ord(keys[0]) == 1:
            if keys[0] in {
    
    'a', 'A'}:
                return self.__check_surplus_cards([chr(ord(keys[1]) + 1)])
            if keys[1] in {
    
    'h', 'H'}:
                return self.__check_surplus_cards([chr(ord(keys[0]) - 1)])
            return self.__check_surplus_cards([chr(ord(keys[0]) - 1), chr(ord(keys[1]) + 1)])
        # straight:差中间
        if ord(keys[1]) - ord(keys[0]) == 2:
            return self.__check_surplus_cards([chr(ord(keys[0]) + 1)])

    def __hooler_x_card(self,
                        counters: list[Counter],
                        jokers_num: int) -> list[Counter]:
        """
        将万能牌进行替换
        @param counters: list[Counter]类型的手牌
        @return: 胡的牌
        """
        if jokers_num == 0:
            return counters
        new_counters = []
        for counter in counters:
            # self.__triple(counter)
            # self.__straight(counter)
            keys = 'abcdefghABCDEFGH'
            # keys = set(counter.keys())
            # for i in keys.copy():
            #     if i != 'X':
            #         keys.add(chr(ord(i) + 32) if ord(i) < 96 else chr(ord(i) - 32))
            #     if i not in {'a', 'A', 'X'}:
            #         x = chr(ord(i) - 1)
            #         keys.add(x)
            #         keys.add(chr(ord(x) + 32) if ord(x) < 96 else chr(ord(x) - 32))
            #     if i not in {'h', 'H', 'X'}:
            #         x = chr(ord(i) + 1)
            #         keys.add(x)
            #         keys.add(chr(ord(x) + 32) if ord(x) < 96 else chr(ord(x) - 32))

            for i in keys:
                if i != 'X':
                    counter_copy = copy.deepcopy(counter)
                    count = {
    
    
                        i: 1,
                        'X': -1,
                    }
                    counter_copy += count
                    new_counters.append(counter_copy)
        return self.__hooler_x_card(new_counters, jokers_num-1)

    def reset_all_crads(self) -> None:
        """
        重置牌堆
        """
        self.__all_cards = Counter('abcdefghABCDEFGH' * self.common_nums + 'X' * self.joker_nums)

    def get_hooler_cards(self, cards: str) -> list[str]:
        """
        得到当前手牌的胡牌
        @param cards: 当前手牌
        @return: 能胡的牌无牌胡返回空
        """
        self.reset_all_crads()  # 每次调用重置一下牌堆
        counter = self.__check_cards(cards)
        if not counter:
            return []

        if counter['X'] != 0:
            counters = self.__hooler_x_card([counter], counter['X'])
            counters = self.__pairs(counters)
        else:
            counters = self.__pair(counter)

        res = []
        for i in counters:
            i_copy = copy.deepcopy(i)
            self.__triple(i)
            straight = self.__straight(i)
            for j in straight:
                temp = self.__hooler_card(j)
                if temp:
                    res.append(temp)

            straight = self.__straight(i_copy)
            for s in straight:

                self.__triple(s)
                temp = self.__hooler_card(s)
                if temp:
                    res.append(temp)

        # numpy库进行列表扁平化
        # res = np.array(res).flatten()
        # res = list(res)

        res = [i for arr in res for i in arr]  # 列表扁平化
        res = list(set(res))
        res.sort()
        return res

    def getlist_hooler_cards(self, cards: list[str]) -> list[list[str]]:
        """
        得到多组手牌能胡的牌
        @param cards: 一组手牌
        @return: 能胡的牌
        """
        res = []
        for i in cards:
            temp = self.get_hooler_cards(i)
            res.append(temp)
        return res

猜你喜欢

转载自blog.csdn.net/youngwyj/article/details/126487869