Python手搓四人麻将游戏思路及示例代码详解

摆烂仙君 2025-05-28 10:24:10编程技术
418

随着电子游戏的不断发展,传统的棋牌游戏也逐渐被搬上了数字平台。麻将作为一种深受人们喜爱的策略性桌游,其复杂的规则和多样的玩法为程序设计带来了挑战。本文以Python语言为基础,探讨如何从零开始构建一个四人麻将游戏。通过详细分析麻将牌的表示、游戏主循环的设计以及胡牌判定逻辑,我们将逐步揭开实现这一经典游戏的核心思路。无论你是对麻将游戏开发感兴趣的开发者,还是希望深入理解Python编程的应用场景的学习者,本文都将为你提供有价值的参考。

一、麻将牌的表示

在麻将游戏中,总共有一百四十四张牌,这些牌被分为多个类别,每个类别又包含了不同的牌型。具体来说,麻将牌主要包括序数牌、字牌和花牌三大类。序数牌中,包含有万子、条子和筒子,每种花色的序数牌都从一到九排列,每个数字有四张相同的牌。例如,一万、二万一直到九万,一共三十六张万子牌;同样的一条到九条,组成三十六张条子牌;还有一筒到九筒,构成另外三十六张筒子牌。

Python手搓四人麻将游戏思路及示例代码详解

字牌则有风牌和箭牌之分。风牌包括东、南、西、北四种风向牌,各有四张,总共十六张。箭牌则包含中、发、白搭三种,同样每种四张,共计十二张。与序数牌不同的是,字牌没有大小顺序之分,它们在游戏中的作用主要是增加游戏的复杂性和多样的胡牌方式。比如,在一些胡牌条件中,要求玩家的手牌中包含特定的字牌组合。花牌虽然在大多数麻将规则中不是必需的,但它们的存在为游戏增添了一定的趣味性。花牌包括春、夏、秋、冬和梅、兰、竹、菊这八种,各一张,总共有八张。这些牌通常在游戏中具有特殊的作用,比如可以替代其他牌来形成特殊牌型,或者在某些计分规则中起到加分的效果。

from enum import Enum

class TileType(Enum):
    """麻将牌的类型枚举"""
    WIND = 1     # 风牌
    DRAGON = 2   # 箭牌
    BAMBOO = 3   # 筒子
    CHARACTER = 4  # 万子
    DOT = 5      # 条子

class TileValue(Enum):
    """麻将牌的值枚举(用于风牌和箭牌)"""
    EAST = 1     # 东
    SOUTH = 2    # 南
    WEST = 3     # 西
    NORTH = 4    # 北
    RED = 5      # 中
    GREEN = 6    # 发
    WHITE = 7    # 白搭

class Tile:
    """表示一张麻将牌"""
    def __init__(self, tile_type, value):
        self.tile_type = tile_type  # 牌的类型
        self.value = value          # 牌的值

    def __repr__(self):
        """返回牌的字符串表示,便于调试和显示"""
        type_name = {
            TileType.WIND: "风",
            TileType.DRAGON: "箭",
            TileType.BAMBOO: "筒",
            TileType.CHARACTER: "万",
            TileType.DOT: "条"
        }.get(self.tile_type, "未知")

        value_name = ""
        if self.tile_type == TileType.WIND:
            value_name = {
                TileValue.EAST: "东",
                TileValue.SOUTH: "南",
                TileValue.WEST: "西",
                TileValue.NORTH: "北"
            }.get(self.value, "未知")
        elif self.tile_type == TileType.DRAGON:
            value_name = {
                TileValue.RED: "中",
                TileValue.GREEN: "发",
                TileValue.WHITE: "白"
            }.get(self.value, "未知")
        else:
            value_name = str(self.value)

        return f"{type_name}{value_name}"

