手搓四人麻将程序
一、麻将牌的表示
在麻将游戏中,总共有一百四十四张牌,这些牌被分为多个类别,每个类别又包含了不同的牌型。具体来说,麻将牌主要包括序数牌、字牌和花牌三大类。序数牌中,包含有万子、条子和筒子,每种花色的序数牌都从一到九排列,每个数字有四张相同的牌。例如,一万、二万一直到九万,一共三十六张万子牌;同样的一条到九条,组成三十六张条子牌;还有一筒到九筒,构成另外三十六张筒子牌。
字牌则有风牌和箭牌之分。风牌包括东、南、西、北四种风向牌,各有四张,总共十六张。箭牌则包含中、发、白搭三种,同样每种四张,共计十二张。与序数牌不同的是,字牌没有大小顺序之分,它们在游戏中的作用主要是增加游戏的复杂性和多样的胡牌方式。比如,在一些胡牌条件中,要求玩家的手牌中包含特定的字牌组合。花牌虽然在大多数麻将规则中不是必需的,但它们的存在为游戏增添了一定的趣味性。花牌包括春、夏、秋、冬和梅、兰、竹、菊这八种,各一张,总共有八张。这些牌通常在游戏中具有特殊的作用,比如可以替代其他牌来形成特殊牌型,或者在某些计分规则中起到加分的效果。
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);对于字牌,我们则可以使用特定的标识符来表示东、南、西、北风以及中、发、白搭等;而花牌则可以用对应的季节或花卉名称来标识。在程序内部,这些属性可以通过枚举类型、类属性或简单的整数编码来实现,以便于游戏逻辑的处理和牌的比较、匹配等操作。
为了便于程序的实现和扩展,我们可能还需要为这些牌设计一些辅助的函数或方法,比如比较两张牌是否相同、判断一张牌是否属于某个特定类别、根据牌的属性生成对应的显示符号等。这些功能将帮助我们在游戏的不同阶段,如发牌、摸牌、打牌、胡牌判定等,快速准确地处理各种与牌相关的操作。通过对麻将牌的详细表示,我们能够确保程序能够正确地模拟麻将游戏中的各种情况,从而为玩家提供一个既真实又有趣的麻将游戏体验。这种对基础元素的深入理解和精确建模,是构建一个完整且功能丰富的麻将程序的关键所在。
二、游戏主循环
在麻将游戏中游戏主循环是整个程序的核心部分它控制着游戏的节奏和进程。游戏主循环的主要任务是不断地循环处理每个玩家的回合直至游戏结束的条件被满足。这个循环需要精确地管理牌的流动、玩家的操作、游戏状态的更新以及胜利条件的检测。
首先游戏开始时需要初始化所有玩家的状态以及牌堆。每个玩家都有自己的手牌区域和得分情况。接着程序进入主循环在每个循环迭代中依次处理当前玩家的回合。当前玩家从牌堆中摸一张牌加入到自己的手牌中。这时候需要检查该玩家是否因为这张牌而胡牌如果满足胡牌条件游戏就在此时结束并结算分数。
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()
如果没有胡牌玩家则需要选择一张牌打出。打出的牌可能会引发其他玩家的一系列操作比如其他玩家可以选择碰牌、杠牌或者胡牌。这时候程序需要暂停当前玩家的回合等待其他玩家的响应。如果玩家选择进行这些操作相应的牌会被移动到新的位置并且可能再次检查是否有人因此胡牌。在处理完这些可能的操作后游戏会继续进行下一位玩家的回合。这个过程一直持续直到牌堆中的牌全部被摸完或者有玩家成功胡牌。如果牌摸完了而没有人胡牌则游戏以平局结束所有玩家的得分根据剩余的牌和游戏规则进行调整。
游戏主循环不仅要处理正常的出牌和胡牌还需要处理一些特殊的游戏事件比如玩家的杠牌操作或者特殊牌型的检测。此外游戏主循环还需要管理玩家的分数及时更新并且在游戏结束时显示最终的结果。在实现游戏主循环时需要特别注意游戏逻辑的正确性和流畅性。每个玩家的操作都需要被准确地记录和响应游戏状态的变化要被及时地反映到用户界面上。同时游戏主循环还要处理可能出现的各种异常情况比如网络延迟(如果是在线游戏)或者玩家的不合法操作确保游戏能够顺利进行。
三、胡牌判定
在麻将程序中,胡牌判定是游戏逻辑的核心部分之一,它的复杂性和多样性直接关系到游戏的真实性和可玩性。胡牌判定不仅仅是一个简单的条件判断,而是一个需要综合考虑多种牌型组合和规则细节的复杂过程。胡牌的基本条件通常是玩家手中的牌形成特定的有效组合。这些组合在不同的麻将变体中可能有所不同,但大多数情况下包括“和牌”的基本结构,比如四个顺子或刻子加一个将牌(一对)。除此之外,还有一些特殊的胡牌形式,如“七对”“清一色”“大三元”等,每一种都有其独特的判定逻辑。
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()
要实现胡牌判定功能,首先需要对玩家的手牌进行分析。手牌通常是一个包含多张牌的列表,程序需要检查这个列表是否满足胡牌的条件。这涉及到对牌的排序、分组和模式匹配。比如,程序需要能够识别出牌中的顺子(三种连续的序数牌)和刻子(三张相同的牌),并确保这些组合的数量和结构符合胡牌的要求。在实现过程中,递归和回溯算法常常被用来穷举可能的牌型组合。这是因为麻将牌的组合方式非常多,直接遍历所有可能性可能会导致效率低下。递归算法可以有效地分解问题,将手牌分成不同的部分进行检查,并在发现不符合条件时及时回溯,尝试其他组合方式。
此外,胡牌判定还需要考虑一些特殊情况,比如是否有剩余的牌未被使用,或者是否满足某些特殊的加分条件。例如,“碰碰胡”要求所有的组合都是刻子,这就需要程序在判定时特别检查是否存在顺子。为了提高判定的效率和准确性,通常会将手牌进行预处理,比如排序和分类。这样可以减少不必要的计算,加快判定过程。同时,为了处理不同的麻将规则变体,胡牌判定逻辑需要具有一定的灵活性和可配置性。可以通过参数化的方式,让程序能够适应不同的规则要求。