class TileDeck:
    """表示麻将牌堆"""
    def __init__(self):
        self.tiles = []
        self.initialize_deck()

    def initialize_deck(self):
        """初始化麻将牌堆,包含所有144张牌"""
        # 风牌(东、南、西、北)
        for wind in [TileValue.EAST, TileValue.SOUTH, TileValue.WEST, TileValue.NORTH]:
            for _ in range(4):  # 每种风牌有4张
                self.tiles.append(Tile(TileType.WIND, wind))

        # 箭牌(中、发、白)
        for dragon in [TileValue.RED, TileValue.GREEN, TileValue.WHITE]:
            for _ in range(4):  # 每种箭牌有4张
                self.tiles.append(Tile(TileType.DRAGON, dragon))

        # 序数牌:万子、条子、筒子,每种从1到9,每种有4张
        for tile_type in [TileType.CHARACTER, TileType.DOT, TileType.BAMBOO]:
            for value in range(1, 10):  # 1到9
                for _ in range(4):     # 每张牌有4张
                    self.tiles.append(Tile(tile_type, value))

# 示例用法
if __name__ == "__main__":
    deck = TileDeck()
    print(f"麻将牌堆中共有 {len(deck.tiles)} 张牌")
    print("前10张牌为:")
    for tile in deck.tiles[:10]:
        print(tile)

为了在程序中准确地表示这些牌,我们需要为每张牌定义其属性,包括牌的类型、花色、值或符号等。例如,对于序数牌,我们可以用一个结构来存储其花色(万子、条子、筒子)和数值(1到9);对于字牌,我们则可以使用特定的标识符来表示东、南、西、北风以及中、发、白搭等;而花牌则可以用对应的季节或花卉名称来标识。在程序内部,这些属性可以通过枚举类型、类属性或简单的整数编码来实现,以便于游戏逻辑的处理和牌的比较、匹配等操作。

为了便于程序的实现和扩展,我们可能还需要为这些牌设计一些辅助的函数或方法,比如比较两张牌是否相同、判断一张牌是否属于某个特定类别、根据牌的属性生成对应的显示符号等。这些功能将帮助我们在游戏的不同阶段,如发牌、摸牌、打牌、胡牌判定等,快速准确地处理各种与牌相关的操作。通过对麻将牌的详细表示,我们能够确保程序能够正确地模拟麻将游戏中的各种情况,从而为玩家提供一个既真实又有趣的麻将游戏体验。这种对基础元素的深入理解和精确建模,是构建一个完整且功能丰富的麻将程序的关键所在。

二、游戏主循环

在麻将游戏中游戏主循环是整个程序的核心部分它控制着游戏的节奏和进程。游戏主循环的主要任务是不断地循环处理每个玩家的回合直至游戏结束的条件被满足。这个循环需要精确地管理牌的流动、玩家的操作、游戏状态的更新以及胜利条件的检测。

Python手搓四人麻将游戏思路及示例代码详解

首先游戏开始时需要初始化所有玩家的状态以及牌堆。每个玩家都有自己的手牌区域和得分情况。接着程序进入主循环在每个循环迭代中依次处理当前玩家的回合。当前玩家从牌堆中摸一张牌加入到自己的手牌中。这时候需要检查该玩家是否因为这张牌而胡牌如果满足胡牌条件游戏就在此时结束并结算分数。

import random
from enum import Enum

class TileType(Enum):
    """麻将牌的类型枚举"""
    WIND = 1     # 风牌
    DRAGON = 2   # 箭牌
    BAMBOO = 3   # 筒子
    CHARACTER = 4  # 万子
    DOT = 5      # 条子

class TileValue(Enum):
    """麻将牌的值枚举(用于风牌和箭牌)"""
    EAST = 1     # 东
    SOUTH = 2    # 南
    WEST = 3     # 西
    NORTH = 4    # 北
    RED = 5      # 中
    GREEN = 6    # 发
    WHITE = 7    # 白搭

class Tile:
    """表示一张麻将牌"""
    def __init__(self, tile_type, value):
        self.tile_type = tile_type  # 牌的类型
        self.value = value          # 牌的值

    def __repr__(self):
        """返回牌的字符串表示,便于调试和显示"""
        type_name = {
            TileType.WIND: "风",
            TileType.DRAGON: "箭",
            TileType.BAMBOO: "筒",
            TileType.CHARACTER: "万",
            TileType.DOT: "条"
        }.get(self.tile_type, "未知")

        value_name = ""
        if self.tile_type == TileType.WIND:
            value_name = {
                TileValue.EAST: "东",
                TileValue.SOUTH: "南",
                TileValue.WEST: "西",
                TileValue.NORTH: "北"
            }.get(self.value, "未知")
        elif self.tile_type == TileType.DRAGON:
            value_name = {
                TileValue.RED: "中",
                TileValue.GREEN: "发",
                TileValue.WHITE: "白"
            }.get(self.value, "未知")
        else:
            value_name = str(self.value)

        return f"{type_name}{value_name}"

class TileDeck:
    """表示麻将牌堆"""
    def __init__(self):
        self.tiles = []
        self.discard_pile = []  # 废牌堆
        self.initialize_deck()
        self.shuffle()

    def initialize_deck(self):
        """初始化麻将牌堆,包含所有144张牌"""
        # 风牌(东、南、西、北)
        for wind in [TileValue.EAST, TileValue.SOUTH, TileValue.WEST, TileValue.NORTH]:
            for _ in range(4):  # 每种风牌有4张
                self.tiles.append(Tile(TileType.WIND, wind))

        # 箭牌(中、发、白)
        for dragon in [TileValue.RED, TileValue.GREEN, TileValue.WHITE]:
            for _ in range(4):  # 每种箭牌有4张
                self.tiles.append(Tile(TileType.DRAGON, dragon))

        # 序数牌:万子、条子、筒子,每种从1到9,每种有4张
        for tile_type in [TileType.CHARACTER, TileType.DOT, TileType.BAMBOO]:
            for value in range(1, 10):  # 1到9
                for _ in range(4):     # 每张牌有4张
                    self.tiles.append(Tile(tile_type, value))

    def shuffle(self):
        """洗牌"""
        random.shuffle(self.tiles)

    def draw_tile(self):
        """摸牌"""
        if len(self.tiles) == 0:
            return None
        return self.tiles.pop()

    def discard_tile(self, tile):
        """打牌到废牌堆"""
        self.discard_pile.append(tile)

class Player:
    """表示一个玩家"""
    def __init__(self, name):
        self.name = name
        self.hand = []  # 手牌
        self.score = 10000  # 初始分数

    def draw(self, deck):
        """摸牌"""
        tile = deck.draw_tile()
        if tile:
            self.hand.append(tile)
        return tile

    def discard(self, tile):
        """打牌"""
        if tile in self.hand:
            self.hand.remove(tile)
            return tile
        return None


class MahjongGame:
    """麻将游戏主循环"""
    def __init__(self):
        self.deck = TileDeck()
        self.players = [Player(f"玩家 {i + 1}") for i in range(4)]
        self.current_player_index = 0  # 当前玩家索引
        self.game_over_flag = False  # 游戏结束标志

    def deal_tiles(self):
        """发牌"""
        # 每个玩家摸13张牌
        for _ in range(13):
            for player in self.players:
                player.draw(self.deck)

    def check_win(self, player):
        """检查玩家是否胡牌(简化版,仅检查手牌数量)"""
        # 这里只是一个简单的示例,实际胡牌判断逻辑更复杂
        return len(player.hand) == 14  # 假设摸到第14张牌就胡牌

    def play_turn(self, player):
        """玩家回合"""
        print(f"{player.name} 的回合")
        print(f"手牌: {player.hand}")

        # 摸牌
        drawn_tile = player.draw(self.deck)
        print(f"摸到: {drawn_tile}")
        print(f"当前手牌: {player.hand}")

        # 检查是否胡牌
        if self.check_win(player):
            print(f"{player.name} 胡牌!")
            self.game_over_flag = True
            return

        # 打牌(这里简化为随机打出一张牌)
        discarded_tile = random.choice(player.hand)
        player.discard(discarded_tile)
        self.deck.discard_tile(discarded_tile)
        print(f"打出: {discarded_tile}")

    def switch_player(self):
        """切换到下一个玩家"""
        self.current_player_index = (self.current_player_index + 1) % 4

    def game_over(self):
        """检查游戏是否结束"""
        return self.game_over_flag or len(self.deck.tiles) == 0

    def start(self):
        """开始游戏"""
        print("麻将游戏开始!")
        self.deal_tiles()  # 发牌

        while not self.game_over():
            current_player = self.players[self.current_player_index]
            self.play_turn(current_player)
            if not self.game_over():
                self.switch_player()

        print("游戏结束!")

# 运行游戏
if __name__ == "__main__":
    game = MahjongGame()
    game.start()

如果没有胡牌玩家则需要选择一张牌打出。打出的牌可能会引发其他玩家的一系列操作比如其他玩家可以选择碰牌、杠牌或者胡牌。这时候程序需要暂停当前玩家的回合等待其他玩家的响应。如果玩家选择进行这些操作相应的牌会被移动到新的位置并且可能再次检查是否有人因此胡牌。在处理完这些可能的操作后游戏会继续进行下一位玩家的回合。这个过程一直持续直到牌堆中的牌全部被摸完或者有玩家成功胡牌。如果牌摸完了而没有人胡牌则游戏以平局结束所有玩家的得分根据剩余的牌和游戏规则进行调整。

Python手搓四人麻将游戏思路及示例代码详解

游戏主循环不仅要处理正常的出牌和胡牌还需要处理一些特殊的游戏事件比如玩家的杠牌操作或者特殊牌型的检测。此外游戏主循环还需要管理玩家的分数及时更新并且在游戏结束时显示最终的结果。在实现游戏主循环时需要特别注意游戏逻辑的正确性和流畅性。每个玩家的操作都需要被准确地记录和响应游戏状态的变化要被及时地反映到用户界面上。同时游戏主循环还要处理可能出现的各种异常情况比如网络延迟(如果是在线游戏)或者玩家的不合法操作确保游戏能够顺利进行。

三、胡牌判定

在麻将程序中,胡牌判定是游戏逻辑的核心部分之一,它的复杂性和多样性直接关系到游戏的真实性和可玩性。胡牌判定不仅仅是一个简单的条件判断,而是一个需要综合考虑多种牌型组合和规则细节的复杂过程。胡牌的基本条件通常是玩家手中的牌形成特定的有效组合。这些组合在不同的麻将变体中可能有所不同,但大多数情况下包括“和牌”的基本结构,比如四个顺子或刻子加一个将牌(一对)。除此之外,还有一些特殊的胡牌形式,如“七对”“清一色”“大三元”等,每一种都有其独特的判定逻辑。

import random
from enum import Enum

class TileType(Enum):
    """麻将牌的类型枚举"""
    WIND = 1     # 风牌
    DRAGON = 2   # 箭牌
    BAMBOO = 3   # 筒子
    CHARACTER = 4  # 万子
    DOT = 5      # 条子

class TileValue(Enum):
    """麻将牌的值枚举(用于风牌和箭牌)"""
    EAST = 1     # 东
    SOUTH = 2    # 南
    WEST = 3     # 西
    NORTH = 4    # 北
    RED = 5      # 中
    GREEN = 6    # 发
    WHITE = 7    # 白搭

class Tile:
    """表示一张麻将牌"""
    def __init__(self, tile_type, value):
        self.tile_type = tile_type  # 牌的类型
        self.value = value          # 牌的值

    def __repr__(self):
        """返回牌的字符串表示,便于调试和显示"""
        type_name = {
            TileType.WIND: "风",
            TileType.DRAGON: "箭",
            TileType.BAMBOO: "筒",
            TileType.CHARACTER: "万",
            TileType.DOT: "条"
        }.get(self.tile_type, "未知")

        value_name = ""
        if self.tile_type == TileType.WIND:
            value_name = {
                TileValue.EAST: "东",
                TileValue.SOUTH: "南",
                TileValue.WEST: "西",
                TileValue.NORTH: "北"
            }.get(self.value, "未知")
        elif self.tile_type == TileType.DRAGON:
            value_name = {
                TileValue.RED: "中",
                TileValue.GREEN: "发",
                TileValue.WHITE: "白"
            }.get(self.value, "未知")
        else:
            value_name = str(self.value)

        return f"{type_name}{value_name}"

class TileDeck:
    """表示麻将牌堆"""
    def __init__(self):
        self.tiles = []
        self.discard_pile = []  # 废牌堆
        self.initialize_deck()
        self.shuffle()

    def initialize_deck(self):
        """初始化麻将牌堆,包含所有144张牌"""
        # 风牌(东、南、西、北)
        for wind in [TileValue.EAST, TileValue.SOUTH, TileValue.WEST, TileValue.NORTH]:
            for _ in range(4):  # 每种风牌有4张
                self.tiles.append(Tile(TileType.WIND, wind))

        # 箭牌(中、发、白)
        for dragon in [TileValue.RED, TileValue.GREEN, TileValue.WHITE]:
            for _ in range(4):  # 每种箭牌有4张
                self.tiles.append(Tile(TileType.DRAGON, dragon))

        # 序数牌:万子、条子、筒子,每种从1到9,每种有4张
        for tile_type in [TileType.CHARACTER, TileType.DOT, TileType.BAMBOO]:
            for value in range(1, 10):  # 1到9
                for _ in range(4):     # 每张牌有4张
                    self.tiles.append(Tile(tile_type, value))

    def shuffle(self):
        """洗牌"""
        random.shuffle(self.tiles)

    def draw_tile(self):
        """摸牌"""
        if len(self.tiles) == 0:
            return None
        return self.tiles.pop()

    def discard_tile(self, tile):
        """打牌到废牌堆"""
        self.discard_pile.append(tile)

class Player:
    """表示一个玩家"""
    def __init__(self, name):
        self.name = name
        self.hand = []  # 手牌
        self.score = 10000  # 初始分数

    def draw(self, deck):
        """摸牌"""
        tile = deck.draw_tile()
        if tile:
            self.hand.append(tile)
        return tile

    def discard(self, tile):
        """打牌"""
        if tile in self.hand:
            self.hand.remove(tile)
            return tile
        return None

class MahjongGame:
    """麻将游戏主循环"""
    def __init__(self):
        self.deck = TileDeck()
        self.players = [Player(f"玩家 {i + 1}") for i in range(4)]
        self.current_player_index = 0  # 当前玩家索引
        self.game_over_flag = False  # 游戏结束标志

    def deal_tiles(self):
        """发牌"""
        # 每个玩家摸13张牌
        for _ in range(13):
            for player in self.players:
                player.draw(self.deck)

    def check_win(self, player):
        """检查玩家是否胡牌(简化版)"""
        # 这里只是一个简单的示例,实际胡牌判断逻辑更复杂
        # 简化版:检查手牌是否为14张(正常胡牌为14张)
        return len(player.hand) == 14 

    def check_exact_win(self, player):
        """精确胡牌判定(示例:检查是否为七对)"""
        # 统计手牌中每张牌的出现次数
        tile_count = {}
        for tile in player.hand:
            tile_str = str(tile)
            if tile_str in tile_count:
                tile_count[tile_str] += 1
            else:
                tile_count[tile_str] = 1

        # 检查是否所有牌都是对子
        for count in tile_count.values():
            if count != 2:
                return False
        return True

    def play_turn(self, player):
        """玩家回合"""
        print(f"{player.name} 的回合")
        print(f"手牌: {player.hand}")

        # 摸牌
        drawn_tile = player.draw(self.deck)
        print(f"摸到: {drawn_tile}")
        print(f"当前手牌: {player.hand}")

        # 检查是否胡牌
        if self.check_win(player):
            print(f"{player.name} 胡牌!")
            self.game_over_flag = True
            return

        # 打牌(这里简化为随机打出一张牌)
        if self.check_exact_win(player):
            print(f"{player.name} 七对胡牌!")
            self.game_over_flag = True
            return

        discarded_tile = random.choice(player.hand)
        player.discard(discarded_tile)
        self.deck.discard_tile(discarded_tile)
        print(f"打出: {discarded_tile}")

    def switch_player(self):
        """切换到下一个玩家"""
        self.current_player_index = (self.current_player_index + 1) % 4

    def game_over(self):
        """检查游戏是否结束"""
        return self.game_over_flag or len(self.deck.tiles) == 0

    def start(self):
        """开始游戏"""
        print("麻将游戏开始!")
        self.deal_tiles()  # 发牌

        while not self.game_over():
            current_player = self.players[self.current_player_index]
            self.play_turn(current_player)
            if not self.game_over():
                self.switch_player()

        print("游戏结束!")

if __name__ == "__main__":
    game = MahjongGame()
    game.start()

要实现胡牌判定功能,首先需要对玩家的手牌进行分析。手牌通常是一个包含多张牌的列表,程序需要检查这个列表是否满足胡牌的条件。这涉及到对牌的排序、分组和模式匹配。比如,程序需要能够识别出牌中的顺子(三种连续的序数牌)和刻子(三张相同的牌),并确保这些组合的数量和结构符合胡牌的要求。在实现过程中,递归和回溯算法常常被用来穷举可能的牌型组合。这是因为麻将牌的组合方式非常多,直接遍历所有可能性可能会导致效率低下。递归算法可以有效地分解问题,将手牌分成不同的部分进行检查,并在发现不符合条件时及时回溯,尝试其他组合方式。

此外,胡牌判定还需要考虑一些特殊情况,比如是否有剩余的牌未被使用,或者是否满足某些特殊的加分条件。例如,“碰碰胡”要求所有的组合都是刻子,这就需要程序在判定时特别检查是否存在顺子。为了提高判定的效率和准确性,通常会将手牌进行预处理,比如排序和分类。这样可以减少不必要的计算,加快判定过程。同时,为了处理不同的麻将规则变体,胡牌判定逻辑需要具有一定的灵活性和可配置性。可以通过参数化的方式,让程序能够适应不同的规则要求。

总结

在本文中,我们全面剖析了使用Python手搓四人麻将游戏的关键步骤与技术细节。从麻将牌的定义与初始化,到游戏主循环的逻辑控制,再到胡牌判定的具体实现,每个环节都体现了程序设计的严谨性和灵活性。通过示例代码的展示与解释,我们不仅展示了如何用Python模拟麻将游戏的基本流程,还强调了在实际开发中需要考虑的各种特殊情况和优化方法。

麻将游戏的开发不仅仅是对规则的简单复现,更是一次对算法设计、数据结构应用以及用户体验优化的综合实践。希望通过本文的介绍,读者能够掌握麻将游戏开发的核心思路,并在此基础上进一步扩展功能,例如支持不同的麻将规则变体、增加图形化用户界面或实现在线多人对战模式。这将为传统游戏注入新的活力,同时也为开发者提供了一个展示创造力和技术能力的舞台。

Python
THE END
蜜芽
故事不长,也不难讲,四字概括,毫无意义。

相关推荐

Python yield 用法大全:轻松掌握生成器与迭代器设计
在Python中,yield关键字是构建生成器的核心工具,它通过状态保存机制实现了高效的内存管理和惰性计算。与传统的迭代器实现相比,yield能将迭代器设计从复杂的类定义简化为直...
2025-09-15 编程技术
550

基于Python的旅游数据分析可视化系统【2026最新】
本研究成功开发了基于Python+Django+Vue+MySQL的旅游数据分析可视化系统,实现了从数据采集到可视化展示的全流程管理。系统采用前后端分离架构,前端通过Vue框架构建响应式界...
2025-09-13 编程技术
576

手把手教你用Python读取txt文件:从基础到实战的完整教程
Python作为数据处理的利器,文件读写是其基础核心功能。掌握txt文件读取不仅能处理日志、配置文件等常见场景,更是理解Python文件I/O的基石。本文ZHANID工具网将从基础语法到...
2025-09-12 编程技术
550

Python Flask 入门指南:从零开始搭建你的第一个 Web 应用
Flask作为 Python 中最轻量级且灵活的 Web 框架之一,特别适合初学者快速上手 Web 应用开发。本文将带你一步步了解如何在本地环境中安装 Flask、创建一个简单的 Web 应用,并...
2025-09-11 编程技术
535

Python 如何调用 MediaPipe?详细安装与使用指南
MediaPipe 是 Google 开发的跨平台机器学习框架,支持实时处理视觉、音频和文本数据。本文脚本之家将系统讲解 Python 环境下 MediaPipe 的安装、配置及核心功能调用方法,涵盖...
2025-09-10 编程技术
579

基于Python开发一个利率计算器的思路及示例代码
利率计算是金融领域的基础需求,涵盖贷款利息、存款收益、投资回报等场景。传统计算依赖手工公式或Excel表格,存在效率低、易出错等问题。Python凭借其简洁的语法和强大的数学...
2025-09-09 编程技术
518